新增画布完美像素功能

在图片工具栏接入完美像素交互、占位流程与历史保护

新增严格像素网格检测和登录态同步处理接口

补齐素材类型校验、持久化边界、并发限制及技术文档

修复 Windows rlib 门禁长文件名解析
This commit is contained in:
2026-07-30 11:42:45 +00:00
parent b512430e53
commit ee2cad377f
25 changed files with 2469 additions and 64 deletions
+141
View File
@@ -1963,6 +1963,147 @@ mod tests {
}
}
#[tokio::test]
async fn editor_pixel_art_snap_requires_bearer_auth() {
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
let request_body = serde_json::json!({
"sourceImageSrc": "editor-resource-source",
"projectId": "proj-source",
"canvasCompletion": {
"dialogId": "dialog-pixel-art",
"title": "完美像素",
"placeholder": {
"x": 0,
"y": 0,
"width": 128,
"height": 128,
"originalWidth": 128,
"originalHeight": 128
}
}
});
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/editor/images/pixel-art-snaps")
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn editor_pixel_art_snap_rejects_inline_source_before_processing() {
let state = AppState::new(AppConfig {
external_generation_mode: ExternalGenerationMode::Queue,
..AppConfig::default()
})
.expect("state should build");
let seed_user = seed_phone_user_with_password(&state, "13800138230", TEST_PASSWORD).await;
let token = sign_test_user_token(&state, &seed_user, "sess_editor_pixel_snap_body");
let app = build_router(state);
let request_body = serde_json::json!({
"sourceImageSrc": "data:image/png;base64,AAAA",
"projectId": "proj-source",
"sourceResourceId": "editor-resource-source",
"assetKind": "character",
"generationInputs": { "fields": [], "references": [] },
"canvasCompletion": {
"dialogId": "dialog-pixel-art",
"title": "完美像素",
"placeholder": {
"x": 0,
"y": 0,
"width": 128,
"height": 128,
"originalWidth": 128,
"originalHeight": 128
}
}
});
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/editor/images/pixel-art-snaps")
.header("authorization", format!("Bearer {token}"))
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let body = response
.into_body()
.collect()
.await
.expect("response body should collect")
.to_bytes();
let body_text = String::from_utf8_lossy(&body);
assert!(
body_text.contains("先上传 OSS"),
"handler should reject inline pixel-art sources: {body_text}"
);
}
#[tokio::test]
async fn editor_pixel_art_snap_requires_dialog_id_before_project_or_media_work() {
let state = AppState::new(AppConfig::default()).expect("state should build");
let seed_user = seed_phone_user_with_password(&state, "13800138231", TEST_PASSWORD).await;
let token = sign_test_user_token(&state, &seed_user, "sess_editor_pixel_snap_dialog");
let app = build_router(state);
let request_body = serde_json::json!({
"sourceImageSrc": "editor-resource-source",
"projectId": "proj-does-not-exist",
"canvasCompletion": {
"title": "完美像素",
"placeholder": {
"x": 0,
"y": 0,
"width": 128,
"height": 128,
"originalWidth": 128,
"originalHeight": 128
}
}
});
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/editor/images/pixel-art-snaps")
.header("authorization", format!("Bearer {token}"))
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let body = response
.into_body()
.collect()
.await
.expect("response body should collect")
.to_bytes();
let body_text = String::from_utf8_lossy(&body);
assert!(
body_text.contains("canvasCompletion.dialogId"),
"handler should reject missing dialog ids before project lookup: {body_text}"
);
}
#[tokio::test]
async fn editor_generation_json_validation_preserves_unsupported_media_type() {
let state = AppState::new(AppConfig {
File diff suppressed because it is too large Load Diff
@@ -22,9 +22,9 @@ use crate::{
get_editor_asset_library, get_editor_generation_pricing, get_editor_project,
list_editor_projects, list_public_editor_project_resources, load_recent_editor_project,
remove_editor_image_background, rename_editor_project, save_editor_project_layout,
split_editor_icon_spritesheet, submit_editor_asset_showcase,
toggle_editor_showcase_asset_like, update_editor_asset, update_editor_asset_folder,
update_editor_project_resource_showcase,
snap_editor_image_to_pixel_art, split_editor_icon_spritesheet,
submit_editor_asset_showcase, toggle_editor_showcase_asset_like, update_editor_asset,
update_editor_asset_folder, update_editor_project_resource_showcase,
},
state::AppState,
};
@@ -205,6 +205,13 @@ pub fn router(state: AppState) -> Router<AppState> {
require_bearer_auth,
)),
)
.route(
"/api/editor/images/pixel-art-snaps",
post(snap_editor_image_to_pixel_art).route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
)
.route(
"/api/editor/icon-spritesheets/generations",
post(generate_editor_icon_spritesheet).route_layer(middleware::from_fn_with_state(
+2 -1
View File
@@ -5,7 +5,8 @@ pub mod vector_engine;
pub use pixel_art_snapper::{
PIXEL_ART_ALPHA_COVERAGE_THRESHOLD, PIXEL_ART_ANALYSIS_COLORS, PIXEL_ART_KMEANS_SAMPLE_LIMIT,
PIXEL_ART_MAX_IMAGE_PIXELS, PixelArtSnapError, snap_pixel_art, snap_pixel_art_with_deadline,
PIXEL_ART_MAX_IMAGE_PIXELS, PixelArtSnapError, snap_pixel_art, snap_pixel_art_strict,
snap_pixel_art_strict_with_deadline, snap_pixel_art_with_deadline,
};
pub use vector_engine::{
DownloadedImage, GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_MODEL, GeneratedImages, NANOBANANA_2_MODEL,
File diff suppressed because it is too large Load Diff