From 586d5ec4ee70f9aec47aee8b9b03841960f3b351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=80=E1=B4=8D=E1=B4=9B=E1=B4=8F=E1=B4=80=E1=B4=87?= =?UTF-8?q?=CA=80?= Date: Fri, 6 Jun 2025 23:34:46 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=A4=A7=E5=B9=85=E7=BC=A9=E5=87=8F?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E7=BB=93=E6=9E=9C=E7=9A=84=E4=BA=8C=E8=BF=9B?= =?UTF-8?q?=E5=88=B6=E6=96=87=E4=BB=B6=E4=BD=93=E7=A7=AF=20(#356)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 137 ++++++++++++++++---- Cargo.toml | 12 +- crates/bili_sync/Cargo.toml | 3 +- crates/bili_sync/src/api/handler.rs | 22 ++-- crates/bili_sync/src/api/request.rs | 2 +- crates/bili_sync/src/api/response.rs | 4 +- crates/bili_sync/src/task/http_server.rs | 35 +++-- web/src/lib/api.ts | 16 +-- web/src/lib/components/status-editor.svelte | 8 +- web/src/lib/types.ts | 4 +- web/src/routes/video/[id]/+page.svelte | 6 +- 11 files changed, 175 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a063ef0..382adb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -438,6 +453,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "base85rs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a" + [[package]] name = "bigdecimal" version = "0.4.7" @@ -478,7 +499,6 @@ dependencies = [ "leaky-bucket", "md5", "memchr", - "mime_guess", "once_cell", "prost", "quick-xml", @@ -486,7 +506,7 @@ dependencies = [ "regex", "reqwest", "rsa", - "rust-embed", + "rust-embed-for-web", "sea-orm", "serde", "serde_json", @@ -596,6 +616,37 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "built" version = "0.7.7" @@ -1321,6 +1372,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "gloo-timers" version = "0.3.0" @@ -1967,6 +2031,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "new_mime_guess" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a2dfb3559d53e90b709376af1c379462f7fb3085a0177deb73e6ea0d99eff4" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2727,35 +2801,41 @@ dependencies = [ ] [[package]] -name = "rust-embed" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +name = "rust-embed-for-web" +version = "11.2.1" +source = "git+https://github.com/amtoaer/rust-embed-for-web?tag=v1.0.0#b6eeb475cbe1ad5cae02d5373a1bba12ea58a869" dependencies = [ - "rust-embed-impl", - "rust-embed-utils", + "rust-embed-for-web-impl", + "rust-embed-for-web-utils", "walkdir", ] [[package]] -name = "rust-embed-impl" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +name = "rust-embed-for-web-impl" +version = "11.2.1" +source = "git+https://github.com/amtoaer/rust-embed-for-web?tag=v1.0.0#b6eeb475cbe1ad5cae02d5373a1bba12ea58a869" dependencies = [ + "brotli", + "flate2", + "globset", "proc-macro2", "quote", - "rust-embed-utils", + "rust-embed-for-web-utils", + "shellexpand", "syn 2.0.96", "walkdir", ] [[package]] -name = "rust-embed-utils" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +name = "rust-embed-for-web-utils" +version = "11.2.1" +source = "git+https://github.com/amtoaer/rust-embed-for-web?tag=v1.0.0#b6eeb475cbe1ad5cae02d5373a1bba12ea58a869" dependencies = [ + "base85rs", + "chrono", + "enum_dispatch", + "globset", + "new_mime_guess", "sha2", "walkdir", ] @@ -3128,6 +3208,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "dirs", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3949,8 +4038,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "utoipa" version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +source = "git+https://github.com/amtoaer/utoipa.git?tag=v1.0.1#6d66603333218fe4d0f36e40fc662628c6682a79" dependencies = [ "indexmap", "serde", @@ -3961,8 +4049,7 @@ dependencies = [ [[package]] name = "utoipa-gen" version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +source = "git+https://github.com/amtoaer/utoipa.git?tag=v1.0.1#6d66603333218fe4d0f36e40fc662628c6682a79" dependencies = [ "proc-macro2", "quote", @@ -3973,14 +4060,13 @@ dependencies = [ [[package]] name = "utoipa-swagger-ui" version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161166ec520c50144922a625d8bc4925cc801b2dda958ab69878527c0e5c5d61" +source = "git+https://github.com/amtoaer/utoipa.git?tag=v1.0.1#6d66603333218fe4d0f36e40fc662628c6682a79" dependencies = [ "axum", "base64", "mime_guess", "regex", - "rust-embed", + "rust-embed-for-web", "serde", "serde_json", "url", @@ -3992,8 +4078,7 @@ dependencies = [ [[package]] name = "utoipa-swagger-ui-vendored" version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" +source = "git+https://github.com/amtoaer/utoipa.git?tag=v1.0.1#6d66603333218fe4d0f36e40fc662628c6682a79" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index ea78a0c..239439d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ hex = "0.4.3" leaky-bucket = "1.1.2" md5 = "0.7.0" memchr = "2.7.4" -mime_guess = "2.0.5" once_cell = "1.21.3" prost = "0.13.5" quick-xml = { version = "0.37.5", features = ["async-tokio"] } @@ -53,7 +52,7 @@ reqwest = { version = "0.12.15", features = [ "stream", ], default-features = false } rsa = { version = "0.9.8", features = ["sha2"] } -rust-embed = "8.7.2" +rust-embed-for-web = { git = "https://github.com/amtoaer/rust-embed-for-web", tag = "v1.0.0" } sea-orm = { version = "1.1.11", features = [ "macros", "runtime-tokio-rustls", @@ -71,8 +70,13 @@ toml = "0.8.22" tower = "0.5.2" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["chrono"] } -utoipa = { version = "5.3.1", features = ["axum_extras"] } -utoipa-swagger-ui = { version = "9.0.0", features = ["axum", "vendored"] } +utoipa = { git = "https://github.com/amtoaer/utoipa.git", tag = "v1.0.1", features = [ + "axum_extras", +] } +utoipa-swagger-ui = { git = "https://github.com/amtoaer/utoipa.git", tag = "v1.0.1", features = [ + "axum", + "vendored", +] } validator = { version = "0.20.0", features = ["derive"] } [workspace.metadata.release] diff --git a/crates/bili_sync/Cargo.toml b/crates/bili_sync/Cargo.toml index 80542c2..1cdfe27 100644 --- a/crates/bili_sync/Cargo.toml +++ b/crates/bili_sync/Cargo.toml @@ -29,7 +29,6 @@ hex = { workspace = true } leaky-bucket = { workspace = true } md5 = { workspace = true } memchr = { workspace = true } -mime_guess = { workspace = true } once_cell = { workspace = true } prost = { workspace = true } quick-xml = { workspace = true } @@ -37,7 +36,7 @@ rand = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } rsa = { workspace = true } -rust-embed = { workspace = true } +rust-embed-for-web = { workspace = true } sea-orm = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/bili_sync/src/api/handler.rs b/crates/bili_sync/src/api/handler.rs index 762d406..fc9d070 100644 --- a/crates/bili_sync/src/api/handler.rs +++ b/crates/bili_sync/src/api/handler.rs @@ -14,9 +14,9 @@ use utoipa::OpenApi; use crate::api::auth::OpenAPIAuth; use crate::api::error::InnerApiError; use crate::api::helper::{update_page_download_status, update_video_download_status}; -use crate::api::request::{ResetVideoStatusRequest, VideosRequest}; +use crate::api::request::{UpdateVideoStatusRequest, VideosRequest}; use crate::api::response::{ - PageInfo, ResetAllVideosResponse, ResetVideoResponse, ResetVideoStatusResponse, VideoInfo, VideoResponse, + PageInfo, ResetAllVideosResponse, ResetVideoResponse, UpdateVideoStatusResponse, VideoInfo, VideoResponse, VideoSource, VideoSourcesResponse, VideosResponse, }; use crate::api::wrapper::{ApiError, ApiResponse, ValidatedJson}; @@ -24,7 +24,7 @@ use crate::utils::status::{PageStatus, VideoStatus}; #[derive(OpenApi)] #[openapi( - paths(get_video_sources, get_videos, get_video, reset_video, reset_all_videos, reset_video_status), + paths(get_video_sources, get_videos, get_video, reset_video, reset_all_videos, update_video_status), modifiers(&OpenAPIAuth), security( ("Token" = []), @@ -284,20 +284,20 @@ pub async fn reset_all_videos( })) } -/// 重置指定视频及其分页的指定状态位 +/// 更新特定视频及其所含分页的状态位 #[utoipa::path( post, - path = "/api/videos/{id}/reset-status", - request_body = ResetVideoStatusRequest, + path = "/api/videos/{id}/update-status", + request_body = UpdateVideoStatusRequest, responses( - (status = 200, body = ApiResponse), + (status = 200, body = ApiResponse), ) )] -pub async fn reset_video_status( +pub async fn update_video_status( Path(id): Path, Extension(db): Extension>, - ValidatedJson(request): ValidatedJson, -) -> Result, ApiError> { + ValidatedJson(request): ValidatedJson, +) -> Result, ApiError> { let (video_info, mut pages_info) = tokio::try_join!( video::Entity::find_by_id(id) .into_partial_model::() @@ -343,7 +343,7 @@ pub async fn reset_video_status( } txn.commit().await?; } - Ok(ApiResponse::ok(ResetVideoStatusResponse { + Ok(ApiResponse::ok(UpdateVideoStatusResponse { success: has_video_updates || has_page_updates, video: video_info, pages: pages_info, diff --git a/crates/bili_sync/src/api/request.rs b/crates/bili_sync/src/api/request.rs index 90b9fac..fed3faa 100644 --- a/crates/bili_sync/src/api/request.rs +++ b/crates/bili_sync/src/api/request.rs @@ -28,7 +28,7 @@ pub struct PageStatusUpdate { } #[derive(Deserialize, ToSchema, Validate)] -pub struct ResetVideoStatusRequest { +pub struct UpdateVideoStatusRequest { #[serde(default)] #[validate(nested)] pub video_updates: Vec, diff --git a/crates/bili_sync/src/api/response.rs b/crates/bili_sync/src/api/response.rs index 66a334e..8440161 100644 --- a/crates/bili_sync/src/api/response.rs +++ b/crates/bili_sync/src/api/response.rs @@ -40,7 +40,7 @@ pub struct ResetAllVideosResponse { } #[derive(Serialize, ToSchema)] -pub struct ResetVideoStatusResponse { +pub struct UpdateVideoStatusResponse { pub success: bool, pub video: VideoInfo, pub pages: Vec, @@ -58,6 +58,7 @@ pub struct VideoInfo { pub id: i32, pub name: String, pub upper_name: String, + #[schema(value_type = [u32; 5])] #[serde(serialize_with = "serde_video_download_status")] pub download_status: u32, } @@ -69,6 +70,7 @@ pub struct PageInfo { pub video_id: i32, pub pid: i32, pub name: String, + #[schema(value_type = [u32; 5])] #[serde(serialize_with = "serde_page_download_status")] pub download_status: u32, } diff --git a/crates/bili_sync/src/task/http_server.rs b/crates/bili_sync/src/task/http_server.rs index 92a0e1c..ccfd72f 100644 --- a/crates/bili_sync/src/task/http_server.rs +++ b/crates/bili_sync/src/task/http_server.rs @@ -1,24 +1,27 @@ use std::sync::Arc; use anyhow::{Context, Result}; +use axum::body::Body; use axum::extract::Request; -use axum::http::{Uri, header}; +use axum::http::{Response, Uri, header}; use axum::response::IntoResponse; use axum::routing::{get, post}; use axum::{Extension, Router, ServiceExt, middleware}; use reqwest::StatusCode; -use rust_embed::Embed; +use rust_embed_for_web::{EmbedableFile, RustEmbed}; use sea_orm::DatabaseConnection; use utoipa::OpenApi; use utoipa_swagger_ui::{Config, SwaggerUi}; use crate::api::auth; use crate::api::handler::{ - ApiDoc, get_video, get_video_sources, get_videos, reset_all_videos, reset_video, reset_video_status, + ApiDoc, get_video, get_video_sources, get_videos, reset_all_videos, reset_video, update_video_status, }; use crate::config::CONFIG; -#[derive(Embed)] +#[derive(RustEmbed)] +#[preserve_source = false] +#[gzip = false] #[folder = "../../web/build"] struct Asset; @@ -28,7 +31,7 @@ pub async fn http_server(database_connection: Arc) -> Result .route("/api/videos", get(get_videos)) .route("/api/videos/{id}", get(get_video)) .route("/api/videos/{id}/reset", post(reset_video)) - .route("/api/videos/{id}/reset-status", post(reset_video_status)) + .route("/api/videos/{id}/update-status", post(update_video_status)) .route("/api/videos/reset-all", post(reset_all_videos)) .merge( SwaggerUi::new("/swagger-ui/") @@ -55,11 +58,19 @@ async fn frontend_files(uri: Uri) -> impl IntoResponse { if path.is_empty() || Asset::get(path).is_none() { path = "index.html"; } - match Asset::get(path) { - Some(content) => { - let mime = mime_guess::from_path(path).first_or_octet_stream(); - ([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response() - } - None => (StatusCode::NOT_FOUND, "404 Not Found").into_response(), - } + let Some(content) = Asset::get(path) else { + return (StatusCode::NOT_FOUND, "404 Not Found").into_response(); + }; + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + content.mime_type().as_deref().unwrap_or("application/octet-stream"), + ) + .header(header::CONTENT_ENCODING, "br") + // safety: `RustEmbed` will always generate br-compressed files if the feature is enabled + .body(Body::from(content.data_br().unwrap())) + .unwrap_or_else(|_| { + return (StatusCode::INTERNAL_SERVER_ERROR, "500 Internal Server Error").into_response(); + }) } diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 5f5f89a..5907143 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -6,8 +6,8 @@ import type { VideoResponse, ResetVideoResponse, ResetAllVideosResponse, - ResetVideoStatusRequest, - ResetVideoStatusResponse, + UpdateVideoStatusRequest, + UpdateVideoStatusResponse, ApiError } from './types'; @@ -162,11 +162,11 @@ class ApiClient { * @param id 视频 ID * @param request 重置请求参数 */ - async resetVideoStatus( + async updateVideoStatus( id: number, - request: ResetVideoStatusRequest - ): Promise> { - return this.post(`/videos/${id}/reset-status`, request); + request: UpdateVideoStatusRequest + ): Promise> { + return this.post(`/videos/${id}/update-status`, request); } } @@ -203,8 +203,8 @@ export const api = { /** * 重置视频状态位 */ - resetVideoStatus: (id: number, request: ResetVideoStatusRequest) => - apiClient.resetVideoStatus(id, request), + updateVideoStatus: (id: number, request: UpdateVideoStatusRequest) => + apiClient.updateVideoStatus(id, request), /** * 设置认证 token diff --git a/web/src/lib/components/status-editor.svelte b/web/src/lib/components/status-editor.svelte index 7009e07..f6c1d36 100644 --- a/web/src/lib/components/status-editor.svelte +++ b/web/src/lib/components/status-editor.svelte @@ -9,14 +9,14 @@ SheetTitle } from '$lib/components/ui/sheet/index.js'; import StatusTaskCard from './status-task-card.svelte'; - import type { VideoInfo, PageInfo, StatusUpdate, ResetVideoStatusRequest } from '$lib/types'; + import type { VideoInfo, PageInfo, StatusUpdate, UpdateVideoStatusRequest } from '$lib/types'; import { toast } from 'svelte-sonner'; export let open = false; export let video: VideoInfo; export let pages: PageInfo[] = []; export let loading = false; - export let onsubmit: (request: ResetVideoStatusRequest) => void; + export let onsubmit: (request: UpdateVideoStatusRequest) => void; // 视频任务名称(与后端 VideoStatus 对应) const videoTaskNames = ['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分P下载']; @@ -109,8 +109,8 @@ return hasVideoChanges() || hasPageChanges(); } - function buildRequest(): ResetVideoStatusRequest { - const request: ResetVideoStatusRequest = {}; + function buildRequest(): UpdateVideoStatusRequest { + const request: UpdateVideoStatusRequest = {}; // 构建视频状态更新 if (hasVideoChanges()) { diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index c076dc1..6ac5b8b 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -91,13 +91,13 @@ export interface PageStatusUpdate { } // 重置视频状态请求类型 -export interface ResetVideoStatusRequest { +export interface UpdateVideoStatusRequest { video_updates?: StatusUpdate[]; page_updates?: PageStatusUpdate[]; } // 重置视频状态响应类型 -export interface ResetVideoStatusResponse { +export interface UpdateVideoStatusResponse { success: boolean; video: VideoInfo; pages: PageInfo[]; diff --git a/web/src/routes/video/[id]/+page.svelte b/web/src/routes/video/[id]/+page.svelte index ae690c9..9aec59d 100644 --- a/web/src/routes/video/[id]/+page.svelte +++ b/web/src/routes/video/[id]/+page.svelte @@ -4,7 +4,7 @@ import { onMount } from 'svelte'; import { Button } from '$lib/components/ui/button/index.js'; import api from '$lib/api'; - import type { ApiError, VideoResponse, ResetVideoStatusRequest } from '$lib/types'; + import type { ApiError, VideoResponse, UpdateVideoStatusRequest } from '$lib/types'; import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw'; import EditIcon from '@lucide/svelte/icons/edit'; import { setBreadcrumb } from '$lib/stores/breadcrumb'; @@ -60,12 +60,12 @@ loadVideoDetail(); } - async function handleStatusEditorSubmit(request: ResetVideoStatusRequest) { + async function handleStatusEditorSubmit(request: UpdateVideoStatusRequest) { if (!videoData) return; statusEditorLoading = true; try { - const result = await api.resetVideoStatus(videoData.video.id, request); + const result = await api.updateVideoStatus(videoData.video.id, request); const data = result.data; if (data.success) {