From 225b525ba1f532c780a679ffc5e3be934225c123 Mon Sep 17 00:00:00 2001
From: super <2903208875@qq.com>
Date: Mon, 27 Apr 2026 09:18:10 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=9E=B6=E6=9E=84?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 2 +
app/.env | 5 +-
app/amazon/__pycache__/main.cpython-312.pyc | Bin 29925 -> 29925 bytes
app/assets/convert.js | 2 +-
app/assets/dedupe.js | 2 +-
app/assets/delete-brand.js | 2 +-
app/assets/split.js | 2 +-
app/new_web_source/convert.html | 8 +-
app/new_web_source/dedupe.html | 8 +-
app/new_web_source/delete-brand.html | 8 +-
app/new_web_source/split.html | 8 +-
app/web_source/brand.html | 48 +-
backend-java/jstack-33832.txt | 1106 +++++++++++++++++
backend-java/pom.xml | 12 +
.../exception/GlobalExceptionHandler.java | 3 +
.../service/DistributedJobLockService.java | 8 +-
.../config/AppearancePatentProperties.java | 16 +
.../aiimage/config/PropertiesConfig.java | 2 +-
.../aiimage/config/TaskFileJobConfig.java | 27 +
.../config/TransientStorageProperties.java | 15 +
.../client/AppearancePatentCozeClient.java | 156 +++
.../AppearancePatentController.java | 143 +++
.../dto/AppearancePatentParseRequest.java | 28 +
.../dto/AppearancePatentParsedPayloadDto.java | 21 +
.../dto/AppearancePatentResultRowDto.java | 33 +
.../dto/AppearancePatentSourceFileDto.java | 15 +
.../AppearancePatentSubmitResultRequest.java | 24 +
.../dto/AppearancePatentTaskBatchRequest.java | 15 +
.../model/vo/AppearancePatentDashboardVo.java | 17 +
.../vo/AppearancePatentHistoryItemVo.java | 33 +
.../model/vo/AppearancePatentHistoryVo.java | 14 +
.../model/vo/AppearancePatentParseVo.java | 26 +
.../model/vo/AppearancePatentParsedRowVo.java | 28 +
.../model/vo/AppearancePatentTaskBatchVo.java | 16 +
.../vo/AppearancePatentTaskDetailVo.java | 16 +
.../model/vo/AppearancePatentTaskItemVo.java | 23 +
.../AppearancePatentTaskCacheService.java | 144 +++
.../service/AppearancePatentTaskService.java | 1001 +++++++++++++++
.../BrandTaskProgressCacheService.java | 58 +-
.../brand/service/BrandTaskService.java | 79 +-
.../service/BrandTaskStorageService.java | 82 +-
.../convert/service/ConvertRunService.java | 4 +-
.../dedupe/service/DedupeRunService.java | 4 +-
.../model/vo/DeleteBrandResultItemVo.java | 4 +
.../service/DeleteBrandRunService.java | 244 +++-
.../service/DeleteBrandStaleTaskService.java | 44 +-
.../service/DeleteBrandTaskCacheService.java | 63 +-
.../DeleteBrandTaskStorageService.java | 100 +-
.../object/RustfsObjectStorageService.java | 85 ++
.../model/vo/PatrolDeleteResultItemVo.java | 4 +
.../service/PatrolDeleteTaskCacheService.java | 74 +-
.../service/PatrolDeleteTaskService.java | 147 ++-
.../PermissionMenuSchemaInitializer.java | 12 +-
.../controller/PriceTrackController.java | 2 +-
.../dto/PriceTrackSubmitResultRequest.java | 3 +
.../model/vo/PriceTrackResultItemVo.java | 4 +
.../PriceTrackExcelAssemblyService.java | 18 +-
.../service/PriceTrackTaskCacheService.java | 74 +-
.../service/PriceTrackTaskService.java | 70 +-
.../model/vo/ProductRiskResultItemVo.java | 4 +
.../service/ProductRiskTaskCacheService.java | 74 +-
.../service/ProductRiskTaskService.java | 75 +-
.../model/vo/QueryAsinResultItemVo.java | 4 +
.../service/QueryAsinTaskCacheService.java | 38 +-
.../service/QueryAsinTaskService.java | 147 ++-
.../service/ShopMatchTaskCacheService.java | 62 +-
.../service/ShopMatchTaskService.java | 82 +-
.../split/service/SplitRunService.java | 4 +-
.../controller/TaskFileJobController.java | 52 +
.../task/mapper/TaskFileJobMapper.java | 9 +
.../mapper/TaskProgressSnapshotMapper.java | 9 +
.../task/mapper/TaskResultItemMapper.java | 9 +
.../task/mapper/TaskResultPayloadMapper.java | 9 +
.../model/dto/TaskFileJobDispatchEvent.java | 14 +
.../task/model/dto/TaskFileJobMessage.java | 16 +
.../task/model/entity/TaskFileJobEntity.java | 28 +
.../entity/TaskProgressSnapshotEntity.java | 28 +
.../model/entity/TaskResultItemEntity.java | 29 +
.../model/entity/TaskResultPayloadEntity.java | 26 +
.../task/service/TaskFileJobConsumer.java | 39 +
.../TaskFileJobDispatchCoordinator.java | 42 +
.../service/TaskFileJobLocalDispatcher.java | 53 +
.../task/service/TaskFileJobPublisher.java | 50 +
.../task/service/TaskFileJobService.java | 225 ++++
.../service/TaskProgressSnapshotService.java | 98 ++
.../task/service/TaskResultFileJobWorker.java | 166 +++
.../task/service/TaskResultItemService.java | 233 ++++
.../service/TaskResultPayloadService.java | 139 +++
.../TaskScopePayloadStorageService.java | 257 +---
.../TransientPayloadStorageService.java | 149 +++
.../service/ZiniaoSessionCacheService.java | 61 +-
.../service/ZiniaoShopIndexService.java | 43 +-
.../resources/application-local.example.yml | 35 +-
.../src/main/resources/application.yml | 35 +
.../resources/db/V34__appearance_patent.sql | 85 ++
.../V35__task_result_payload_and_file_job.sql | 74 ++
frontend-vue/appearance-patent.html | 12 +
frontend-vue/src/appearance-patent-main.ts | 7 +
.../components/BrandAppearancePatentTab.vue | 487 ++++++++
.../brand/components/BrandConvertTab.vue | 8 +-
.../pages/brand/components/BrandDedupeTab.vue | 9 +-
.../brand/components/BrandDeleteBrandTab.vue | 39 +-
.../brand/components/BrandPatrolDeleteTab.vue | 16 +-
.../brand/components/BrandPriceTrackTab.vue | 66 +-
.../brand/components/BrandProductRiskTab.vue | 2 +-
.../brand/components/BrandQueryAsinTab.vue | 16 +-
.../brand/components/BrandShopMatchTab.vue | 81 +-
.../pages/brand/components/BrandSplitTab.vue | 9 +-
.../pages/brand/components/BrandTopBar.vue | 2 +
frontend-vue/src/shared/api/java-modules.ts | 171 +++
.../src/shared/task-progress-config.ts | 8 +-
frontend-vue/vite.config.ts | 1 +
new_web_source/assets/brand-UU-ckLq6.js | 1 -
new_web_source/assets/convert-7wWJ02Tw.css | 1 -
new_web_source/assets/convert.js | 1 -
new_web_source/assets/dedupe-BpNHwt51.css | 1 -
new_web_source/assets/dedupe.js | 1 -
.../assets/delete-brand-CWLpe7lu.css | 1 -
new_web_source/assets/delete-brand.js | 1 -
new_web_source/assets/el-input-CezJelDw.css | 1 -
.../assets/listingFilters-BpGOU_pJ.js | 1 -
.../assets/listingFilters-CK58rX4v.css | 1 -
.../assets/patrol-delete-BPu1AAPM.css | 1 -
new_web_source/assets/patrol-delete.js | 1 -
.../assets/price-track-85NbckiJ.css | 1 -
.../assets/price-track-RLJUgMl_.css | 1 -
new_web_source/assets/price-track.js | 1 -
.../assets/product-risk-cfL3Ch5-.css | 1 -
new_web_source/assets/product-risk.js | 1 -
new_web_source/assets/pywebview-CeWJDVeG.js | 55 -
new_web_source/assets/pywebview-ij4pgMq8.css | 1 -
new_web_source/assets/query-asin-B0yMJ9Or.css | 1 -
new_web_source/assets/query-asin-B5e5m-vs.css | 1 -
new_web_source/assets/query-asin-Ba06RE4W.css | 1 -
new_web_source/assets/query-asin-CcqwgsLE.css | 1 -
new_web_source/assets/query-asin.js | 1 -
new_web_source/assets/shop-match-Banz7DWL.css | 1 -
new_web_source/assets/shop-match.js | 1 -
new_web_source/assets/split-CRUIYKS6.css | 1 -
new_web_source/assets/split.js | 1 -
new_web_source/assets/zh-cn-DyKmB71i.js | 1 -
new_web_source/convert.html | 8 +-
new_web_source/dedupe.html | 8 +-
new_web_source/delete-brand.html | 8 +-
new_web_source/patrol-delete.html | 11 +-
new_web_source/price-track.html | 11 +-
new_web_source/product-risk.html | 9 +-
new_web_source/query-asin.html | 11 +-
new_web_source/shop-match.html | 11 +-
new_web_source/split.html | 8 +-
150 files changed, 7013 insertions(+), 804 deletions(-)
create mode 100644 backend-java/jstack-33832.txt
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/config/TaskFileJobConfig.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParseRequest.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParsedPayloadDto.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentResultRowDto.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSourceFileDto.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSubmitResultRequest.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentTaskBatchRequest.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentDashboardVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryItemVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParseVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParsedRowVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskBatchVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskDetailVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskItemVo.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskCacheService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/controller/TaskFileJobController.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/mapper/TaskFileJobMapper.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/mapper/TaskProgressSnapshotMapper.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/mapper/TaskResultItemMapper.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/mapper/TaskResultPayloadMapper.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/dto/TaskFileJobDispatchEvent.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/dto/TaskFileJobMessage.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/entity/TaskFileJobEntity.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/entity/TaskProgressSnapshotEntity.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/entity/TaskResultItemEntity.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/model/entity/TaskResultPayloadEntity.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobConsumer.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobDispatchCoordinator.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobLocalDispatcher.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobPublisher.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskProgressSnapshotService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java
create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TransientPayloadStorageService.java
create mode 100644 backend-java/src/main/resources/db/V34__appearance_patent.sql
create mode 100644 backend-java/src/main/resources/db/V35__task_result_payload_and_file_job.sql
create mode 100644 frontend-vue/appearance-patent.html
create mode 100644 frontend-vue/src/appearance-patent-main.ts
create mode 100644 frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue
delete mode 100644 new_web_source/assets/brand-UU-ckLq6.js
delete mode 100644 new_web_source/assets/convert-7wWJ02Tw.css
delete mode 100644 new_web_source/assets/convert.js
delete mode 100644 new_web_source/assets/dedupe-BpNHwt51.css
delete mode 100644 new_web_source/assets/dedupe.js
delete mode 100644 new_web_source/assets/delete-brand-CWLpe7lu.css
delete mode 100644 new_web_source/assets/delete-brand.js
delete mode 100644 new_web_source/assets/el-input-CezJelDw.css
delete mode 100644 new_web_source/assets/listingFilters-BpGOU_pJ.js
delete mode 100644 new_web_source/assets/listingFilters-CK58rX4v.css
delete mode 100644 new_web_source/assets/patrol-delete-BPu1AAPM.css
delete mode 100644 new_web_source/assets/patrol-delete.js
delete mode 100644 new_web_source/assets/price-track-85NbckiJ.css
delete mode 100644 new_web_source/assets/price-track-RLJUgMl_.css
delete mode 100644 new_web_source/assets/price-track.js
delete mode 100644 new_web_source/assets/product-risk-cfL3Ch5-.css
delete mode 100644 new_web_source/assets/product-risk.js
delete mode 100644 new_web_source/assets/pywebview-CeWJDVeG.js
delete mode 100644 new_web_source/assets/pywebview-ij4pgMq8.css
delete mode 100644 new_web_source/assets/query-asin-B0yMJ9Or.css
delete mode 100644 new_web_source/assets/query-asin-B5e5m-vs.css
delete mode 100644 new_web_source/assets/query-asin-Ba06RE4W.css
delete mode 100644 new_web_source/assets/query-asin-CcqwgsLE.css
delete mode 100644 new_web_source/assets/query-asin.js
delete mode 100644 new_web_source/assets/shop-match-Banz7DWL.css
delete mode 100644 new_web_source/assets/shop-match.js
delete mode 100644 new_web_source/assets/split-CRUIYKS6.css
delete mode 100644 new_web_source/assets/split.js
delete mode 100644 new_web_source/assets/zh-cn-DyKmB71i.js
diff --git a/.gitignore b/.gitignore
index 55e3fff..5fc4f49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,5 @@ app/assets/
app/new_web_source
app/user_data/
OPS_REDIS_MYSQL_OPTIMIZATION_NOTES.md
+
+架构.md
\ No newline at end of file
diff --git a/app/.env b/app/.env
index a8104b3..517a315 100644
--- a/app/.env
+++ b/app/.env
@@ -13,7 +13,8 @@ client_name=ShuFuAI
# java_api_base=http://47.111.163.154:18080
-java_api_base=http://127.0.0.1:18080
-# java_api_base=http://8.136.19.173:18080
+# java_api_base=http://127.0.0.1:18080
+java_api_base=http://8.136.19.173:18080
+# java_api_base=http://121.196.149.225:18080
diff --git a/app/amazon/__pycache__/main.cpython-312.pyc b/app/amazon/__pycache__/main.cpython-312.pyc
index 7e8147dccbdc7c5e7b3a7eaba9b72faf437568d6..a92e3b52a8d2a1915c4897f75ad7e0bd2893d995 100644
GIT binary patch
delta 22
ccmaF*lJV(FM()$Ryj%=G!1d|PM($fB0A_9oivR!s
delta 22
ccmaF*lJV(FM()$Ryj%=GkihwBBloQm0Aol8_5c6?
diff --git a/app/assets/convert.js b/app/assets/convert.js
index a140d73..bec17e0 100644
--- a/app/assets/convert.js
+++ b/app/assets/convert.js
@@ -1 +1 @@
-import{d as A,o as H,a as n,c as o,b as z,B as K,e as s,F as w,r as F,t as r,f as _,n as E,w as M,s as j,u as q,g as u,h as T,x as J,i as k,E as i,y as L,z as O,A as G,_ as Q,p as W,q as X}from"./pywebview-CeWJDVeG.js";import{e as Y}from"./brand-UU-ckLq6.js";const Z={class:"page-shell module-page"},ee={class:"main-content"},se={class:"left-panel"},te={class:"upload-zone"},ae={class:"selected-files clean-placeholder"},le={key:0,class:"more-line"},ne={key:1},oe={class:"option-group"},ie=["value"],re={class:"template-item-content"},ce={class:"label"},ue={key:0,class:"template-tag"},de={key:1,class:"template-tag muted"},ve={key:2,class:"template-tag custom"},pe={class:"desc"},_e={key:0,class:"empty-option"},fe={class:"option-group"},me={class:"radio-item active"},he={class:"desc"},ye={class:"run-row"},ge=["disabled"],be={class:"loading-msg"},we={class:"right-panel"},ke={class:"task-list-wrap"},Ce={class:"clean-result-summary"},xe={class:"summary-card"},Fe={class:"summary-card"},Pe={class:"summary-card"},Ee={class:"result-list-wrap"},Te={key:0,class:"empty-tasks"},Ie={key:1,class:"task-list clean-result-list"},Be={class:"left"},Ne=["title"],$e={key:0,class:"files"},De={key:1,class:"files"},Re={class:"task-right"},Se=["onClick"],Ue=["onClick"],Ve=A({__name:"BrandConvertTab",setup(He){const v=u([]),g=u(""),f=u([]),m=u(!1),b=u([]),h=u({total:0,successCount:0,failedCount:0,items:[]}),d=u([]),c=u(""),C=T(()=>v.value.slice(0,8)),I=T(()=>d.value.find(a=>a.id===c.value)||null);async function P(a){const e=k();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const l of a){const p=typeof l=="string"?l:l.absolutePath,V=typeof l=="string"?void 0:l.relativePath,y=await e.upload_file_to_java(p,V);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${p}`);t.push(y.data)}return t}async function B(a){const e=await J();d.value=e||[];const t=d.value.find(p=>p.id===c.value);if(t){c.value=t.id;return}const l=d.value.find(p=>p.isDefault);if(l){c.value=l.id;return}c.value=d.value[0]?.id||""}async function N(a,e){v.value=a,g.value="",f.value=await P(a),i.success(e)}async function $(){const a=k();if(!a?.select_brand_xlsx_files){i.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await a.select_brand_xlsx_files();if(!e?.length)return;await N(e,`已选择 ${e.length} 个待转换文件`)}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function D(){const a=k();if(!a?.select_brand_folder){i.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await a.select_brand_folder();if(!e)return;g.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await Y(e);if(t.success&&t.items?.length){v.value=t.items.map(l=>l.relativePath),f.value=await P(t.items),i.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}i.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function R(){if(!f.value.length){i.warning("请先选择待转换 Excel 文件或文件夹");return}if(!c.value){i.warning("请先选择模板");return}try{m.value=!0;const a=await L({files:f.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),templateId:c.value,archiveName:f.value.some(e=>!!e.relativePath)&&g.value?g.value:void 0});h.value=a,b.value=a.items||[],await x(),i.success("格式转换完成")}catch(a){i.error(a instanceof Error?a.message:"格式转换失败")}finally{m.value=!1}}async function x(){try{const a=await O();b.value=a.items||[],h.value={total:a.items?.length||0,successCount:a.items?.filter(e=>e.success).length||0,failedCount:a.items?.filter(e=>!e.success).length||0,items:a.items||[]}}catch{}}async function S(a){try{await G(a),await x(),i.success("已删除")}catch(e){i.error(e instanceof Error?e.message:"删除失败")}}async function U(a){const e=k();if(!a.downloadUrl){i.warning("当前结果没有下载地址");return}const t=a.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.txt`;if(e?.save_file_from_url_new){const l=await e.save_file_from_url_new(a.downloadUrl,t);l.success?i.success(`已保存:${l.path||t}`):l.error&&l.error!=="用户取消"&&i.error(l.error);return}}return H(()=>{B().catch(()=>{}),x().catch(()=>{})}),(a,e)=>(n(),o("div",Z,[z(K,{active:"convert"}),s("div",ee,[s("aside",se,[e[5]||(e[5]=s("div",{class:"section-title"},"选择文件",-1)),s("div",te,[e[1]||(e[1]=s("div",{class:"hint"},"选择需要转换的 Excel 文件或文件夹,将按所选模板生成 txt 文件。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:$},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:D},"选择文件夹")]),s("div",ae,[v.value.length?(n(),o(w,{key:0},[(n(!0),o(w,null,F(C.value,t=>(n(),o("span",{key:t},r(t),1))),128)),v.value.length>C.value.length?(n(),o("span",le," 还有 "+r(v.value.length-C.value.length)+" 个文件未展开显示 ",1)):_("",!0)],64)):(n(),o("span",ne,"暂未选择待转换文件"))])]),e[6]||(e[6]=s("div",{class:"section-title"},"模板选择",-1)),s("div",oe,[(n(!0),o(w,null,F(d.value,t=>(n(),o("label",{key:t.id,class:E(["radio-item",{active:c.value===t.id}])},[M(s("input",{"onUpdate:modelValue":e[0]||(e[0]=l=>c.value=l),type:"radio",value:t.id,name:"convert-template"},null,8,ie),[[j,c.value]]),s("div",re,[s("div",null,[s("span",ce,[q(r(t.templateName)+" ",1),t.isDefault?(n(),o("span",ue,"默认")):t.builtIn?(n(),o("span",de,"内置")):(n(),o("span",ve,"自定义"))]),s("div",pe,"输出文件:"+r(t.outputFilename),1)])])],2))),128)),d.value.length?_("",!0):(n(),o("div",_e,"暂无可用模板"))]),e[7]||(e[7]=s("div",{class:"section-title"},"转换说明",-1)),s("div",fe,[s("label",me,[e[3]||(e[3]=s("input",{type:"checkbox",checked:"",disabled:""},null,-1)),s("div",null,[e[2]||(e[2]=s("span",{class:"label"},"按当前模板输出",-1)),s("div",he,"当前模板:"+r(I.value?.templateName||"未加载模板"),1)])]),e[4]||(e[4]=s("label",{class:"radio-item active"},[s("input",{type:"checkbox",checked:"",disabled:""}),s("div",null,[s("span",{class:"label"},"SKU 使用雪花ID-序号"),s("div",{class:"desc"},"例如 1773816146796-1")])],-1))]),s("div",ye,[s("button",{type:"button",class:"btn-run",disabled:m.value,onClick:R},r(m.value?"转换中...":"开始转换"),9,ge),s("span",be,r(m.value?"正在生成并上传结果,请稍候…":"选择文件后即可执行格式转换,结果会显示在右侧供下载"),1)])]),s("section",we,[e[12]||(e[12]=s("div",{class:"panel-header"},"转换结果",-1)),s("div",ke,[s("div",Ce,[s("div",xe,[e[8]||(e[8]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,r(h.value.total),1)]),s("div",Fe,[e[9]||(e[9]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,r(h.value.successCount),1)]),s("div",Pe,[e[10]||(e[10]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,r(h.value.failedCount),1)])]),s("div",Ee,[e[11]||(e[11]=s("div",{class:"result-list-header"},[s("span",null,"生成的结果压缩包列表")],-1)),b.value.length===0?(n(),o("div",Te," 暂无转换结果,完成格式转换后会在这里展示结果压缩包 ")):(n(),o("ul",Ie,[(n(!0),o(w,null,F(b.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Be,[s("span",{class:"id",title:t.sourceFilename},r(t.sourceFilename||"-"),9,Ne),t.outputFilename?(n(),o("div",$e,"压缩包文件:"+r(t.outputFilename),1)):_("",!0),t.error?(n(),o("div",De,"错误信息:"+r(t.error),1)):_("",!0)]),s("div",Re,[s("span",{class:E(["status",t.success?"success":"failed"])},r(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:l=>U(t)}," 下载压缩包 ",8,Se)):_("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:l=>S(t.resultId)}," 删除 ",8,Ue)):_("",!0)])]))),128))]))])])])])]))}}),Ae=Q(Ve,[["__scopeId","data-v-7c280b44"]]);W(Ae).use(X).mount("#app");
+import{d as A,o as H,a as l,c as n,b as z,B as K,e as s,F as w,r as F,t as i,f as _,n as P,w as M,u as j,x as q,g as d,h as E,y as J,i as k,E as r,z as L,A as O,C as G,D as Q,_ as W,q as X,s as Y}from"./pywebview-C66x_2Dh.js";import{e as Z}from"./brand-BZije8D7.js";const ee={class:"page-shell module-page"},se={class:"main-content"},te={class:"left-panel"},ae={class:"upload-zone"},le={class:"selected-files clean-placeholder"},ne={key:0,class:"more-line"},oe={key:1},re={class:"option-group"},ie=["value"],ce={class:"template-item-content"},ue={class:"label"},de={key:0,class:"template-tag"},ve={key:1,class:"template-tag muted"},pe={key:2,class:"template-tag custom"},_e={class:"desc"},fe={key:0,class:"empty-option"},me={class:"option-group"},he={class:"radio-item active"},ye={class:"desc"},ge={class:"run-row"},be=["disabled"],we={class:"loading-msg"},ke={class:"right-panel"},Ce={class:"task-list-wrap"},xe={class:"clean-result-summary"},Fe={class:"summary-card"},Ie={class:"summary-card"},Pe={class:"summary-card"},Ee={class:"result-list-wrap"},Te={key:0,class:"empty-tasks"},Be={key:1,class:"task-list clean-result-list"},De={class:"left"},Ne=["title"],Re={key:0,class:"files"},$e={key:1,class:"files"},Se={class:"task-right"},Ue=["onClick"],Ve=["onClick"],Ae=A({__name:"BrandConvertTab",setup(ze){const p=d([]),g=d(""),f=d([]),m=d(!1),b=d([]),h=d({total:0,successCount:0,failedCount:0,items:[]}),v=d([]),u=d(""),C=E(()=>p.value.slice(0,8)),T=E(()=>v.value.find(a=>a.id===u.value)||null);async function I(a){const e=k();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const o of a){const c=typeof o=="string"?o:o.absolutePath,V=typeof o=="string"?void 0:o.relativePath,y=await e.upload_file_to_java(c,V);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${c}`);t.push(y.data)}return t}async function B(a){const e=await J();v.value=e||[];const t=v.value.find(c=>c.id===u.value);if(t){u.value=t.id;return}const o=v.value.find(c=>c.isDefault);if(o){u.value=o.id;return}u.value=v.value[0]?.id||""}async function D(a,e){p.value=a,g.value="",f.value=await I(a),r.success(e)}async function N(){const a=k();if(!a?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await a.select_brand_xlsx_files();if(!e?.length)return;await D(e,`已选择 ${e.length} 个待转换文件`)}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function R(){const a=k();if(!a?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await a.select_brand_folder();if(!e)return;g.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await Z(e);if(t.success&&t.items?.length){p.value=t.items.map(o=>o.relativePath),f.value=await I(t.items),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function $(){if(!f.value.length){r.warning("请先选择待转换 Excel 文件或文件夹");return}if(!u.value){r.warning("请先选择模板");return}try{m.value=!0;const a=await L({files:f.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),templateId:u.value,archiveName:f.value.some(e=>!!e.relativePath)&&g.value?g.value:void 0});h.value=a,b.value=a.items||[],await x(),r.success("格式转换完成")}catch(a){r.error(a instanceof Error?a.message:"格式转换失败")}finally{m.value=!1}}async function x(){try{const a=await O();b.value=a.items||[],h.value={total:a.items?.length||0,successCount:a.items?.filter(e=>e.success).length||0,failedCount:a.items?.filter(e=>!e.success).length||0,items:a.items||[]}}catch{}}async function S(a){try{await G(a),await x(),r.success("已删除")}catch(e){r.error(e instanceof Error?e.message:"删除失败")}}async function U(a){const e=k(),t=a.downloadUrl||(a.resultId?Q(a.resultId):"");if(!t){r.warning("当前结果没有下载地址");return}const o=a.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.txt`;if(e?.save_file_from_url_new){const c=await e.save_file_from_url_new(t,o);c.success?r.success(`已保存:${c.path||o}`):c.error&&c.error!=="用户取消"&&r.error(c.error);return}}return H(()=>{B().catch(()=>{}),x().catch(()=>{})}),(a,e)=>(l(),n("div",ee,[z(K,{active:"convert"}),s("div",se,[s("aside",te,[e[5]||(e[5]=s("div",{class:"section-title"},"选择文件",-1)),s("div",ae,[e[1]||(e[1]=s("div",{class:"hint"},"选择需要转换的 Excel 文件或文件夹,将按所选模板生成 txt 文件。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:N},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:R},"选择文件夹")]),s("div",le,[p.value.length?(l(),n(w,{key:0},[(l(!0),n(w,null,F(C.value,t=>(l(),n("span",{key:t},i(t),1))),128)),p.value.length>C.value.length?(l(),n("span",ne," 还有 "+i(p.value.length-C.value.length)+" 个文件未展开显示 ",1)):_("",!0)],64)):(l(),n("span",oe,"暂未选择待转换文件"))])]),e[6]||(e[6]=s("div",{class:"section-title"},"模板选择",-1)),s("div",re,[(l(!0),n(w,null,F(v.value,t=>(l(),n("label",{key:t.id,class:P(["radio-item",{active:u.value===t.id}])},[M(s("input",{"onUpdate:modelValue":e[0]||(e[0]=o=>u.value=o),type:"radio",value:t.id,name:"convert-template"},null,8,ie),[[j,u.value]]),s("div",ce,[s("div",null,[s("span",ue,[q(i(t.templateName)+" ",1),t.isDefault?(l(),n("span",de,"默认")):t.builtIn?(l(),n("span",ve,"内置")):(l(),n("span",pe,"自定义"))]),s("div",_e,"输出文件:"+i(t.outputFilename),1)])])],2))),128)),v.value.length?_("",!0):(l(),n("div",fe,"暂无可用模板"))]),e[7]||(e[7]=s("div",{class:"section-title"},"转换说明",-1)),s("div",me,[s("label",he,[e[3]||(e[3]=s("input",{type:"checkbox",checked:"",disabled:""},null,-1)),s("div",null,[e[2]||(e[2]=s("span",{class:"label"},"按当前模板输出",-1)),s("div",ye,"当前模板:"+i(T.value?.templateName||"未加载模板"),1)])]),e[4]||(e[4]=s("label",{class:"radio-item active"},[s("input",{type:"checkbox",checked:"",disabled:""}),s("div",null,[s("span",{class:"label"},"SKU 使用雪花ID-序号"),s("div",{class:"desc"},"例如 1773816146796-1")])],-1))]),s("div",ge,[s("button",{type:"button",class:"btn-run",disabled:m.value,onClick:$},i(m.value?"转换中...":"开始转换"),9,be),s("span",we,i(m.value?"正在生成并上传结果,请稍候…":"选择文件后即可执行格式转换,结果会显示在右侧供下载"),1)])]),s("section",ke,[e[12]||(e[12]=s("div",{class:"panel-header"},"转换结果",-1)),s("div",Ce,[s("div",xe,[s("div",Fe,[e[8]||(e[8]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,i(h.value.total),1)]),s("div",Ie,[e[9]||(e[9]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,i(h.value.successCount),1)]),s("div",Pe,[e[10]||(e[10]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,i(h.value.failedCount),1)])]),s("div",Ee,[e[11]||(e[11]=s("div",{class:"result-list-header"},[s("span",null,"生成的结果压缩包列表")],-1)),b.value.length===0?(l(),n("div",Te," 暂无转换结果,完成格式转换后会在这里展示结果压缩包 ")):(l(),n("ul",Be,[(l(!0),n(w,null,F(b.value,t=>(l(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",De,[s("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Ne),t.outputFilename?(l(),n("div",Re,"压缩包文件:"+i(t.outputFilename),1)):_("",!0),t.error?(l(),n("div",$e,"错误信息:"+i(t.error),1)):_("",!0)]),s("div",Se,[s("span",{class:P(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&(t.downloadUrl||t.resultId)?(l(),n("button",{key:0,type:"button",class:"download",onClick:o=>U(t)}," 下载压缩包 ",8,Ue)):_("",!0),t.resultId?(l(),n("button",{key:1,type:"button",class:"btn-delete",onClick:o=>S(t.resultId)}," 删除 ",8,Ve)):_("",!0)])]))),128))]))])])])])]))}}),He=W(Ae,[["__scopeId","data-v-0e58908e"]]);X(He).use(Y).mount("#app");
diff --git a/app/assets/dedupe.js b/app/assets/dedupe.js
index 6ba331f..4aebb8b 100644
--- a/app/assets/dedupe.js
+++ b/app/assets/dedupe.js
@@ -1 +1 @@
-import{d as z,o as W,a as n,c as o,b as q,B as J,e as s,F as C,r as F,t as i,f as b,n as I,w as P,v as B,g as u,h as L,i as x,E as r,j as O,k as G,l as Q,m as X,_ as Y,p as Z,q as ee}from"./pywebview-CeWJDVeG.js";import{e as se}from"./brand-UU-ckLq6.js";const te={class:"page-shell module-page"},le={class:"main-content"},ae={class:"left-panel"},ne={class:"upload-zone"},oe={class:"selected-files clean-placeholder"},re={key:0,class:"more-line"},ie={key:1},ue={class:"option-group column-option-group"},ce={class:"column-toolbar"},de={class:"column-count"},ve={key:0,class:"column-grid"},pe=["value"],fe={key:1,class:"empty-column-state"},_e={class:"option-group"},me={class:"run-row"},he=["disabled"],ge={class:"loading-msg"},ye={class:"right-panel"},be={class:"task-list-wrap"},we={class:"clean-result-summary"},ke={class:"summary-card"},Ce={class:"summary-card"},Ie={class:"summary-card"},xe={class:"result-list-wrap"},Ee={key:0,class:"empty-tasks"},De={key:1,class:"task-list clean-result-list"},Fe={class:"left"},Pe=["title"],Be={key:0,class:"files"},Se={key:1,class:"files"},Ue={class:"task-right"},Ne=["onClick"],$e=["onClick"],Ae=z({__name:"BrandDedupeTab",setup(Ke){const p=u([]),c=u([]),f=u([]),w=u(""),d=u([]),S=u(!1),_=u(!0),m=u(!0),h=u(!1),k=u([]),g=u({total:0,successCount:0,failedCount:0,items:[]}),E=L(()=>f.value.slice(0,8));function $(){c.value=[...p.value]}function A(){c.value=[]}async function U(l){const e=x();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const a of l){const v=typeof a=="string"?a:a.absolutePath,j=typeof a=="string"?void 0:a.relativePath,y=await e.upload_file_to_java(v,j);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${v}`);t.push(y.data)}return t}async function N(l){const e=await X(l);if(!e.headers?.length){r.error("读取 Excel 表头失败"),p.value=[],c.value=[];return}const t=["id","ASIN","国家","价格","卖家名称"],a=[...t.filter(v=>e.headers?.includes(v)),...(e.headers||[]).filter(v=>!t.includes(v))];p.value=a,c.value=t.filter(v=>a.includes(v))}async function H(l,e){f.value=l,w.value="",d.value=await U(l),d.value.length>0&&await N(d.value[0].fileKey),r.success(e)}async function K(){const l=x();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await l.select_brand_xlsx_files();if(!e?.length)return;await H(e,`已选择 ${e.length} 个待清洗文件`)}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function R(){const l=x();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await l.select_brand_folder();if(!e)return;w.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await se(e);if(t.success&&t.items?.length){f.value=t.items.map(a=>a.relativePath),d.value=await U(t.items),d.value.length>0&&await N(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function V(){if(!d.value.length){r.warning("请先选择待清洗 Excel 文件或文件夹");return}if(!c.value.length){r.warning("请至少选择一列保留列");return}if(!S.value&&!_.value&&!m.value){r.warning("请至少选择一种 ID 保留规则");return}try{h.value=!0;const l=await O({files:d.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),selectedColumns:c.value,keepIntegerIds:S.value,keepUnderscoreIds:_.value,keepIntegerMainIdsWhenNoSubIds:m.value,archiveName:d.value.some(e=>!!e.relativePath)&&w.value?w.value:void 0});g.value=l,k.value=l.items||[],await D(),r.success("数据去重完成")}catch(l){r.error(l instanceof Error?l.message:"去重失败")}finally{h.value=!1}}async function D(){try{const l=await G();k.value=l.items||[],g.value={total:l.items?.length||0,successCount:l.items?.filter(e=>e.success).length||0,failedCount:l.items?.filter(e=>!e.success).length||0,items:l.items||[]}}catch{}}async function M(l){try{await Q(l),await D(),r.success("已删除")}catch(e){r.error(e instanceof Error?e.message:"删除失败")}}async function T(l){const e=x();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}_cleaned.xlsx`;if(e?.save_file_from_url_new){const a=await e.save_file_from_url_new(l.downloadUrl,t);a.success?r.success(`已保存:${a.path||t}`):a.error&&a.error!=="用户取消"&&r.error(a.error);return}}return W(()=>{D().catch(()=>{})}),(l,e)=>(n(),o("div",te,[q(J,{active:"dedupe"}),s("div",le,[s("aside",ae,[e[7]||(e[7]=s("div",{class:"section-title"},"选择文件",-1)),s("div",ne,[e[3]||(e[3]=s("div",{class:"hint"},"选择需要清洗的 Excel 文件或文件夹,后续将按所选规则输出处理结果。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:K},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:R},"选择文件夹")]),s("div",oe,[f.value.length?(n(),o(C,{key:0},[(n(!0),o(C,null,F(E.value,t=>(n(),o("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(n(),o("span",re," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):b("",!0)],64)):(n(),o("span",ie,"暂未选择待清洗文件"))])]),e[8]||(e[8]=s("div",{class:"section-title"},"保留列设置",-1)),s("div",ue,[s("div",ce,[s("span",de,"已选择 "+i(c.value.length)+" / "+i(p.value.length)+" 列",1),s("div",{class:"column-actions"},[s("button",{type:"button",class:"opt-btn small",onClick:$},"全选"),s("button",{type:"button",class:"opt-btn small",onClick:A},"清空")])]),p.value.length?(n(),o("div",ve,[(n(!0),o(C,null,F(p.value,t=>(n(),o("label",{key:t,class:I(["column-item",{active:c.value.includes(t)}])},[P(s("input",{"onUpdate:modelValue":e[0]||(e[0]=a=>c.value=a),type:"checkbox",value:t},null,8,pe),[[B,c.value]]),s("span",null,i(t),1)],2))),128))])):(n(),o("div",fe,"请选择 Excel 文件后读取表头并填充可保留列")),e[4]||(e[4]=s("div",{class:"desc"},"读取首个 Excel 文件的第一行作为表头,用于动态选择需要保留的列。",-1))]),e[9]||(e[9]=s("div",{class:"section-title"},"ID 保留规则",-1)),s("div",_e,[s("label",{class:I(["radio-item",{active:_.value}])},[P(s("input",{"onUpdate:modelValue":e[1]||(e[1]=t=>_.value=t),type:"checkbox"},null,512),[[B,_.value]]),e[5]||(e[5]=s("div",null,[s("span",{class:"label"},"保留子链接"),s("div",{class:"desc"},"例如 1_1、2_3 这种带下划线的子项 ID")],-1))],2),s("label",{class:I(["radio-item",{active:m.value}])},[P(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>m.value=t),type:"checkbox"},null,512),[[B,m.value]]),e[6]||(e[6]=s("div",null,[s("span",{class:"label"},"仅剩主链接无子链接"),s("div",{class:"desc"},"当某个主链接只有 13 这类主 ID、没有 13_1 这类对应子 ID 时,自动保留该主 ID")],-1))],2)]),s("div",me,[s("button",{type:"button",class:"btn-run",disabled:h.value,onClick:V},i(h.value?"清洗中...":"开始清洗"),9,he),s("span",ge,i(h.value?"正在处理文件并上传结果,请稍候…":"选择文件、列和 ID 规则后即可执行清洗,结果会显示在右侧供下载"),1)])]),s("section",ye,[e[14]||(e[14]=s("div",{class:"panel-header"},"去重结果",-1)),s("div",be,[s("div",we,[s("div",ke,[e[10]||(e[10]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,i(g.value.total),1)]),s("div",Ce,[e[11]||(e[11]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,i(g.value.successCount),1)]),s("div",Ie,[e[12]||(e[12]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,i(g.value.failedCount),1)])]),s("div",xe,[e[13]||(e[13]=s("div",{class:"result-list-header"},[s("span",null,"处理后的 Excel 列表")],-1)),k.value.length===0?(n(),o("div",Ee," 暂无去重结果,完成数据去重后会在这里展示输出文件 ")):(n(),o("ul",De,[(n(!0),o(C,null,F(k.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Fe,[s("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Pe),t.outputFilename?(n(),o("div",Be,"输出文件:"+i(t.outputFilename),1)):b("",!0),t.error?(n(),o("div",Se,"错误信息:"+i(t.error),1)):b("",!0)]),s("div",Ue,[s("span",{class:I(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:a=>T(t)}," 下载文件 ",8,Ne)):b("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:a=>M(t.resultId)}," 删除 ",8,$e)):b("",!0)])]))),128))]))])])])])]))}}),He=Y(Ae,[["__scopeId","data-v-bdaefe1c"]]);Z(He).use(ee).mount("#app");
+import{d as z,o as W,a,c as n,b as q,B as J,e as s,F as C,r as F,t as u,f as b,n as I,w as P,v as B,g as c,h as L,i as x,E as o,j as O,k as G,l as Q,m as X,p as Y,_ as Z,q as ee,s as se}from"./pywebview-C66x_2Dh.js";import{e as te}from"./brand-BZije8D7.js";const le={class:"page-shell module-page"},ae={class:"main-content"},ne={class:"left-panel"},oe={class:"upload-zone"},re={class:"selected-files clean-placeholder"},ie={key:0,class:"more-line"},ue={key:1},ce={class:"option-group column-option-group"},de={class:"column-toolbar"},ve={class:"column-count"},pe={key:0,class:"column-grid"},fe=["value"],_e={key:1,class:"empty-column-state"},me={class:"option-group"},he={class:"run-row"},ge=["disabled"],ye={class:"loading-msg"},be={class:"right-panel"},we={class:"task-list-wrap"},ke={class:"clean-result-summary"},Ce={class:"summary-card"},Ie={class:"summary-card"},xe={class:"summary-card"},De={class:"result-list-wrap"},Ee={key:0,class:"empty-tasks"},Fe={key:1,class:"task-list clean-result-list"},Pe={class:"left"},Be=["title"],Se={key:0,class:"files"},Ue={key:1,class:"files"},Ne={class:"task-right"},$e=["onClick"],Ae=["onClick"],He=z({__name:"BrandDedupeTab",setup(Re){const p=c([]),d=c([]),f=c([]),w=c(""),v=c([]),S=c(!1),_=c(!0),m=c(!0),h=c(!1),k=c([]),g=c({total:0,successCount:0,failedCount:0,items:[]}),D=L(()=>f.value.slice(0,8));function $(){d.value=[...p.value]}function A(){d.value=[]}async function U(l){const e=x();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const r of l){const i=typeof r=="string"?r:r.absolutePath,j=typeof r=="string"?void 0:r.relativePath,y=await e.upload_file_to_java(i,j);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${i}`);t.push(y.data)}return t}async function N(l){const e=await Y(l);if(!e.headers?.length){o.error("读取 Excel 表头失败"),p.value=[],d.value=[];return}const t=["id","ASIN","国家","价格","卖家名称"],r=[...t.filter(i=>e.headers?.includes(i)),...(e.headers||[]).filter(i=>!t.includes(i))];p.value=r,d.value=t.filter(i=>r.includes(i))}async function H(l,e){f.value=l,w.value="",v.value=await U(l),v.value.length>0&&await N(v.value[0].fileKey),o.success(e)}async function K(){const l=x();if(!l?.select_brand_xlsx_files){o.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await l.select_brand_xlsx_files();if(!e?.length)return;await H(e,`已选择 ${e.length} 个待清洗文件`)}catch(e){o.error(e instanceof Error?e.message:"选择失败")}}async function R(){const l=x();if(!l?.select_brand_folder){o.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await l.select_brand_folder();if(!e)return;w.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await te(e);if(t.success&&t.items?.length){f.value=t.items.map(r=>r.relativePath),v.value=await U(t.items),v.value.length>0&&await N(v.value[0].fileKey),o.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}o.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){o.error(e instanceof Error?e.message:"选择失败")}}async function V(){if(!v.value.length){o.warning("请先选择待清洗 Excel 文件或文件夹");return}if(!d.value.length){o.warning("请至少选择一列保留列");return}if(!S.value&&!_.value&&!m.value){o.warning("请至少选择一种 ID 保留规则");return}try{h.value=!0;const l=await O({files:v.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),selectedColumns:d.value,keepIntegerIds:S.value,keepUnderscoreIds:_.value,keepIntegerMainIdsWhenNoSubIds:m.value,archiveName:v.value.some(e=>!!e.relativePath)&&w.value?w.value:void 0});g.value=l,k.value=l.items||[],await E(),o.success("数据去重完成")}catch(l){o.error(l instanceof Error?l.message:"去重失败")}finally{h.value=!1}}async function E(){try{const l=await G();k.value=l.items||[],g.value={total:l.items?.length||0,successCount:l.items?.filter(e=>e.success).length||0,failedCount:l.items?.filter(e=>!e.success).length||0,items:l.items||[]}}catch{}}async function M(l){try{await Q(l),await E(),o.success("已删除")}catch(e){o.error(e instanceof Error?e.message:"删除失败")}}async function T(l){const e=x(),t=l.downloadUrl||(l.resultId?X(l.resultId):"");if(!t){o.warning("当前结果没有下载地址");return}const r=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}_cleaned.xlsx`;if(e?.save_file_from_url_new){const i=await e.save_file_from_url_new(t,r);i.success?o.success(`已保存:${i.path||r}`):i.error&&i.error!=="用户取消"&&o.error(i.error);return}}return W(()=>{E().catch(()=>{})}),(l,e)=>(a(),n("div",le,[q(J,{active:"dedupe"}),s("div",ae,[s("aside",ne,[e[7]||(e[7]=s("div",{class:"section-title"},"选择文件",-1)),s("div",oe,[e[3]||(e[3]=s("div",{class:"hint"},"选择需要清洗的 Excel 文件或文件夹,后续将按所选规则输出处理结果。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:K},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:R},"选择文件夹")]),s("div",re,[f.value.length?(a(),n(C,{key:0},[(a(!0),n(C,null,F(D.value,t=>(a(),n("span",{key:t},u(t),1))),128)),f.value.length>D.value.length?(a(),n("span",ie," 还有 "+u(f.value.length-D.value.length)+" 个文件未展开显示 ",1)):b("",!0)],64)):(a(),n("span",ue,"暂未选择待清洗文件"))])]),e[8]||(e[8]=s("div",{class:"section-title"},"保留列设置",-1)),s("div",ce,[s("div",de,[s("span",ve,"已选择 "+u(d.value.length)+" / "+u(p.value.length)+" 列",1),s("div",{class:"column-actions"},[s("button",{type:"button",class:"opt-btn small",onClick:$},"全选"),s("button",{type:"button",class:"opt-btn small",onClick:A},"清空")])]),p.value.length?(a(),n("div",pe,[(a(!0),n(C,null,F(p.value,t=>(a(),n("label",{key:t,class:I(["column-item",{active:d.value.includes(t)}])},[P(s("input",{"onUpdate:modelValue":e[0]||(e[0]=r=>d.value=r),type:"checkbox",value:t},null,8,fe),[[B,d.value]]),s("span",null,u(t),1)],2))),128))])):(a(),n("div",_e,"请选择 Excel 文件后读取表头并填充可保留列")),e[4]||(e[4]=s("div",{class:"desc"},"读取首个 Excel 文件的第一行作为表头,用于动态选择需要保留的列。",-1))]),e[9]||(e[9]=s("div",{class:"section-title"},"ID 保留规则",-1)),s("div",me,[s("label",{class:I(["radio-item",{active:_.value}])},[P(s("input",{"onUpdate:modelValue":e[1]||(e[1]=t=>_.value=t),type:"checkbox"},null,512),[[B,_.value]]),e[5]||(e[5]=s("div",null,[s("span",{class:"label"},"保留子链接"),s("div",{class:"desc"},"例如 1_1、2_3 这种带下划线的子项 ID")],-1))],2),s("label",{class:I(["radio-item",{active:m.value}])},[P(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>m.value=t),type:"checkbox"},null,512),[[B,m.value]]),e[6]||(e[6]=s("div",null,[s("span",{class:"label"},"仅剩主链接无子链接"),s("div",{class:"desc"},"当某个主链接只有 13 这类主 ID、没有 13_1 这类对应子 ID 时,自动保留该主 ID")],-1))],2)]),s("div",he,[s("button",{type:"button",class:"btn-run",disabled:h.value,onClick:V},u(h.value?"清洗中...":"开始清洗"),9,ge),s("span",ye,u(h.value?"正在处理文件并上传结果,请稍候…":"选择文件、列和 ID 规则后即可执行清洗,结果会显示在右侧供下载"),1)])]),s("section",be,[e[14]||(e[14]=s("div",{class:"panel-header"},"去重结果",-1)),s("div",we,[s("div",ke,[s("div",Ce,[e[10]||(e[10]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,u(g.value.total),1)]),s("div",Ie,[e[11]||(e[11]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,u(g.value.successCount),1)]),s("div",xe,[e[12]||(e[12]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,u(g.value.failedCount),1)])]),s("div",De,[e[13]||(e[13]=s("div",{class:"result-list-header"},[s("span",null,"处理后的 Excel 列表")],-1)),k.value.length===0?(a(),n("div",Ee," 暂无去重结果,完成数据去重后会在这里展示输出文件 ")):(a(),n("ul",Fe,[(a(!0),n(C,null,F(k.value,t=>(a(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Pe,[s("span",{class:"id",title:t.sourceFilename},u(t.sourceFilename||"-"),9,Be),t.outputFilename?(a(),n("div",Se,"输出文件:"+u(t.outputFilename),1)):b("",!0),t.error?(a(),n("div",Ue,"错误信息:"+u(t.error),1)):b("",!0)]),s("div",Ne,[s("span",{class:I(["status",t.success?"success":"failed"])},u(t.success?"已完成":"失败"),3),t.success&&(t.downloadUrl||t.resultId)?(a(),n("button",{key:0,type:"button",class:"download",onClick:r=>T(t)}," 下载文件 ",8,$e)):b("",!0),t.resultId?(a(),n("button",{key:1,type:"button",class:"btn-delete",onClick:r=>M(t.resultId)}," 删除 ",8,Ae)):b("",!0)])]))),128))]))])])])])]))}}),Ke=Z(He,[["__scopeId","data-v-c7e8afd4"]]);ee(Ke).use(se).mount("#app");
diff --git a/app/assets/delete-brand.js b/app/assets/delete-brand.js
index cac4617..d0ceee6 100644
--- a/app/assets/delete-brand.js
+++ b/app/assets/delete-brand.js
@@ -1 +1 @@
-import{d as Ye,o as Ze,I as es,a as d,c as f,b as ss,B as ts,e as l,F as H,r as fe,t as v,f as h,J as ns,K as De,n as Me,g as y,h as J,L as as,i as G,E as p,M as ls,N as rs,O as os,P as us,Q as is,_ as cs,p as ds,q as fs}from"./pywebview-CeWJDVeG.js";import{e as vs}from"./brand-UU-ckLq6.js";const hs={class:"page-shell module-page"},ps={class:"main-content"},_s={class:"left-panel"},ys={class:"upload-zone"},gs={class:"selected-files clean-placeholder split-selected-files"},ms={key:0,class:"more-line"},ks={key:1},ws={class:"run-row"},Is=["disabled"],bs=["disabled"],Ss={class:"loading-msg"},Ts={key:0,class:"queue-debug-card"},Cs={key:0,class:"queue-debug-line"},Es={key:1,class:"queue-debug-payload"},Fs={class:"right-panel"},Ps={class:"task-list-wrap"},Rs={class:"clean-result-summary"},xs={class:"summary-card"},Ns={class:"summary-card"},$s={class:"summary-card"},As={class:"result-list-wrap"},Ds={key:0,class:"empty-tasks"},Ms={key:0,class:"result-subsection"},Bs={class:"task-list clean-result-list"},qs={class:"left split-result-main"},Ls=["title"],Us={class:"files"},Os={class:"files"},Ks={key:0,class:"files"},Hs={key:1,class:"files"},Js={key:2,class:"files"},Gs={key:3,class:"time"},js={key:4,class:"delete-brand-progress-block"},zs={class:"delete-brand-progress-header"},Vs={class:"delete-brand-progress-bar"},Qs={class:"files delete-brand-progress"},Xs={key:5,class:"files split-entry-list delete-brand-preview"},Ws={key:6,class:"files"},Ys={class:"task-right split-result-actions"},Zs=["onClick"],et=["onClick"],st={key:1,class:"result-subsection"},tt={class:"task-list clean-result-list"},nt={class:"left split-result-main"},at=["title"],lt={class:"files"},rt={class:"files"},ot={key:0,class:"files"},ut={key:1,class:"files"},it={key:2,class:"time"},ct={key:3,class:"delete-brand-progress-block"},dt={class:"delete-brand-progress-header"},ft={class:"delete-brand-progress-bar"},vt={class:"files delete-brand-progress"},ht={key:4,class:"files split-entry-list delete-brand-preview"},pt={key:5,class:"files"},_t={class:"task-right split-result-actions"},yt=["onClick"],gt=["onClick"],mt=Ye({__name:"BrandDeleteBrandTab",setup(wt){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const A=y([]),j=y([]),B=y(!1),ve=y([]),i=y([]),q=y([]),z=y({total:0,successCount:0,failedCount:0,items:[]}),b=y(""),S=y(""),g=y({}),T=y(null),ee=y(!1),V=y(!1),E=y(!1),D=y(""),Q=y(!1),L=y(null),se=y(!1),te=J(()=>A.value.slice(0,10)),F=J(()=>{const e=[];return i.value.forEach(s=>{e.push(...s.items)}),e});function Be(){try{const e=typeof window<"u"?window.localStorage.getItem($()):null;if(e){if(i.value=JSON.parse(e),i.value.length>0){const s=i.value[i.value.length-1];b.value=s.queuePushResult||"",S.value=s.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function ne(e,s,t,n){if(!s?.length)return;const a=i.value.findIndex(c=>c.taskId===e),o=a>=0?i.value[a]:null,r=s.map(c=>{const _=o?.items.find(We=>M(We,c)),k=Array.isArray(c.countries)?c.countries:void 0,m=Array.isArray(_?.countries)?_?.countries:void 0,w=k&&k.length>0?k:m&&m.length>0?m:k??m,I=Array.isArray(c.previewRows)?c.previewRows:void 0,C=Array.isArray(_?.previewRows)?_?.previewRows:void 0,de=I&&I.length>0?I:C&&C.length>0?C:I??C;return{...c,countries:w,previewRows:de,_pushed:_?._pushed??c._pushed??!1,_completed:_?._completed??c._completed??!1}}),u={taskId:e,items:r,createdAt:o?.createdAt||Date.now(),queuePushResult:t??o?.queuePushResult,queuePayloadText:n??o?.queuePayloadText};a>=0?i.value[a]=u:i.value.push(u),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function he(e){const s=i.value.length;i.value=i.value.filter(t=>t.taskId!==e),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function pe(e){return e==="SUCCESS"||e==="FAILED"||e==="COMPLETED"}function qe(e){if(!e?.length)return!1;const s=new Set;if(e.forEach(n=>{n.taskId&&pe(n.taskStatus)&&s.add(n.taskId)}),!s.size)return!1;const t=i.value.length;return i.value=i.value.filter(n=>!s.has(n.taskId)),i.value.length!==t?(ae(),!0):!1}const X=J(()=>q.value.filter(e=>!F.value.some(s=>M(s,e)))),Le=J(()=>F.value.length>0||X.value.length>0),Ue=J(()=>F.value.filter(e=>xe(e)&&P(e)==="未入队"));function ae(){typeof window<"u"&&(i.value.length===0?window.localStorage.removeItem($()):window.localStorage.setItem($(),JSON.stringify(i.value)))}function R(e){return e?(i.value.find(t=>t.taskId===e)?.items.length||0)>1:!1}function le(e){return e&&i.value.find(s=>s.taskId===e)||null}function M(e,s){return!e||!s?!1:e.resultId!=null&&s.resultId!=null?e.resultId===s.resultId:e.fileKey&&s.fileKey?e.fileKey===s.fileKey:e.sourceFilename===s.sourceFilename}function x(e,s){const t=le(e);return t&&t.items.find(n=>M(n,s))||null}function _e(e){if(!e)return!1;const s=le(e);if(!s)return!1;let t=!1;const n=g.value[e]?.line_progress?.info,a=R(e);if(n?.file_name&&n.current_line!=null&&n.total_lines!=null&&n.total_lines>0&&n.current_line>=n.total_lines){const o=s.items.find(r=>r.sourceFilename===n.file_name);o&&!o._completed&&(o._completed=!0,t=!0)}if(!a){const o=g.value[e]?.task?.status;if((o==="SUCCESS"||o==="FAILED")&&s.items.length===1){const r=s.items[0];r&&!r._completed&&(r._completed=!0,t=!0)}}return t&&ae(),t}function ye(e){return e&&g.value[e]?.task?.status||""}function W(e){if(e.error)return e.error;if(e.matchStatus&&e.matchStatus!=="MATCHED")return e.matchMessage||"";const s=e.taskId;return s&&g.value[s]?.task?.errorMessage||""}function ge(e){const s=e.taskId;if(!s)return!1;const t=g.value[s]?.line_progress,n=ye(s),a=R(s),o=P(e);if(a){const r=x(s,e),u=K(e)===D.value,c=!!(t?.has_progress&&t?.info?.file_name===e.sourceFilename);return u||c||r?._pushed||(n==="RUNNING"||n==="PENDING"||!n)&&r?._completed?!0:{success:!1,retryable:!0}}return o==="本文件解析完毕"||o==="已被全局判定为终态"?!1:t?.has_progress||t?.info?!0:o==="已入队"||o==="处理中"}function me(e){return e.map(s=>s.matchStatus==="MATCHED"?{...s,_pushed:!1,_completed:!1}:{...s,matched:!1,openStoreUrl:void 0,error:s.error||void 0,_pushed:!1,_completed:!1})}function ke(e){return e.matchStatus==="MATCHED"?`已匹配 ${e.shopId||""}`.trim():e.matchMessage?e.matchMessage:e.matchStatus==="CONFLICT"?"存在多个同名店铺,请人工确认":e.matchStatus==="PENDING"||e.matchStatus==="INDEX_STALE"?"店铺索引暂未就绪":e.matchStatus?e.matchStatus:e.matched?`已匹配 ${e.shopId||""}`.trim():"待匹配"}function Oe(){ve.value=[...F.value,...X.value]}function N(){Oe(),Ke(ve.value)}function Ke(e){z.value={total:e.length,successCount:e.filter(s=>s.success).length,failedCount:e.filter(s=>!s.success).length,items:e}}function re(e){const s=e.taskId;return s&&(g.value[s]?.fileProgress||[]).find(a=>a.fileKey&&e.fileKey?a.fileKey===e.fileKey:a.sourceFilename&&e.sourceFilename?a.sourceFilename===e.sourceFilename:!1)||null}function we(e){const s=e.taskId;if(!s)return"";const t=g.value[s],n=t?.line_progress?.info,a=re(e);if(R(s)){const u=x(s,e),c=t?.task?.status,_=!!(n?.file_name&&n.file_name===e.sourceFilename);let k=u?._completed?"COMPLETED":(u?._pushed,"PENDING"),m=0,w=Math.max(0,e.totalRows??a?.totalRows??0);c==="SUCCESS"||c==="FAILED"&&u?._completed?(k="COMPLETED",m=w):_?(k="RUNNING",m=Math.max(0,Math.min(w||Number.MAX_SAFE_INTEGER,n?.current_line??0)),!w&&n?.total_lines&&n.total_lines>0&&(w=n.total_lines)):u?._completed?(k="COMPLETED",m=w):u?._pushed&&(k="PENDING",m=0);const I=[];if(I.push(`文件状态:${k}`),I.push(`文件进度:${m}/${w}`),n?.finished_files!=null){const C=n.file_total&&n.file_total>0?`/${n.file_total}`:"";I.push(`已完成文件:${n.finished_files}${C}`)}return n?.phase&&I.push(`阶段:${n.phase}`),I.join("|")}if(a){const u=[],c=a.processedRows??0,_=a.totalRows??0;if(a.status&&u.push(`文件状态:${a.status}`),u.push(`文件进度:${c}/${_}`),n?.finished_files!=null){const k=n.file_total&&n.file_total>0?`/${n.file_total}`:"";u.push(`已完成文件:${n.finished_files}${k}`)}return n?.phase&&u.push(`阶段:${n.phase}`),u.join("|")}if(!t?.line_progress?.has_progress||!n){const u=t?.task?.status;return u?`任务状态:${u}`:""}const r=[];if(n.phase&&r.push(`阶段:${n.phase}`),n.file_index&&n.file_total&&r.push(`文件:${n.file_index}/${n.file_total}`),n.file_name&&r.push(`当前文件:${n.file_name}`),n.current_line!=null&&n.total_lines!=null&&r.push(`${R(s)?"当前文件进度":"进度"}:${n.current_line}/${n.total_lines}`),n.current_country&&r.push(`国家:${n.current_country}`),n.current_asin&&r.push(`ASIN:${n.current_asin}`),n.finished_files!=null){const u=n.file_total&&n.file_total>0?`/${n.file_total}`:"";r.push(`已完成文件:${n.finished_files}${u}`)}return r.join("|")}function Y(e){const s=e.taskId;if(!s)return 0;const t=g.value[s]?.line_progress?.info;if(R(s)){const r=x(s,e);if(g.value[s]?.task?.status==="SUCCESS"||r?._completed)return 100;const c=Math.max(0,e.totalRows??re(e)?.totalRows??0);if(!!(t?.file_name&&t.file_name===e.sourceFilename)){if(t?.current_line!=null&&t?.total_lines&&t.total_lines>0)return Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100)));if(t?.current_line!=null&&c>0)return Math.max(0,Math.min(100,Math.round(t.current_line/c*100)))}return 0}const a=re(e);return a?Math.max(0,Math.min(100,a.percent??0)):t?t.current_line!=null&&t.total_lines&&t.total_lines>0?Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100))):t.finished_files!=null&&t.file_total&&t.file_total>0?Math.max(0,Math.min(100,Math.round(t.finished_files/t.file_total*100))):g.value[s]?.task?.status==="SUCCESS"?100:0:0}function Ie(e){const s=g.value[e]?.task?.status;return pe(s)}function U(){const e=new Set;return i.value.forEach(s=>{s.items.some(t=>t._pushed)&&e.add(s.taskId)}),Array.from(e)}function P(e){const s=e.taskId;if(!s||!le(s))return"";const n=x(s,e),a=ye(s),o=g.value[s]?.line_progress,r=R(s);if(!r&&(a==="SUCCESS"||a==="FAILED"||e.taskStatus==="SUCCESS"||e.taskStatus==="FAILED"))return"已被全局判定为终态";if(n?._completed)return"本文件解析完毕";if(a==="RUNNING"&&o?.has_progress&&o?.info?.file_name===e.sourceFilename)return"处理中";if(r&&!n?._pushed&&!n?._completed)return"未入队";if(r&&a==="SUCCESS")return"本文件解析完毕";if(!r){const u=g.value[s];if(u?.items&&u.items.find(_=>M(_,e))&&a==="SUCCESS")return"本文件解析完毕"}return n?._pushed?"已入队":"未入队"}function be(e){if(e.downloadUrl)return!0;const s=e.taskId;return s?g.value[s]?.task?.status==="SUCCESS":{success:!1,retryable:!1}}function He(e){const s=e?.task?.id;if(!s)return!1;const t=i.value.find(r=>r.taskId===s);if(!t)return{success:!1,retryable:!1};const n=e?.items||[];let a=!1;const o=t.items.map(r=>{const u=n.find(de=>M(de,r));if(!u)return r;const c=Array.isArray(u.countries)?u.countries:void 0,_=Array.isArray(r.countries)?r.countries:void 0,k=c&&c.length>0?c:_&&_.length>0?_:c??_,m=Array.isArray(u.previewRows)?u.previewRows:void 0,w=Array.isArray(r.previewRows)?r.previewRows:void 0,I=m&&m.length>0?m:w&&w.length>0?w:m??w,C={...r,...u,countries:k,previewRows:I,_pushed:r._pushed,_completed:r._completed};return JSON.stringify(C)!==JSON.stringify(r)&&(a=!0),C});for(const r of n)o.some(u=>M(u,r))||(o.push({...r,_pushed:!1,_completed:!1}),a=!0);return JSON.stringify(o)!==JSON.stringify(t.items)&&(t.items=o,a=!0),a&&ae(),a}async function oe(e){const s=e||U();if(s.length)try{const t=await as(s);se.value&&E.value&&(b.value="后端已恢复,继续执行当前任务并自动衔接后续任务..."),se.value=!1;let n=!1;for(const a of t.items||[]){const o=a?.task?.id;typeof o=="number"&&o>0&&(g.value[o]=a,He(a)&&(n=!0),_e(o)&&(n=!0))}n&&N()}catch(t){const n=(t instanceof Error?t.message:String(t||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(o=>n.includes(o.toLowerCase()))&&E.value&&(se.value=!0,b.value="当前任务运行中,正在等待后端服务恢复...")}}function Je(){return is()}function ue(e=!1){if(T.value){if(!e)return;clearTimeout(T.value),T.value=null}const s=async()=>{if(T.value=null,ee.value){ue();return}const t=U();if(!t.length)return;ee.value=!0;let n=!1,a=!1;try{await oe(t),await Ae();for(const o of t)Ie(o)&&(he(o),n=!0,a=!0)}finally{ee.value=!1}a?await O():n&&N(),U().length>0&&ue()};e?s():T.value=window.setTimeout(s,Je())}function ie(e=!1){T.value&&!e||ue(e)}function Z(e){const s=e.taskId?g.value[e.taskId]?.task?.status:null;return s==="SUCCESS"?{className:"success",text:"已完成"}:s==="FAILED"?{className:"failed",text:"失败"}:s==="RUNNING"?{className:"running",text:"执行中"}:e.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:e.taskStatus==="FAILED"?{className:"failed",text:"失败"}:e.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:e.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Se(){T.value&&(window.clearTimeout(T.value),T.value=null),ce()}async function Te(e){const s=G();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const n of e){const a=typeof n=="string"?n:n.absolutePath,o=typeof n=="string"?void 0:n.relativePath,r=await s.upload_file_to_java(a,o);if(!r?.success||!r.data)throw new Error(r?.error||r?.message||`上传失败:${a}`);t.push(r.data)}return t}async function Ge(){const e=G();if(!e?.select_brand_xlsx_files){p.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await e.select_brand_xlsx_files();if(!s?.length)return;A.value=s,j.value=await Te(s),p.success(`已选择 ${s.length} 个删除品牌文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function je(){const e=G();if(!e?.select_brand_folder){p.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await e.select_brand_folder();if(!s)return;const t=await vs(s);if(!t.success||!t.items?.length){p.warning(t.error||"该文件夹下没有 xlsx 文件");return}A.value=t.items.map(n=>n.relativePath||n.absolutePath),j.value=await Te(t.items),p.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function ze(){if(!j.value.length){p.warning("请先选择待处理文件");return}try{B.value=!0;const e=await ls({files:j.value.map(a=>({fileKey:a.fileKey,originalFilename:a.originalFilename,relativePath:a.relativePath}))}),s=me(e.items||[]),t=s.some(a=>a.matchStatus==="MATCHED"),n=s.some(a=>a.matchStatus&&a.matchStatus!=="MATCHED");b.value=t?"解析完成,可在左侧点击“推送到 Python 队列”开始串行处理":"解析完成,当前没有可推送的已匹配文件",S.value="",s.length&&s[0].taskId&&ne(s[0].taskId,s,b.value,S.value),N(),n?p.warning("部分文件尚未命中可用店铺索引,已保留状态信息,请等待后台刷新后重试。"):t?p.success("删除品牌解析完成,请在左侧推送到 Python 队列"):p.warning("当前没有可推送的已匹配文件,请先等待店铺索引刷新。"),await O()}catch(e){p.error(e instanceof Error?e.message:"执行失败")}finally{B.value=!1}}async function Ce(e){const s=e.taskId;if(!s){p.warning("未找到任务ID");return}const t=G(),n=rs(s),a=e.outputFilename||e.sourceFilename||`delete-brand_${s}.xlsx`;if(t?.save_file_from_url_new){const o=await t.save_file_from_url_new(n,a);o.success?p.success(`已保存:${o.path||a}`):o.error&&o.error!=="用户取消"&&p.error(o.error);return}}async function O(){try{const e=await os();q.value=me(e.items||[]),qe(q.value),N(),await oe(),ie(!0)}catch(e){const s=(e instanceof Error?e.message:String(e||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(n=>s.includes(n.toLowerCase()))||console.error("loadHistory error:",e)}}function Ee(e){i.value.forEach(t=>{t.items=t.items.filter(n=>n.resultId!==e)});const s=i.value.length;i.value=i.value.filter(t=>t.items.length>0),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),q.value=q.value.filter(t=>t.resultId!==e),N(),U().length===0&&Se()}async function Fe(e){try{await us(e),Ee(e),await O(),p.success("已删除")}catch(s){const t=s instanceof Error?s.message:"删除失败";if(typeof t=="string"&&t.includes("记录不存在")){Ee(e),p.success("已删除");return}p.error(t)}}function Pe(e){const s=e.slice(0,5).map(n=>`${n.country}:${n.asin}${n.status?`(${n.status})`:""}`);return e.length-s.length>0?`${s.join("、")} 等 ${e.length} 条`:s.join("、")}function It(e,s){}function ce(){L.value&&(window.clearTimeout(L.value),L.value=null)}function Ve(e=3e3){ce(),L.value=window.setTimeout(()=>{L.value=null,Ae().catch(()=>{})},e)}function Qe(e){return e.includes("当前店铺正在执行中")}function Re(){for(const e of i.value)for(const s of e.items){const t=P(s);if(t==="已入队"||t==="处理中")return!0}return!1}function K(e){return e.resultId!=null?`result:${e.resultId}`:`task:${e.taskId||0}:${e.sourceFilename||""}`}function xe(e){return!!(e.taskId&&(e.matchStatus==="MATCHED"||e.matched))}function Ne(){return F.value.find(e=>!xe(e)||K(e)===D.value||x(e.taskId,e)?._completed?!1:P(e)==="未入队")}async function $e(e,s){if(s?.auto&&Re())return e.taskId,e.sourceFilename,{success:!1,retryable:!0};const t=G(),n=e.taskId;if(!n)return{success:!1,retryable:!1};const a=i.value.find(c=>c.taskId===n);if(!a)return{success:!1,retryable:!1};if(!t?.enqueue_json)return p.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)"),{success:!1,retryable:!1};const o={type:"delete-brand-run",ts:Date.now(),data:{taskId:n,items:[e]}};S.value=JSON.stringify(o,null,2);const r=await t.enqueue_json(o);let u="";if(r?.success){u=`已推送文件 ${e.sourceFilename},当前队列长度:${r.queue_size??"-"}`;const c=x(n,e);return c&&(c._pushed=!0,c._completed=!1),D.value=K(e),ne(n,a.items,u,S.value),ie(!0),b.value=u,{success:!0,retryable:!1}}else{const c=r?.error||"未知错误";if(u=`推送到 Python 队列失败:${c}`,b.value=u,ne(n,a.items,u,S.value),s?.auto&&Qe(c))return{success:!1,retryable:!0}}return b.value=u,{success:!1,retryable:!1}}async function Xe(){const e=Ne();if(!e){p.warning("当前没有可推送的已匹配文件");return}V.value=!0,E.value=!0,ce();const s=await $e(e);if(V.value=!1,!s.success){E.value=!1,D.value="";return}p.success("已开始按顺序推送到 Python 队列")}async function Ae(){if(!E.value||Q.value){E.value,Q.value;return}if(Re())return;const e=D.value;if(e){const t=F.value.find(n=>K(n)===e);if(t){const n=t.taskId,a=R(n);a&&(_e(n),P(t),void 0);const o=P(t);if(o==="已入队"||o==="处理中"){t.sourceFilename;return}if(a){const r=x(n,t);if(r&&!r._completed){t.sourceFilename,r._pushed,r._completed;return}}}}const s=Ne();if(!s){D.value="",E.value=!1;return}Q.value=!0;try{s.taskId,s.sourceFilename,K(s);const t=await $e(s,{auto:!0});if(!t.success){if(t.retryable){s.taskId,s.sourceFilename,Ve();return}s.taskId,s.sourceFilename,E.value=!1}}finally{Q.value=!1}}return Ze(()=>{Be();const e=U();e.length>0?oe(e).then(async()=>{let s=!1,t=!1;for(const n of e)Ie(n)&&(he(n),s=!0,t=!0);t?await O():s&&N(),ie(!0)}):N(),O().catch(()=>{})}),es(()=>{Se()}),(e,s)=>(d(),f("div",hs,[ss(ts,{active:"delete-brand"}),l("div",ps,[l("aside",_s,[s[2]||(s[2]=l("div",{class:"section-title"},"选择文件",-1)),l("div",ys,[s[0]||(s[0]=l("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),l("div",{class:"btns"},[l("button",{type:"button",class:"opt-btn",onClick:Ge},"选择 Excel 文件"),l("button",{type:"button",class:"opt-btn",onClick:je},"选择文件夹")]),l("div",gs,[A.value.length?(d(),f(H,{key:0},[(d(!0),f(H,null,fe(te.value,t=>(d(),f("span",{key:t},v(t),1))),128)),A.value.length>te.value.length?(d(),f("span",ms," 还有 "+v(A.value.length-te.value.length)+" 个文件未展开显示 ",1)):h("",!0)],64)):(d(),f("span",ks,"暂未选择删除品牌文件"))])]),s[3]||(s[3]=ns('
执行说明
',2)),l("div",ws,[l("button",{type:"button",class:"btn-run",disabled:B.value,onClick:ze},v(B.value?"解析中...":"开始解析"),9,Is),l("button",{type:"button",class:"btn-run btn-queue",disabled:V.value||!Ue.value.length,onClick:Xe},v(V.value?"推送中...":"推送到 Python 队列"),9,bs),l("span",Ss,v(B.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),b.value||S.value?(d(),f("div",Ts,[s[1]||(s[1]=l("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),b.value?(d(),f("div",Cs,v(b.value),1)):h("",!0),S.value?(d(),f("pre",Es,v(S.value),1)):h("",!0)])):h("",!0)]),l("section",Fs,[s[12]||(s[12]=l("div",{class:"panel-header"},"删除品牌结果",-1)),l("div",Ps,[l("div",Rs,[l("div",xs,[s[4]||(s[4]=l("span",{class:"summary-label"},"已处理文件",-1)),l("strong",null,v(z.value.total),1)]),l("div",Ns,[s[5]||(s[5]=l("span",{class:"summary-label"},"成功结果",-1)),l("strong",null,v(z.value.successCount),1)]),l("div",$s,[s[6]||(s[6]=l("span",{class:"summary-label"},"失败文件",-1)),l("strong",null,v(z.value.failedCount),1)])]),l("div",As,[s[11]||(s[11]=l("div",{class:"result-list-header"},[l("span",null,"删除品牌结果列表")],-1)),Le.value?(d(),f(H,{key:1},[F.value.length?(d(),f("div",Ms,[s[8]||(s[8]=l("div",{class:"result-subsection-title"},"当前任务",-1)),l("ul",Bs,[(d(!0),f(H,null,fe(F.value,t=>(d(),f("li",{key:`current-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",qs,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,Ls),l("div",Us,"店铺名:"+v(t.shopName||"-"),1),l("div",Os,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",Ks,"平台:"+v(t.platform),1)):h("",!0),P(t)?(d(),f("div",Hs,"队列状态:"+v(P(t)),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",Js,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",Gs,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",js,[l("div",zs,[s[7]||(s[7]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",Vs,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",Qs,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",Xs,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",Ws,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",Ys,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,Zs)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,et)):h("",!0)])]))),128))])])):h("",!0),X.value.length?(d(),f("div",st,[s[10]||(s[10]=l("div",{class:"result-subsection-title"},"历史记录",-1)),l("ul",tt,[(d(!0),f(H,null,fe(X.value,t=>(d(),f("li",{key:`history-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",nt,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,at),l("div",lt,"店铺名:"+v(t.shopName||"-"),1),l("div",rt,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",ot,"平台:"+v(t.platform),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",ut,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",it,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",ct,[l("div",dt,[s[9]||(s[9]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",ft,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",vt,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",ht,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",pt,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",_t,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,yt)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,gt)):h("",!0)])]))),128))])])):h("",!0)],64)):(d(),f("div",Ds," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),kt=cs(mt,[["__scopeId","data-v-64ee024e"]]);ds(kt).use(fs).mount("#app");
+import{d as Ye,o as Ze,L as es,a as d,c as f,b as ss,B as ts,e as r,F as H,r as fe,t as v,f as h,M as as,N as De,n as Me,g as y,h as G,O as ns,i as J,E as p,P as ls,Q as rs,R as os,S as us,T as is,_ as cs,q as ds,s as fs}from"./pywebview-C66x_2Dh.js";import{e as vs}from"./brand-BZije8D7.js";const hs={class:"page-shell module-page"},ps={class:"main-content"},_s={class:"left-panel"},ys={class:"upload-zone"},gs={class:"selected-files clean-placeholder split-selected-files"},ms={key:0,class:"more-line"},ks={key:1},ws={class:"run-row"},Is=["disabled"],bs=["disabled"],Ss={class:"loading-msg"},Ts={key:0,class:"queue-debug-card"},Es={key:0,class:"queue-debug-line"},Cs={key:1,class:"queue-debug-payload"},Fs={class:"right-panel"},Rs={class:"task-list-wrap"},Ns={class:"clean-result-summary"},Ps={class:"summary-card"},xs={class:"summary-card"},$s={class:"summary-card"},As={class:"result-list-wrap"},Ds={key:0,class:"empty-tasks"},Ms={key:0,class:"result-subsection"},Ls={class:"task-list clean-result-list"},Bs={class:"left split-result-main"},qs=["title"],Us={class:"files"},Os={class:"files"},Ks={key:0,class:"files"},Hs={key:1,class:"files"},Gs={key:2,class:"files"},Js={key:3,class:"time"},js={key:4,class:"delete-brand-progress-block"},zs={class:"delete-brand-progress-header"},Vs={class:"delete-brand-progress-bar"},Xs={class:"files delete-brand-progress"},Qs={key:5,class:"files split-entry-list delete-brand-preview"},Ws={key:6,class:"files"},Ys={class:"task-right split-result-actions"},Zs=["onClick"],et=["onClick"],st={key:1,class:"result-subsection"},tt={class:"task-list clean-result-list"},at={class:"left split-result-main"},nt=["title"],lt={class:"files"},rt={class:"files"},ot={key:0,class:"files"},ut={key:1,class:"files"},it={key:2,class:"time"},ct={key:3,class:"delete-brand-progress-block"},dt={class:"delete-brand-progress-header"},ft={class:"delete-brand-progress-bar"},vt={class:"files delete-brand-progress"},ht={key:4,class:"files split-entry-list delete-brand-preview"},pt={key:5,class:"files"},_t={class:"task-right split-result-actions"},yt=["onClick"],gt=["onClick"],mt=Ye({__name:"BrandDeleteBrandTab",setup(wt){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const A=y([]),j=y([]),L=y(!1),ve=y([]),i=y([]),B=y([]),z=y({total:0,successCount:0,failedCount:0,items:[]}),b=y(""),S=y(""),m=y({}),T=y(null),ee=y(!1),V=y(!1),C=y(!1),D=y(""),X=y(!1),q=y(null),se=y(!1),te=G(()=>A.value.slice(0,10)),F=G(()=>{const e=[];return i.value.forEach(s=>{e.push(...s.items)}),e});function Le(){try{const e=typeof window<"u"?window.localStorage.getItem($()):null;if(e){if(i.value=JSON.parse(e),i.value.length>0){const s=i.value[i.value.length-1];b.value=s.queuePushResult||"",S.value=s.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function ae(e,s,t,a){if(!s?.length)return;const l=i.value.findIndex(c=>c.taskId===e),n=l>=0?i.value[l]:null,o=s.map(c=>{const _=n?.items.find(We=>M(We,c)),k=Array.isArray(c.countries)?c.countries:void 0,g=Array.isArray(_?.countries)?_?.countries:void 0,w=k&&k.length>0?k:g&&g.length>0?g:k??g,I=Array.isArray(c.previewRows)?c.previewRows:void 0,E=Array.isArray(_?.previewRows)?_?.previewRows:void 0,de=I&&I.length>0?I:E&&E.length>0?E:I??E;return{...c,countries:w,previewRows:de,_pushed:_?._pushed??c._pushed??!1,_completed:_?._completed??c._completed??!1}}),u={taskId:e,items:o,createdAt:n?.createdAt||Date.now(),queuePushResult:t??n?.queuePushResult,queuePayloadText:a??n?.queuePayloadText};l>=0?i.value[l]=u:i.value.push(u),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function he(e){const s=i.value.length;i.value=i.value.filter(t=>t.taskId!==e),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function pe(e){return e==="SUCCESS"||e==="FAILED"||e==="COMPLETED"}function Be(e){if(!e?.length)return!1;const s=new Set;if(e.forEach(a=>{a.taskId&&pe(a.taskStatus)&&s.add(a.taskId)}),!s.size)return!1;const t=i.value.length;return i.value=i.value.filter(a=>!s.has(a.taskId)),i.value.length!==t?(ne(),!0):!1}const Q=G(()=>B.value.filter(e=>!F.value.some(s=>M(s,e)))),qe=G(()=>F.value.length>0||Q.value.length>0),Ue=G(()=>F.value.filter(e=>Pe(e)&&R(e)==="未入队"));function ne(){typeof window<"u"&&(i.value.length===0?window.localStorage.removeItem($()):window.localStorage.setItem($(),JSON.stringify(i.value)))}function N(e){return e?(i.value.find(t=>t.taskId===e)?.items.length||0)>1:!1}function le(e){return e&&i.value.find(s=>s.taskId===e)||null}function M(e,s){return!e||!s?!1:e.resultId!=null&&s.resultId!=null?e.resultId===s.resultId:e.fileKey&&s.fileKey?e.fileKey===s.fileKey:e.sourceFilename===s.sourceFilename}function P(e,s){const t=le(e);return t&&t.items.find(a=>M(a,s))||null}function _e(e){if(!e)return!1;const s=le(e);if(!s)return!1;let t=!1;const a=m.value[e]?.line_progress?.info,l=N(e);if(a?.file_name&&a.current_line!=null&&a.total_lines!=null&&a.total_lines>0&&a.current_line>=a.total_lines){const n=s.items.find(o=>o.sourceFilename===a.file_name);n&&!n._completed&&(n._completed=!0,t=!0)}if(!l){const n=m.value[e]?.task?.status;if((n==="SUCCESS"||n==="FAILED")&&s.items.length===1){const o=s.items[0];o&&!o._completed&&(o._completed=!0,t=!0)}}return t&&ne(),t}function ye(e){return e&&m.value[e]?.task?.status||""}function W(e){if(e.error)return e.error;if(e.matchStatus&&e.matchStatus!=="MATCHED")return e.matchMessage||"";const s=e.taskId;return s&&m.value[s]?.task?.errorMessage||""}function ge(e){const s=e.taskId;if(!s)return!1;const t=m.value[s]?.line_progress,a=ye(s),l=N(s),n=R(e);if(l){const o=P(s,e),u=K(e)===D.value,c=!!(t?.has_progress&&t?.info?.file_name===e.sourceFilename);return u||c||o?._pushed||(a==="RUNNING"||a==="PENDING"||!a)&&o?._completed?!0:{success:!1,retryable:!0}}return n==="本文件解析完毕"||n==="已被全局判定为终态"?!1:t?.has_progress||t?.info?!0:n==="已入队"||n==="处理中"}function me(e){return e.map(s=>s.matched||s.matchStatus==="MATCHED"||s.matchStatus==="INDEX_STALE"?{...s,_pushed:!1,_completed:!1}:{...s,matched:!1,openStoreUrl:void 0,error:s.error||void 0,_pushed:!1,_completed:!1})}function ke(e){return e.matched&&e.matchStatus==="INDEX_STALE"?e.matchMessage||`已匹配 ${e.shopId||""}(索引过期)`.trim():e.matchStatus==="MATCHED"?`已匹配 ${e.shopId||""}`.trim():e.matchMessage?e.matchMessage:e.matchStatus==="CONFLICT"?"存在多个同名店铺,请人工确认":e.matchStatus==="PENDING"?"店铺索引暂未就绪":e.matchStatus?e.matchStatus:e.matched?`已匹配 ${e.shopId||""}`.trim():"待匹配"}function Oe(){ve.value=[...F.value,...Q.value]}function x(){Oe(),Ke(ve.value)}function Ke(e){z.value={total:e.length,successCount:e.filter(s=>s.success).length,failedCount:e.filter(s=>!s.success).length,items:e}}function re(e){const s=e.taskId;return s&&(m.value[s]?.fileProgress||[]).find(l=>l.fileKey&&e.fileKey?l.fileKey===e.fileKey:l.sourceFilename&&e.sourceFilename?l.sourceFilename===e.sourceFilename:!1)||null}function we(e){const s=e.taskId;if(!s)return"";const t=m.value[s],a=t?.line_progress?.info,l=re(e);if(N(s)){const u=P(s,e),c=t?.task?.status,_=!!(a?.file_name&&a.file_name===e.sourceFilename);let k=u?._completed?"COMPLETED":(u?._pushed,"PENDING"),g=0,w=Math.max(0,e.totalRows??l?.totalRows??0);c==="SUCCESS"||c==="FAILED"&&u?._completed?(k="COMPLETED",g=w):_?(k="RUNNING",g=Math.max(0,Math.min(w||Number.MAX_SAFE_INTEGER,a?.current_line??0)),!w&&a?.total_lines&&a.total_lines>0&&(w=a.total_lines)):u?._completed?(k="COMPLETED",g=w):u?._pushed&&(k="PENDING",g=0);const I=[];if(I.push(`文件状态:${k}`),I.push(`文件进度:${g}/${w}`),a?.finished_files!=null){const E=a.file_total&&a.file_total>0?`/${a.file_total}`:"";I.push(`已完成文件:${a.finished_files}${E}`)}return a?.phase&&I.push(`阶段:${a.phase}`),I.join("|")}if(l){const u=[],c=l.processedRows??0,_=l.totalRows??0;if(l.status&&u.push(`文件状态:${l.status}`),u.push(`文件进度:${c}/${_}`),a?.finished_files!=null){const k=a.file_total&&a.file_total>0?`/${a.file_total}`:"";u.push(`已完成文件:${a.finished_files}${k}`)}return a?.phase&&u.push(`阶段:${a.phase}`),u.join("|")}if(!t?.line_progress?.has_progress||!a){const u=t?.task?.status;return u?`任务状态:${u}`:""}const o=[];if(a.phase&&o.push(`阶段:${a.phase}`),a.file_index&&a.file_total&&o.push(`文件:${a.file_index}/${a.file_total}`),a.file_name&&o.push(`当前文件:${a.file_name}`),a.current_line!=null&&a.total_lines!=null&&o.push(`${N(s)?"当前文件进度":"进度"}:${a.current_line}/${a.total_lines}`),a.current_country&&o.push(`国家:${a.current_country}`),a.current_asin&&o.push(`ASIN:${a.current_asin}`),a.finished_files!=null){const u=a.file_total&&a.file_total>0?`/${a.file_total}`:"";o.push(`已完成文件:${a.finished_files}${u}`)}return o.join("|")}function Y(e){const s=e.taskId;if(!s)return 0;const t=m.value[s]?.line_progress?.info;if(N(s)){const o=P(s,e);if(m.value[s]?.task?.status==="SUCCESS"||o?._completed)return 100;const c=Math.max(0,e.totalRows??re(e)?.totalRows??0);if(!!(t?.file_name&&t.file_name===e.sourceFilename)){if(t?.current_line!=null&&t?.total_lines&&t.total_lines>0)return Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100)));if(t?.current_line!=null&&c>0)return Math.max(0,Math.min(100,Math.round(t.current_line/c*100)))}return 0}const l=re(e);return l?Math.max(0,Math.min(100,l.percent??0)):t?t.current_line!=null&&t.total_lines&&t.total_lines>0?Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100))):t.finished_files!=null&&t.file_total&&t.file_total>0?Math.max(0,Math.min(100,Math.round(t.finished_files/t.file_total*100))):m.value[s]?.task?.status==="SUCCESS"?100:0:0}function Ie(e){const s=m.value[e]?.task?.status;return pe(s)}function U(){const e=new Set;return i.value.forEach(s=>{s.items.some(t=>t._pushed)&&e.add(s.taskId)}),Array.from(e)}function R(e){const s=e.taskId;if(!s||!le(s))return"";const a=P(s,e),l=ye(s),n=m.value[s]?.line_progress,o=N(s);if(!o&&(l==="SUCCESS"||l==="FAILED"||e.taskStatus==="SUCCESS"||e.taskStatus==="FAILED"))return"已被全局判定为终态";if(a?._completed)return"本文件解析完毕";if(l==="RUNNING"&&n?.has_progress&&n?.info?.file_name===e.sourceFilename)return"处理中";if(o&&!a?._pushed&&!a?._completed)return"未入队";if(o&&l==="SUCCESS")return"本文件解析完毕";if(!o){const u=m.value[s];if(u?.items&&u.items.find(_=>M(_,e))&&l==="SUCCESS")return"本文件解析完毕"}return a?._pushed?"已入队":"未入队"}function be(e){return!!(e.downloadUrl||e.fileReady)}function He(e){const s=e?.task?.id;if(!s)return!1;const t=i.value.find(o=>o.taskId===s);if(!t)return{success:!1,retryable:!1};const a=e?.items||[];let l=!1;const n=t.items.map(o=>{const u=a.find(de=>M(de,o));if(!u)return o;const c=Array.isArray(u.countries)?u.countries:void 0,_=Array.isArray(o.countries)?o.countries:void 0,k=c&&c.length>0?c:_&&_.length>0?_:c??_,g=Array.isArray(u.previewRows)?u.previewRows:void 0,w=Array.isArray(o.previewRows)?o.previewRows:void 0,I=g&&g.length>0?g:w&&w.length>0?w:g??w,E={...o,...u,countries:k,previewRows:I,_pushed:o._pushed,_completed:o._completed};return JSON.stringify(E)!==JSON.stringify(o)&&(l=!0),E});for(const o of a)n.some(u=>M(u,o))||(n.push({...o,_pushed:!1,_completed:!1}),l=!0);return JSON.stringify(n)!==JSON.stringify(t.items)&&(t.items=n,l=!0),l&&ne(),l}async function oe(e){const s=e||U();if(s.length)try{const t=await ns(s);se.value&&C.value&&(b.value="后端已恢复,继续执行当前任务并自动衔接后续任务..."),se.value=!1;let a=!1;for(const l of t.items||[]){const n=l?.task?.id;typeof n=="number"&&n>0&&(m.value[n]=l,He(l)&&(a=!0),_e(n)&&(a=!0))}a&&x()}catch(t){const a=(t instanceof Error?t.message:String(t||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(n=>a.includes(n.toLowerCase()))&&C.value&&(se.value=!0,b.value="当前任务运行中,正在等待后端服务恢复...")}}function Ge(){return is()}function ue(e=!1){if(T.value){if(!e)return;clearTimeout(T.value),T.value=null}const s=async()=>{if(T.value=null,ee.value){ue();return}const t=U();if(!t.length)return;ee.value=!0;let a=!1,l=!1;try{await oe(t),await Ae();for(const n of t)Ie(n)&&(he(n),a=!0,l=!0)}finally{ee.value=!1}l?await O():a&&x(),U().length>0&&ue()};e?s():T.value=window.setTimeout(s,Ge())}function ie(e=!1){T.value&&!e||ue(e)}function Z(e){const s=e.taskId?m.value[e.taskId]?.task?.status:null;return s==="SUCCESS"?{className:"success",text:"已完成"}:s==="FAILED"?{className:"failed",text:"失败"}:s==="RUNNING"?{className:"running",text:"执行中"}:e.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:e.taskStatus==="FAILED"?{className:"failed",text:"失败"}:e.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:e.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Se(){T.value&&(window.clearTimeout(T.value),T.value=null),ce()}async function Te(e){const s=J();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const a of e){const l=typeof a=="string"?a:a.absolutePath,n=typeof a=="string"?void 0:a.relativePath,o=await s.upload_file_to_java(l,n);if(!o?.success||!o.data)throw new Error(o?.error||o?.message||`上传失败:${l}`);t.push(o.data)}return t}async function Je(){const e=J();if(!e?.select_brand_xlsx_files){p.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await e.select_brand_xlsx_files();if(!s?.length)return;A.value=s,j.value=await Te(s),p.success(`已选择 ${s.length} 个删除品牌文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function je(){const e=J();if(!e?.select_brand_folder){p.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await e.select_brand_folder();if(!s)return;const t=await vs(s);if(!t.success||!t.items?.length){p.warning(t.error||"该文件夹下没有 xlsx 文件");return}A.value=t.items.map(a=>a.relativePath||a.absolutePath),j.value=await Te(t.items),p.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function ze(){if(!j.value.length){p.warning("请先选择待处理文件");return}try{L.value=!0;const e=await ls({files:j.value.map(n=>({fileKey:n.fileKey,originalFilename:n.originalFilename,relativePath:n.relativePath}))}),s=me(e.items||[]),t=s.some(n=>n.matchStatus==="MATCHED"||n.matchStatus==="INDEX_STALE"||n.matched),a=s.some(n=>n.matchStatus==="INDEX_STALE"&&n.matched),l=s.some(n=>!n.matched&&n.matchStatus!=="MATCHED"&&n.matchStatus!=="INDEX_STALE");b.value=t?"解析完成,可在左侧点击“推送到 Python 队列”开始串行处理":"解析完成,当前没有可推送的文件",S.value="",s.length&&s[0].taskId&&ae(s[0].taskId,s,b.value,S.value),x(),l?p.warning("部分文件尚未命中可用店铺索引,已保留状态信息,请等待后台刷新后重试。"):a?p.warning("部分文件命中的是过期索引,仍可推送到 Python 队列;后台刷新后会自动重新匹配。"):t?p.success("删除品牌解析完成,请在左侧推送到 Python 队列"):p.warning("当前没有可推送的文件,请先等待店铺索引刷新。"),await O()}catch(e){p.error(e instanceof Error?e.message:"执行失败")}finally{L.value=!1}}async function Ee(e){const s=e.taskId;if(!s){p.warning("未找到任务ID");return}const t=J(),a=rs(s),l=e.outputFilename||e.sourceFilename||`delete-brand_${s}.xlsx`;if(t?.save_file_from_url_new){const n=await t.save_file_from_url_new(a,l);n.success?p.success(`已保存:${n.path||l}`):n.error&&n.error!=="用户取消"&&p.error(n.error);return}}async function O(){try{const e=await os();B.value=me(e.items||[]),Be(B.value),x(),await oe(),ie(!0)}catch(e){const s=(e instanceof Error?e.message:String(e||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(a=>s.includes(a.toLowerCase()))||console.error("loadHistory error:",e)}}function Ce(e){i.value.forEach(t=>{t.items=t.items.filter(a=>a.resultId!==e)});const s=i.value.length;i.value=i.value.filter(t=>t.items.length>0),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),B.value=B.value.filter(t=>t.resultId!==e),x(),U().length===0&&Se()}async function Fe(e){try{await us(e),Ce(e),await O(),p.success("已删除")}catch(s){const t=s instanceof Error?s.message:"删除失败";if(typeof t=="string"&&t.includes("记录不存在")){Ce(e),p.success("已删除");return}p.error(t)}}function Re(e){const s=e.slice(0,5).map(a=>`${a.country}:${a.asin}${a.status?`(${a.status})`:""}`);return e.length-s.length>0?`${s.join("、")} 等 ${e.length} 条`:s.join("、")}function It(e,s){}function ce(){q.value&&(window.clearTimeout(q.value),q.value=null)}function Ve(e=3e3){ce(),q.value=window.setTimeout(()=>{q.value=null,Ae().catch(()=>{})},e)}function Xe(e){return e.includes("当前店铺正在执行中")}function Ne(){for(const e of i.value)for(const s of e.items){const t=R(s);if(t==="已入队"||t==="处理中")return!0}return!1}function K(e){return e.resultId!=null?`result:${e.resultId}`:`task:${e.taskId||0}:${e.sourceFilename||""}`}function Pe(e){return!!(e.taskId&&(e.matchStatus==="MATCHED"||e.matchStatus==="INDEX_STALE"||e.matched))}function xe(){return F.value.find(e=>!Pe(e)||K(e)===D.value||P(e.taskId,e)?._completed?!1:R(e)==="未入队")}async function $e(e,s){if(s?.auto&&Ne())return e.taskId,e.sourceFilename,{success:!1,retryable:!0};const t=J(),a=e.taskId;if(!a)return{success:!1,retryable:!1};const l=i.value.find(c=>c.taskId===a);if(!l)return{success:!1,retryable:!1};if(!t?.enqueue_json)return p.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)"),{success:!1,retryable:!1};const n={type:"delete-brand-run",ts:Date.now(),data:{taskId:a,items:[e]}};S.value=JSON.stringify(n,null,2);const o=await t.enqueue_json(n);let u="";if(o?.success){u=`已推送文件 ${e.sourceFilename},当前队列长度:${o.queue_size??"-"}`;const c=P(a,e);return c&&(c._pushed=!0,c._completed=!1),D.value=K(e),ae(a,l.items,u,S.value),ie(!0),b.value=u,{success:!0,retryable:!1}}else{const c=o?.error||"未知错误";if(u=`推送到 Python 队列失败:${c}`,b.value=u,ae(a,l.items,u,S.value),s?.auto&&Xe(c))return{success:!1,retryable:!0}}return b.value=u,{success:!1,retryable:!1}}async function Qe(){const e=xe();if(!e){p.warning("当前没有可推送的文件");return}V.value=!0,C.value=!0,ce();const s=await $e(e);if(V.value=!1,!s.success){C.value=!1,D.value="";return}p.success("已开始按顺序推送到 Python 队列")}async function Ae(){if(!C.value||X.value){C.value,X.value;return}if(Ne())return;const e=D.value;if(e){const t=F.value.find(a=>K(a)===e);if(t){const a=t.taskId,l=N(a);l&&(_e(a),R(t),void 0);const n=R(t);if(n==="已入队"||n==="处理中"){t.sourceFilename;return}if(l){const o=P(a,t);if(o&&!o._completed){t.sourceFilename,o._pushed,o._completed;return}}}}const s=xe();if(!s){D.value="",C.value=!1;return}X.value=!0;try{s.taskId,s.sourceFilename,K(s);const t=await $e(s,{auto:!0});if(!t.success){if(t.retryable){s.taskId,s.sourceFilename,Ve();return}s.taskId,s.sourceFilename,C.value=!1}}finally{X.value=!1}}return Ze(()=>{Le();const e=U();e.length>0?oe(e).then(async()=>{let s=!1,t=!1;for(const a of e)Ie(a)&&(he(a),s=!0,t=!0);t?await O():s&&x(),ie(!0)}):x(),O().catch(()=>{})}),es(()=>{Se()}),(e,s)=>(d(),f("div",hs,[ss(ts,{active:"delete-brand"}),r("div",ps,[r("aside",_s,[s[2]||(s[2]=r("div",{class:"section-title"},"选择文件",-1)),r("div",ys,[s[0]||(s[0]=r("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),r("div",{class:"btns"},[r("button",{type:"button",class:"opt-btn",onClick:Je},"选择 Excel 文件"),r("button",{type:"button",class:"opt-btn",onClick:je},"选择文件夹")]),r("div",gs,[A.value.length?(d(),f(H,{key:0},[(d(!0),f(H,null,fe(te.value,t=>(d(),f("span",{key:t},v(t),1))),128)),A.value.length>te.value.length?(d(),f("span",ms," 还有 "+v(A.value.length-te.value.length)+" 个文件未展开显示 ",1)):h("",!0)],64)):(d(),f("span",ks,"暂未选择删除品牌文件"))])]),s[3]||(s[3]=as('执行说明
',2)),r("div",ws,[r("button",{type:"button",class:"btn-run",disabled:L.value,onClick:ze},v(L.value?"解析中...":"开始解析"),9,Is),r("button",{type:"button",class:"btn-run btn-queue",disabled:V.value||!Ue.value.length,onClick:Qe},v(V.value?"推送中...":"推送到 Python 队列"),9,bs),r("span",Ss,v(L.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),b.value||S.value?(d(),f("div",Ts,[s[1]||(s[1]=r("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),b.value?(d(),f("div",Es,v(b.value),1)):h("",!0),S.value?(d(),f("pre",Cs,v(S.value),1)):h("",!0)])):h("",!0)]),r("section",Fs,[s[12]||(s[12]=r("div",{class:"panel-header"},"删除品牌结果",-1)),r("div",Rs,[r("div",Ns,[r("div",Ps,[s[4]||(s[4]=r("span",{class:"summary-label"},"已处理文件",-1)),r("strong",null,v(z.value.total),1)]),r("div",xs,[s[5]||(s[5]=r("span",{class:"summary-label"},"成功结果",-1)),r("strong",null,v(z.value.successCount),1)]),r("div",$s,[s[6]||(s[6]=r("span",{class:"summary-label"},"失败文件",-1)),r("strong",null,v(z.value.failedCount),1)])]),r("div",As,[s[11]||(s[11]=r("div",{class:"result-list-header"},[r("span",null,"删除品牌结果列表")],-1)),qe.value?(d(),f(H,{key:1},[F.value.length?(d(),f("div",Ms,[s[8]||(s[8]=r("div",{class:"result-subsection-title"},"当前任务",-1)),r("ul",Ls,[(d(!0),f(H,null,fe(F.value,t=>(d(),f("li",{key:`current-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[r("div",Bs,[r("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,qs),r("div",Us,"店铺名:"+v(t.shopName||"-"),1),r("div",Os,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",Ks,"平台:"+v(t.platform),1)):h("",!0),R(t)?(d(),f("div",Hs,"队列状态:"+v(R(t)),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",Gs,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",Js,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",js,[r("div",zs,[s[7]||(s[7]=r("span",null,"任务进度",-1)),r("span",null,v(Y(t))+"%",1)]),r("div",Vs,[r("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),r("div",Xs,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",Qs,v(Re(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",Ws,"错误信息:"+v(W(t)),1)):h("",!0)]),r("div",Ys,[r("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:a=>Ee(t)}," 下载 ",8,Zs)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:a=>Fe(t.resultId)}," 删除 ",8,et)):h("",!0)])]))),128))])])):h("",!0),Q.value.length?(d(),f("div",st,[s[10]||(s[10]=r("div",{class:"result-subsection-title"},"历史记录",-1)),r("ul",tt,[(d(!0),f(H,null,fe(Q.value,t=>(d(),f("li",{key:`history-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[r("div",at,[r("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,nt),r("div",lt,"店铺名:"+v(t.shopName||"-"),1),r("div",rt,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",ot,"平台:"+v(t.platform),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",ut,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",it,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",ct,[r("div",dt,[s[9]||(s[9]=r("span",null,"任务进度",-1)),r("span",null,v(Y(t))+"%",1)]),r("div",ft,[r("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),r("div",vt,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",ht,v(Re(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",pt,"错误信息:"+v(W(t)),1)):h("",!0)]),r("div",_t,[r("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:a=>Ee(t)}," 下载 ",8,yt)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:a=>Fe(t.resultId)}," 删除 ",8,gt)):h("",!0)])]))),128))])])):h("",!0)],64)):(d(),f("div",Ds," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),kt=cs(mt,[["__scopeId","data-v-248c8db1"]]);ds(kt).use(fs).mount("#app");
diff --git a/app/assets/split.js b/app/assets/split.js
index ad4852d..f86d567 100644
--- a/app/assets/split.js
+++ b/app/assets/split.js
@@ -1 +1 @@
-import{d as G,o as J,a,c as n,b as L,B as O,e,F as S,r as $,t as i,f as v,n as x,w as k,s as V,C as M,g as c,h as Q,i as F,E as r,D as W,G as X,m as Y,v as Z,H as ss,_ as es,p as ts,q as ls}from"./pywebview-CeWJDVeG.js";import{e as as}from"./brand-UU-ckLq6.js";const ns={class:"page-shell module-page"},os={class:"main-content"},is={class:"left-panel"},rs={class:"upload-zone"},us={class:"selected-files clean-placeholder split-selected-files"},cs={key:0,class:"more-line"},ds={key:1},ps={class:"option-group column-option-group"},vs={class:"column-toolbar"},fs={class:"column-count"},_s={class:"column-actions"},ms={key:0,class:"column-grid"},ys=["value"],gs={key:1,class:"empty-column-state"},hs={class:"option-group"},bs={class:"radio-item active"},ws={class:"desc"},ks={class:"option-group"},Cs={class:"option-group"},Ss={key:0,class:"split-input-card"},xs={key:1,class:"split-input-card"},Fs={class:"run-row"},Es=["disabled"],Ps={class:"loading-msg"},$s={class:"right-panel"},Bs={class:"task-list-wrap"},Rs={class:"clean-result-summary"},Is={class:"summary-card"},Us={class:"summary-card"},Vs={class:"summary-card"},Ms={class:"result-list-wrap"},Ts={key:0,class:"empty-tasks"},Ds={key:1,class:"task-list clean-result-list"},Hs={class:"left split-result-main"},Ns=["title"],js={key:0,class:"files"},As={key:1,class:"time"},Ks={key:2,class:"files"},zs={key:3,class:"files split-entry-list"},qs={key:4,class:"files"},Gs={class:"task-right split-result-actions"},Js=["onClick"],Ls=["onClick"],Os=G({__name:"BrandSplitTab",setup(Ws){const f=c([]),C=c(""),d=c([]),_=c([]),p=c([]),B=c(0),u=c("rows_per_file"),m=c(2e3),y=c(5),g=c(!1),h=c([]),b=c({total:0,successCount:0,failedCount:0,items:[]}),E=Q(()=>f.value.slice(0,8));function T(l){b.value={total:l.length,successCount:l.filter(s=>s.success).length,failedCount:l.filter(s=>!s.success).length,items:l}}async function R(l){const s=F();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const o of l){const U=typeof o=="string"?o:o.absolutePath,q=typeof o=="string"?void 0:o.relativePath,w=await s.upload_file_to_java(U,q);if(!w?.success||!w.data)throw new Error(w?.error||w?.message||`上传失败:${U}`);t.push(w.data)}return t}async function I(l){const s=await Y(l);_.value=s.headers||[],p.value=[...s.headers||[]],B.value=s.totalRows||0}async function D(l,s){f.value=l,C.value="",d.value=await R(l),d.value.length>0&&await I(d.value[0].fileKey),r.success(s)}async function H(){const l=F();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await l.select_brand_xlsx_files();if(!s?.length)return;await D(s,`已选择 ${s.length} 个待拆分文件`)}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function N(){const l=F();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await l.select_brand_folder();if(!s)return;C.value=s.split(/[/\\]/).filter(Boolean).pop()||"";const t=await as(s);if(t.success&&t.items?.length){f.value=t.items.map(o=>o.relativePath),d.value=await R(t.items),d.value.length>0&&await I(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function j(){if(!d.value.length){r.warning("请先选择待拆分 Excel 文件或文件夹");return}if(!p.value.length){r.warning("请至少选择一列保留列");return}if(u.value==="rows_per_file"&&(!m.value||m.value<=0)){r.warning("请输入有效的每份条数");return}if(u.value==="parts"&&(!y.value||y.value<=0)){r.warning("请输入有效的拆分份数");return}try{g.value=!0;const l=await W({files:d.value.map(s=>({fileKey:s.fileKey,originalFilename:s.originalFilename,relativePath:s.relativePath})),selectedColumns:p.value,splitMode:u.value,rowsPerFile:u.value==="rows_per_file"&&m.value||void 0,parts:u.value==="parts"&&y.value||void 0,archiveName:d.value.some(s=>!!s.relativePath)&&C.value?C.value:void 0});b.value=l,h.value=l.items||[],await P(),r.success("数据拆分完成")}catch(l){r.error(l instanceof Error?l.message:"数据拆分失败")}finally{g.value=!1}}async function P(){try{const l=await X();h.value=l.items||[],T(h.value)}catch{}}async function A(l){try{await ss(l),await P(),r.success("已删除")}catch(s){r.error(s instanceof Error?s.message:"删除失败")}}function K(l){const s=l.slice(0,4).map(o=>o.rowCount!==void 0?`${o.filename}(${o.rowCount}条)`:o.filename);return l.length-s.length>0?`${s.join("、")} 等 ${l.length} 个文件`:s.join("、")}async function z(l){const s=F();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.zip`;if(s?.save_file_from_url_new){const o=await s.save_file_from_url_new(l.downloadUrl,t);o.success?r.success(`已保存:${o.path||t}`):o.error&&o.error!=="用户取消"&&r.error(o.error);return}}return J(()=>{P().catch(()=>{})}),(l,s)=>(a(),n("div",ns,[L(O,{active:"split"}),e("div",os,[e("aside",is,[s[14]||(s[14]=e("div",{class:"section-title"},"选择文件",-1)),e("div",rs,[s[7]||(s[7]=e("div",{class:"hint"},"选择需要拆分的 Excel 文件或文件夹,按规则拆成多个 Excel 文件。",-1)),e("div",{class:"btns"},[e("button",{type:"button",class:"opt-btn",onClick:H},"选择 Excel 文件"),e("button",{type:"button",class:"opt-btn",onClick:N},"选择文件夹")]),e("div",us,[f.value.length?(a(),n(S,{key:0},[(a(!0),n(S,null,$(E.value,t=>(a(),n("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(a(),n("span",cs," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):v("",!0)],64)):(a(),n("span",ds,"暂未选择待拆分文件"))])]),s[15]||(s[15]=e("div",{class:"section-title"},"保留列设置",-1)),e("div",ps,[e("div",vs,[e("span",fs,"已选择 "+i(p.value.length)+" / "+i(_.value.length)+" 列",1),e("div",_s,[e("button",{type:"button",class:"opt-btn small",onClick:s[0]||(s[0]=t=>p.value=[..._.value])},"全选"),e("button",{type:"button",class:"opt-btn small",onClick:s[1]||(s[1]=t=>p.value=[])},"清空")])]),_.value.length?(a(),n("div",ms,[(a(!0),n(S,null,$(_.value,t=>(a(),n("label",{key:t,class:x(["column-item",{active:p.value.includes(t)}])},[k(e("input",{"onUpdate:modelValue":s[2]||(s[2]=o=>p.value=o),type:"checkbox",value:t},null,8,ys),[[Z,p.value]]),e("span",null,i(t),1)],2))),128))])):(a(),n("div",gs,"请选择 Excel 文件后读取表头并填充可保留列"))]),s[16]||(s[16]=e("div",{class:"section-title"},"总条数",-1)),e("div",hs,[e("label",bs,[s[9]||(s[9]=e("input",{type:"checkbox",checked:"",disabled:""},null,-1)),e("div",null,[s[8]||(s[8]=e("span",{class:"label"},"当前数据总条数",-1)),e("div",ws,i(B.value)+" 条(不含表头)",1)])])]),s[17]||(s[17]=e("div",{class:"section-title"},"拆分模式",-1)),e("div",ks,[e("label",{class:x(["radio-item",{active:u.value==="rows_per_file"}])},[k(e("input",{"onUpdate:modelValue":s[3]||(s[3]=t=>u.value=t),type:"radio",value:"rows_per_file",name:"split-mode"},null,512),[[V,u.value]]),s[10]||(s[10]=e("div",null,[e("span",{class:"label"},"按每份条数"),e("div",{class:"desc"},"例如每份 2000 条,最后一份放剩余数据")],-1))],2),e("label",{class:x(["radio-item",{active:u.value==="parts"}])},[k(e("input",{"onUpdate:modelValue":s[4]||(s[4]=t=>u.value=t),type:"radio",value:"parts",name:"split-mode"},null,512),[[V,u.value]]),s[11]||(s[11]=e("div",null,[e("span",{class:"label"},"按份数平均拆分"),e("div",{class:"desc"},"例如拆 5 份,按总条数尽量平均分配")],-1))],2)]),s[18]||(s[18]=e("div",{class:"section-title"},"拆分参数",-1)),e("div",Cs,[u.value==="rows_per_file"?(a(),n("label",Ss,[s[12]||(s[12]=e("span",{class:"label"},"每份条数",-1)),k(e("input",{"onUpdate:modelValue":s[5]||(s[5]=t=>m.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 2000"},null,512),[[M,m.value,void 0,{number:!0}]])])):(a(),n("label",xs,[s[13]||(s[13]=e("span",{class:"label"},"拆分份数",-1)),k(e("input",{"onUpdate:modelValue":s[6]||(s[6]=t=>y.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 5"},null,512),[[M,y.value,void 0,{number:!0}]])]))]),e("div",Fs,[e("button",{type:"button",class:"btn-run",disabled:g.value,onClick:j},i(g.value?"拆分中...":"开始拆分"),9,Es),e("span",Ps,i(g.value?"正在拆分并上传结果,请稍候…":"选择文件、保留列和拆分规则后即可执行拆分,结果会显示在右侧供下载"),1)])]),e("section",$s,[s[23]||(s[23]=e("div",{class:"panel-header"},"拆分结果",-1)),e("div",Bs,[e("div",Rs,[e("div",Is,[s[19]||(s[19]=e("span",{class:"summary-label"},"已处理文件",-1)),e("strong",null,i(b.value.total),1)]),e("div",Us,[s[20]||(s[20]=e("span",{class:"summary-label"},"成功结果",-1)),e("strong",null,i(b.value.successCount),1)]),e("div",Vs,[s[21]||(s[21]=e("span",{class:"summary-label"},"失败文件",-1)),e("strong",null,i(b.value.failedCount),1)])]),e("div",Ms,[s[22]||(s[22]=e("div",{class:"result-list-header"},[e("span",null,"拆分结果压缩包列表")],-1)),h.value.length===0?(a(),n("div",Ts," 暂无拆分结果,完成数据拆分后会在这里展示压缩包文件 ")):(a(),n("ul",Ds,[(a(!0),n(S,null,$(h.value,t=>(a(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}-${t.rowCount||0}`,class:"task-item split-result-item"},[e("div",Hs,[e("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Ns),t.outputFilename?(a(),n("div",js,"压缩包文件:"+i(t.outputFilename),1)):v("",!0),t.rowCount!==void 0?(a(),n("div",As,"压缩包共包含 "+i(t.rowCount)+" 条数据",1)):v("",!0),t.entryCount&&t.entryCount>0?(a(),n("div",Ks,"压缩包内共 "+i(t.entryCount)+" 个拆分文件 ",1)):v("",!0),t.entries&&t.entries.length?(a(),n("div",zs," 压缩包内容:"+i(K(t.entries)),1)):v("",!0),t.error?(a(),n("div",qs,"错误信息:"+i(t.error),1)):v("",!0)]),e("div",Gs,[e("span",{class:x(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(a(),n("button",{key:0,type:"button",class:"download",onClick:o=>z(t)}," 下载压缩包 ",8,Js)):v("",!0),t.resultId?(a(),n("button",{key:1,type:"button",class:"btn-delete",onClick:o=>A(t.resultId)}," 删除 ",8,Ls)):v("",!0)])]))),128))]))])])])])]))}}),Qs=es(Os,[["__scopeId","data-v-c5a98d6b"]]);ts(Qs).use(ls).mount("#app");
+import{d as q,o as G,a,c as n,b as L,B as O,e,F as x,r as I,t as i,f,n as F,w as C,u as V,G as M,g as d,h as Q,i as E,E as r,H as W,I as X,p as Y,v as Z,J as ss,K as es,_ as ts,q as ls,s as as}from"./pywebview-C66x_2Dh.js";import{e as ns}from"./brand-BZije8D7.js";const os={class:"page-shell module-page"},is={class:"main-content"},rs={class:"left-panel"},us={class:"upload-zone"},ds={class:"selected-files clean-placeholder split-selected-files"},cs={key:0,class:"more-line"},ps={key:1},vs={class:"option-group column-option-group"},fs={class:"column-toolbar"},_s={class:"column-count"},ms={class:"column-actions"},ys={key:0,class:"column-grid"},gs=["value"],hs={key:1,class:"empty-column-state"},bs={class:"option-group"},ws={class:"radio-item active"},ks={class:"desc"},Cs={class:"option-group"},Ss={class:"option-group"},xs={key:0,class:"split-input-card"},Fs={key:1,class:"split-input-card"},Es={class:"run-row"},Ps=["disabled"],$s={class:"loading-msg"},Is={class:"right-panel"},Rs={class:"task-list-wrap"},Bs={class:"clean-result-summary"},Us={class:"summary-card"},Vs={class:"summary-card"},Ms={class:"summary-card"},Ts={class:"result-list-wrap"},Ds={key:0,class:"empty-tasks"},Hs={key:1,class:"task-list clean-result-list"},Ks={class:"left split-result-main"},Ns=["title"],js={key:0,class:"files"},As={key:1,class:"time"},zs={key:2,class:"files"},Js={key:3,class:"files split-entry-list"},qs={key:4,class:"files"},Gs={class:"task-right split-result-actions"},Ls=["onClick"],Os=["onClick"],Qs=q({__name:"BrandSplitTab",setup(Xs){const _=d([]),S=d(""),c=d([]),m=d([]),p=d([]),R=d(0),u=d("rows_per_file"),y=d(2e3),g=d(5),h=d(!1),b=d([]),w=d({total:0,successCount:0,failedCount:0,items:[]}),P=Q(()=>_.value.slice(0,8));function T(l){w.value={total:l.length,successCount:l.filter(s=>s.success).length,failedCount:l.filter(s=>!s.success).length,items:l}}async function B(l){const s=E();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const o of l){const v=typeof o=="string"?o:o.absolutePath,J=typeof o=="string"?void 0:o.relativePath,k=await s.upload_file_to_java(v,J);if(!k?.success||!k.data)throw new Error(k?.error||k?.message||`上传失败:${v}`);t.push(k.data)}return t}async function U(l){const s=await Y(l);m.value=s.headers||[],p.value=[...s.headers||[]],R.value=s.totalRows||0}async function D(l,s){_.value=l,S.value="",c.value=await B(l),c.value.length>0&&await U(c.value[0].fileKey),r.success(s)}async function H(){const l=E();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await l.select_brand_xlsx_files();if(!s?.length)return;await D(s,`已选择 ${s.length} 个待拆分文件`)}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function K(){const l=E();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await l.select_brand_folder();if(!s)return;S.value=s.split(/[/\\]/).filter(Boolean).pop()||"";const t=await ns(s);if(t.success&&t.items?.length){_.value=t.items.map(o=>o.relativePath),c.value=await B(t.items),c.value.length>0&&await U(c.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function N(){if(!c.value.length){r.warning("请先选择待拆分 Excel 文件或文件夹");return}if(!p.value.length){r.warning("请至少选择一列保留列");return}if(u.value==="rows_per_file"&&(!y.value||y.value<=0)){r.warning("请输入有效的每份条数");return}if(u.value==="parts"&&(!g.value||g.value<=0)){r.warning("请输入有效的拆分份数");return}try{h.value=!0;const l=await W({files:c.value.map(s=>({fileKey:s.fileKey,originalFilename:s.originalFilename,relativePath:s.relativePath})),selectedColumns:p.value,splitMode:u.value,rowsPerFile:u.value==="rows_per_file"&&y.value||void 0,parts:u.value==="parts"&&g.value||void 0,archiveName:c.value.some(s=>!!s.relativePath)&&S.value?S.value:void 0});w.value=l,b.value=l.items||[],await $(),r.success("数据拆分完成")}catch(l){r.error(l instanceof Error?l.message:"数据拆分失败")}finally{h.value=!1}}async function $(){try{const l=await X();b.value=l.items||[],T(b.value)}catch{}}async function j(l){try{await ss(l),await $(),r.success("已删除")}catch(s){r.error(s instanceof Error?s.message:"删除失败")}}function A(l){const s=l.slice(0,4).map(o=>o.rowCount!==void 0?`${o.filename}(${o.rowCount}条)`:o.filename);return l.length-s.length>0?`${s.join("、")} 等 ${l.length} 个文件`:s.join("、")}async function z(l){const s=E(),t=l.downloadUrl||(l.resultId?es(l.resultId):"");if(!t){r.warning("当前结果没有下载地址");return}const o=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.zip`;if(s?.save_file_from_url_new){const v=await s.save_file_from_url_new(t,o);v.success?r.success(`已保存:${v.path||o}`):v.error&&v.error!=="用户取消"&&r.error(v.error);return}}return G(()=>{$().catch(()=>{})}),(l,s)=>(a(),n("div",os,[L(O,{active:"split"}),e("div",is,[e("aside",rs,[s[14]||(s[14]=e("div",{class:"section-title"},"选择文件",-1)),e("div",us,[s[7]||(s[7]=e("div",{class:"hint"},"选择需要拆分的 Excel 文件或文件夹,按规则拆成多个 Excel 文件。",-1)),e("div",{class:"btns"},[e("button",{type:"button",class:"opt-btn",onClick:H},"选择 Excel 文件"),e("button",{type:"button",class:"opt-btn",onClick:K},"选择文件夹")]),e("div",ds,[_.value.length?(a(),n(x,{key:0},[(a(!0),n(x,null,I(P.value,t=>(a(),n("span",{key:t},i(t),1))),128)),_.value.length>P.value.length?(a(),n("span",cs," 还有 "+i(_.value.length-P.value.length)+" 个文件未展开显示 ",1)):f("",!0)],64)):(a(),n("span",ps,"暂未选择待拆分文件"))])]),s[15]||(s[15]=e("div",{class:"section-title"},"保留列设置",-1)),e("div",vs,[e("div",fs,[e("span",_s,"已选择 "+i(p.value.length)+" / "+i(m.value.length)+" 列",1),e("div",ms,[e("button",{type:"button",class:"opt-btn small",onClick:s[0]||(s[0]=t=>p.value=[...m.value])},"全选"),e("button",{type:"button",class:"opt-btn small",onClick:s[1]||(s[1]=t=>p.value=[])},"清空")])]),m.value.length?(a(),n("div",ys,[(a(!0),n(x,null,I(m.value,t=>(a(),n("label",{key:t,class:F(["column-item",{active:p.value.includes(t)}])},[C(e("input",{"onUpdate:modelValue":s[2]||(s[2]=o=>p.value=o),type:"checkbox",value:t},null,8,gs),[[Z,p.value]]),e("span",null,i(t),1)],2))),128))])):(a(),n("div",hs,"请选择 Excel 文件后读取表头并填充可保留列"))]),s[16]||(s[16]=e("div",{class:"section-title"},"总条数",-1)),e("div",bs,[e("label",ws,[s[9]||(s[9]=e("input",{type:"checkbox",checked:"",disabled:""},null,-1)),e("div",null,[s[8]||(s[8]=e("span",{class:"label"},"当前数据总条数",-1)),e("div",ks,i(R.value)+" 条(不含表头)",1)])])]),s[17]||(s[17]=e("div",{class:"section-title"},"拆分模式",-1)),e("div",Cs,[e("label",{class:F(["radio-item",{active:u.value==="rows_per_file"}])},[C(e("input",{"onUpdate:modelValue":s[3]||(s[3]=t=>u.value=t),type:"radio",value:"rows_per_file",name:"split-mode"},null,512),[[V,u.value]]),s[10]||(s[10]=e("div",null,[e("span",{class:"label"},"按每份条数"),e("div",{class:"desc"},"例如每份 2000 条,最后一份放剩余数据")],-1))],2),e("label",{class:F(["radio-item",{active:u.value==="parts"}])},[C(e("input",{"onUpdate:modelValue":s[4]||(s[4]=t=>u.value=t),type:"radio",value:"parts",name:"split-mode"},null,512),[[V,u.value]]),s[11]||(s[11]=e("div",null,[e("span",{class:"label"},"按份数平均拆分"),e("div",{class:"desc"},"例如拆 5 份,按总条数尽量平均分配")],-1))],2)]),s[18]||(s[18]=e("div",{class:"section-title"},"拆分参数",-1)),e("div",Ss,[u.value==="rows_per_file"?(a(),n("label",xs,[s[12]||(s[12]=e("span",{class:"label"},"每份条数",-1)),C(e("input",{"onUpdate:modelValue":s[5]||(s[5]=t=>y.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 2000"},null,512),[[M,y.value,void 0,{number:!0}]])])):(a(),n("label",Fs,[s[13]||(s[13]=e("span",{class:"label"},"拆分份数",-1)),C(e("input",{"onUpdate:modelValue":s[6]||(s[6]=t=>g.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 5"},null,512),[[M,g.value,void 0,{number:!0}]])]))]),e("div",Es,[e("button",{type:"button",class:"btn-run",disabled:h.value,onClick:N},i(h.value?"拆分中...":"开始拆分"),9,Ps),e("span",$s,i(h.value?"正在拆分并上传结果,请稍候…":"选择文件、保留列和拆分规则后即可执行拆分,结果会显示在右侧供下载"),1)])]),e("section",Is,[s[23]||(s[23]=e("div",{class:"panel-header"},"拆分结果",-1)),e("div",Rs,[e("div",Bs,[e("div",Us,[s[19]||(s[19]=e("span",{class:"summary-label"},"已处理文件",-1)),e("strong",null,i(w.value.total),1)]),e("div",Vs,[s[20]||(s[20]=e("span",{class:"summary-label"},"成功结果",-1)),e("strong",null,i(w.value.successCount),1)]),e("div",Ms,[s[21]||(s[21]=e("span",{class:"summary-label"},"失败文件",-1)),e("strong",null,i(w.value.failedCount),1)])]),e("div",Ts,[s[22]||(s[22]=e("div",{class:"result-list-header"},[e("span",null,"拆分结果压缩包列表")],-1)),b.value.length===0?(a(),n("div",Ds," 暂无拆分结果,完成数据拆分后会在这里展示压缩包文件 ")):(a(),n("ul",Hs,[(a(!0),n(x,null,I(b.value,t=>(a(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}-${t.rowCount||0}`,class:"task-item split-result-item"},[e("div",Ks,[e("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Ns),t.outputFilename?(a(),n("div",js,"压缩包文件:"+i(t.outputFilename),1)):f("",!0),t.rowCount!==void 0?(a(),n("div",As,"压缩包共包含 "+i(t.rowCount)+" 条数据",1)):f("",!0),t.entryCount&&t.entryCount>0?(a(),n("div",zs,"压缩包内共 "+i(t.entryCount)+" 个拆分文件 ",1)):f("",!0),t.entries&&t.entries.length?(a(),n("div",Js," 压缩包内容:"+i(A(t.entries)),1)):f("",!0),t.error?(a(),n("div",qs,"错误信息:"+i(t.error),1)):f("",!0)]),e("div",Gs,[e("span",{class:F(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&(t.downloadUrl||t.resultId)?(a(),n("button",{key:0,type:"button",class:"download",onClick:o=>z(t)}," 下载压缩包 ",8,Ls)):f("",!0),t.resultId?(a(),n("button",{key:1,type:"button",class:"btn-delete",onClick:o=>j(t.resultId)}," 删除 ",8,Os)):f("",!0)])]))),128))]))])])])])]))}}),Ws=ts(Qs,[["__scopeId","data-v-f3b42aa3"]]);ls(Ws).use(as).mount("#app");
diff --git a/app/new_web_source/convert.html b/app/new_web_source/convert.html
index 7e695d2..ed6e58f 100644
--- a/app/new_web_source/convert.html
+++ b/app/new_web_source/convert.html
@@ -5,10 +5,10 @@
格式转换 - 数富AI
-
-
-
-
+
+
+
+
diff --git a/app/new_web_source/dedupe.html b/app/new_web_source/dedupe.html
index 9a875cb..2f621af 100644
--- a/app/new_web_source/dedupe.html
+++ b/app/new_web_source/dedupe.html
@@ -5,10 +5,10 @@
数据去重 - 数富AI
-
-
-
-
+
+
+
+
diff --git a/app/new_web_source/delete-brand.html b/app/new_web_source/delete-brand.html
index 3ce8fa8..ff84f95 100644
--- a/app/new_web_source/delete-brand.html
+++ b/app/new_web_source/delete-brand.html
@@ -5,10 +5,10 @@
删除品牌 - 数富AI
-
-
-
-
+
+
+
+
diff --git a/app/new_web_source/split.html b/app/new_web_source/split.html
index 516fa0a..3ef460a 100644
--- a/app/new_web_source/split.html
+++ b/app/new_web_source/split.html
@@ -5,10 +5,10 @@
数据拆分 - 数富AI
-
-
-
-
+
+
+
+
diff --git a/app/web_source/brand.html b/app/web_source/brand.html
index 68c93e4..6593b0f 100644
--- a/app/web_source/brand.html
+++ b/app/web_source/brand.html
@@ -784,7 +784,24 @@
else if (status === 'cancelled') showToast('已取消');
}
- function pollRunTask(taskId) {
+ function verifyTerminalTaskStatus(taskId, expectedStatus) {
+ return fetch('/api/brand/tasks/' + taskId, {
+ credentials: 'include',
+ headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' })
+ })
+ .then(function(r) { return r.json(); })
+ .then(function(res) {
+ if (!res.success || !res.task) return null;
+ var actualStatus = (res.task.status || '').toLowerCase();
+ if (actualStatus === 'success' || actualStatus === 'failed' || actualStatus === 'cancelled') {
+ return actualStatus;
+ }
+ return null;
+ })
+ .catch(function() { return null; });
+ }
+
+ function pollRunTask(taskId) {
pollTaskId = taskId;
var runProgressFill = document.getElementById('runProgressFill');
var runProgressText = document.getElementById('runProgressText');
@@ -847,15 +864,18 @@
break;
}
} catch (err) {}
- }
-
- if (terminalStatus) {
- try { runTaskEventAbortController.abort(); } catch (e2) {}
- runTaskEventAbortController = null;
- onRunTaskFinished(terminalStatus);
- return;
- }
- }
+ }
+
+ if (terminalStatus) {
+ var verifiedStatus = await verifyTerminalTaskStatus(taskId, terminalStatus);
+ if (verifiedStatus) {
+ try { runTaskEventAbortController.abort(); } catch (e2) {}
+ runTaskEventAbortController = null;
+ onRunTaskFinished(verifiedStatus);
+ return;
+ }
+ }
+ }
} catch (err) {}
})();
@@ -917,7 +937,7 @@
});
}
tick();
- pollTimer = setInterval(tick, 10000);
+ pollTimer = setInterval(tick, 3000);
}
document.getElementById('btnRun').onclick = function() {
@@ -1127,7 +1147,11 @@
});
loadTasks();
- // setInterval(loadTasks, 10000);
+ setInterval(function() {
+ if (cachedTasks.some(function(t) { return (t.status || '').toLowerCase() === 'running'; })) {
+ loadTasks();
+ }
+ }, 3000);
if (window.addEventListener) {
window.addEventListener('pywebviewready', function() {
diff --git a/backend-java/jstack-33832.txt b/backend-java/jstack-33832.txt
new file mode 100644
index 0000000..82de4e2
--- /dev/null
+++ b/backend-java/jstack-33832.txt
@@ -0,0 +1,1106 @@
+2026-04-27 08:55:51
+Full thread dump Java HotSpot(TM) 64-Bit Server VM (21.0.9+7-LTS-338 mixed mode, sharing):
+
+Threads class SMR info:
+_java_thread_list=0x0000023371912e70, length=49, elements={
+0x0000023366303450, 0x0000023366309fe0, 0x000002336630d5c0, 0x000002336630e280,
+0x000002336630fc60, 0x0000023366311870, 0x000002336631e0a0, 0x0000023366312c20,
+0x000002336b23eb60, 0x000002336b2497c0, 0x000002336c9ea570, 0x000002336dcf88a0,
+0x000002336dcf95c0, 0x000002336dcf8f30, 0x000002336dcf9c50, 0x000002336dcf74f0,
+0x0000023372aec650, 0x0000023372aee090, 0x0000023372aed370, 0x0000023372aee720,
+0x0000023372aeb2a0, 0x0000023372aeda00, 0x0000023372aeedb0, 0x0000023372aeb930,
+0x0000023372aef440, 0x0000023372aefad0, 0x0000023372af0160, 0x0000023372af0e80,
+0x0000023372af1510, 0x0000023371823030, 0x00000233718236c0, 0x0000023371822310,
+0x00000233718229a0, 0x00000233718243e0, 0x0000023371826b40, 0x0000023371825100,
+0x0000023371823d50, 0x0000023371824a70, 0x0000023371828c10, 0x00000233718271d0,
+0x00000233718264b0, 0x0000023371828580, 0x0000023371827860, 0x0000023371827ef0,
+0x0000023371825e20, 0x000002336e8db070, 0x000002336e8dc420, 0x000002336e8dd7d0,
+0x000002336e8dbd90
+}
+
+"Reference Handler" #9 [36596] daemon prio=10 os_prio=2 cpu=15.62ms elapsed=372.99s tid=0x0000023366303450 nid=36596 waiting on condition [0x0000003920aff000]
+ java.lang.Thread.State: RUNNABLE
+ at java.lang.ref.Reference.waitForReferencePendingList(java.base@21.0.9/Native Method)
+ at java.lang.ref.Reference.processPendingReferences(java.base@21.0.9/Reference.java:246)
+ at java.lang.ref.Reference$ReferenceHandler.run(java.base@21.0.9/Reference.java:208)
+
+"Finalizer" #10 [37640] daemon prio=8 os_prio=1 cpu=0.00ms elapsed=372.99s tid=0x0000023366309fe0 nid=37640 in Object.wait() [0x0000003920bff000]
+ java.lang.Thread.State: WAITING (on object monitor)
+ at java.lang.Object.wait0(java.base@21.0.9/Native Method)
+ - waiting on <0x000000068c2c9638> (a java.lang.ref.NativeReferenceQueue$Lock)
+ at java.lang.Object.wait(java.base@21.0.9/Object.java:366)
+ at java.lang.Object.wait(java.base@21.0.9/Object.java:339)
+ at java.lang.ref.NativeReferenceQueue.await(java.base@21.0.9/NativeReferenceQueue.java:48)
+ at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.9/ReferenceQueue.java:158)
+ at java.lang.ref.NativeReferenceQueue.remove(java.base@21.0.9/NativeReferenceQueue.java:89)
+ - locked <0x000000068c2c9638> (a java.lang.ref.NativeReferenceQueue$Lock)
+ at java.lang.ref.Finalizer$FinalizerThread.run(java.base@21.0.9/Finalizer.java:173)
+
+"Signal Dispatcher" #11 [29528] daemon prio=9 os_prio=2 cpu=0.00ms elapsed=372.99s tid=0x000002336630d5c0 nid=29528 waiting on condition [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"Attach Listener" #12 [32964] daemon prio=5 os_prio=2 cpu=15.62ms elapsed=372.99s tid=0x000002336630e280 nid=32964 waiting on condition [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"Service Thread" #13 [34584] daemon prio=9 os_prio=0 cpu=15.62ms elapsed=372.99s tid=0x000002336630fc60 nid=34584 runnable [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"Monitor Deflation Thread" #14 [13176] daemon prio=9 os_prio=0 cpu=15.62ms elapsed=372.99s tid=0x0000023366311870 nid=13176 runnable [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"C2 CompilerThread0" #15 [31784] daemon prio=9 os_prio=2 cpu=4265.62ms elapsed=372.99s tid=0x000002336631e0a0 nid=31784 waiting on condition [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+ No compile task
+
+"C1 CompilerThread0" #23 [24972] daemon prio=9 os_prio=2 cpu=1250.00ms elapsed=372.99s tid=0x0000023366312c20 nid=24972 waiting on condition [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+ No compile task
+
+"Notification Thread" #27 [36192] daemon prio=9 os_prio=0 cpu=15.62ms elapsed=372.94s tid=0x000002336b23eb60 nid=36192 runnable [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"Common-Cleaner" #28 [37400] daemon prio=8 os_prio=1 cpu=0.00ms elapsed=372.93s tid=0x000002336b2497c0 nid=37400 waiting on condition [0x00000039215ff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068c2cebe0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1852)
+ at java.lang.ref.ReferenceQueue.await(java.base@21.0.9/ReferenceQueue.java:71)
+ at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.9/ReferenceQueue.java:143)
+ at java.lang.ref.ReferenceQueue.remove(java.base@21.0.9/ReferenceQueue.java:218)
+ at jdk.internal.ref.CleanerImpl.run(java.base@21.0.9/CleanerImpl.java:140)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+ at jdk.internal.misc.InnocuousThread.run(java.base@21.0.9/InnocuousThread.java:186)
+
+"AsyncAppender-Worker-DefaultAppender" #48 [35540] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=366.81s tid=0x000002336c9ea570 nid=35540 waiting on condition [0x00000039223ff000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068db92e28> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.ArrayBlockingQueue.take(java.base@21.0.9/ArrayBlockingQueue.java:420)
+ at org.apache.rocketmq.logging.ch.qos.logback.core.AsyncAppenderBase$Worker.run(AsyncAppenderBase.java:298)
+
+"Catalina-utility-1" #49 [32292] prio=1 os_prio=-2 cpu=93.75ms elapsed=365.95s tid=0x000002336dcf88a0 nid=32292 waiting on condition [0x0000003922bff000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068dbc1428> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1177)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"Catalina-utility-2" #50 [27800] prio=1 os_prio=-2 cpu=78.12ms elapsed=365.95s tid=0x000002336dcf95c0 nid=27800 waiting on condition [0x0000003922cff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068dbc1428> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"container-0" #51 [36976] prio=5 os_prio=0 cpu=0.00ms elapsed=365.95s tid=0x000002336dcf8f30 nid=36976 waiting on condition [0x0000003922dff000]
+ java.lang.Thread.State: TIMED_WAITING (sleeping)
+ at java.lang.Thread.sleep0(java.base@21.0.9/Native Method)
+ at java.lang.Thread.sleep(java.base@21.0.9/Thread.java:509)
+ at org.apache.catalina.core.StandardServer.await(StandardServer.java:520)
+ at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:219)
+
+"Jndi-Dns-address-change-listener" #52 [33984] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=365.81s tid=0x000002336dcf9c50 nid=33984 runnable [0x0000003922efe000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.net.dns.ResolverConfigurationImpl.notifyAddrChange0(java.base@21.0.9/Native Method)
+ at sun.net.dns.ResolverConfigurationImpl$AddressChangeListener.run(java.base@21.0.9/ResolverConfigurationImpl.java:179)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+ at jdk.internal.misc.InnocuousThread.run(java.base@21.0.9/InnocuousThread.java:186)
+
+"lettuce-timer-3-1" #53 [29332] daemon prio=5 os_prio=0 cpu=46.88ms elapsed=365.63s tid=0x000002336dcf74f0 nid=29332 waiting on condition [0x0000003922fff000]
+ java.lang.Thread.State: TIMED_WAITING (sleeping)
+ at java.lang.Thread.sleep0(java.base@21.0.9/Native Method)
+ at java.lang.Thread.sleep(java.base@21.0.9/Thread.java:509)
+ at io.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:598)
+ at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:494)
+ at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"mysql-cj-abandoned-connection-cleanup" #54 [1808] daemon prio=5 os_prio=0 cpu=15.62ms elapsed=365.19s tid=0x0000023372aec650 nid=1808 waiting on condition [0x00000039232fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068d7c7970> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1852)
+ at java.lang.ref.ReferenceQueue.await(java.base@21.0.9/ReferenceQueue.java:71)
+ at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.9/ReferenceQueue.java:143)
+ at java.lang.ref.ReferenceQueue.remove(java.base@21.0.9/ReferenceQueue.java:218)
+ at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:93)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1144)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"MQClientFactoryScheduledThread" #56 [32888] prio=5 os_prio=0 cpu=0.00ms elapsed=362.22s tid=0x0000023372aee090 nid=32888 waiting on condition [0x00000039233fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e32bee0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"NettyEventExecutor" #57 [9732] prio=5 os_prio=0 cpu=0.00ms elapsed=362.21s tid=0x0000023372aed370 nid=9732 waiting on condition [0x00000039234ff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e32e5d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.LinkedBlockingQueue.poll(java.base@21.0.9/LinkedBlockingQueue.java:460)
+ at org.apache.rocketmq.remoting.netty.NettyRemotingAbstract$NettyEventExecutor.run(NettyRemotingAbstract.java:701)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"ClientHouseKeepingService" #55 [32364] prio=5 os_prio=0 cpu=453.12ms elapsed=362.21s tid=0x0000023372aee720 nid=32364 waiting on condition [0x00000039235fe000]
+ java.lang.Thread.State: TIMED_WAITING (sleeping)
+ at java.lang.Thread.sleep0(java.base@21.0.9/Native Method)
+ at java.lang.Thread.sleep(java.base@21.0.9/Thread.java:509)
+ at io.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:598)
+ at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:494)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"PullMessageService" #58 [37548] prio=5 os_prio=0 cpu=0.00ms elapsed=362.21s tid=0x0000023372aeb2a0 nid=37548 waiting on condition [0x00000039236ff000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e3333e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.rocketmq.client.impl.consumer.PullMessageService.run(PullMessageService.java:131)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"RebalanceService" #59 [24716] prio=5 os_prio=0 cpu=0.00ms elapsed=362.21s tid=0x0000023372aeda00 nid=24716 waiting on condition [0x00000039237fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e335ac0> (a org.apache.rocketmq.common.CountDownLatch2$Sync)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@21.0.9/AbstractQueuedSynchronizer.java:756)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1126)
+ at org.apache.rocketmq.common.CountDownLatch2.await(CountDownLatch2.java:114)
+ at org.apache.rocketmq.common.ServiceThread.waitForRunning(ServiceThread.java:117)
+ at org.apache.rocketmq.client.impl.consumer.RebalanceService.run(RebalanceService.java:45)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"LatencyFaultToleranceScheduledThread" #60 [14148] prio=5 os_prio=0 cpu=0.00ms elapsed=362.21s tid=0x0000023372aeedb0 nid=14148 waiting on condition [0x00000039238ff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e3381b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"RequestHouseKeepingService1" #61 [12344] prio=5 os_prio=0 cpu=93.75ms elapsed=362.21s tid=0x0000023372aeb930 nid=12344 waiting on condition [0x00000039239fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e33a8b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"LatencyFaultToleranceScheduledThread" #62 [28968] prio=5 os_prio=0 cpu=31.25ms elapsed=362.21s tid=0x0000023372aef440 nid=28968 waiting on condition [0x0000003923afe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e33cfa8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"Client_192.168.9.14@33832#166861769321600_GuardForSyncSend" #63 [6832] prio=5 os_prio=0 cpu=1015.62ms elapsed=362.20s tid=0x0000023372aefad0 nid=6832 waiting on condition [0x0000003923bff000]
+ java.lang.Thread.State: TIMED_WAITING (sleeping)
+ at java.lang.Thread.sleep0(java.base@21.0.9/Native Method)
+ at java.lang.Thread.sleep(java.base@21.0.9/Thread.java:509)
+ at org.apache.rocketmq.client.producer.ProduceAccumulator$GuardForSyncSendService.doWork(ProduceAccumulator.java:109)
+ at org.apache.rocketmq.client.producer.ProduceAccumulator$GuardForSyncSendService.run(ProduceAccumulator.java:84)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"Client_192.168.9.14@33832#166861769321600_GuardForAsyncSend" #64 [17916] prio=5 os_prio=0 cpu=906.25ms elapsed=362.20s tid=0x0000023372af0160 nid=17916 waiting on condition [0x0000003923cff000]
+ java.lang.Thread.State: TIMED_WAITING (sleeping)
+ at java.lang.Thread.sleep0(java.base@21.0.9/Native Method)
+ at java.lang.Thread.sleep(java.base@21.0.9/Thread.java:509)
+ at org.apache.rocketmq.client.producer.ProduceAccumulator$GuardForAsyncSendService.doWork(ProduceAccumulator.java:154)
+ at org.apache.rocketmq.client.producer.ProduceAccumulator$GuardForAsyncSendService.run(ProduceAccumulator.java:131)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-1" #65 [26964] daemon prio=5 os_prio=0 cpu=140.62ms elapsed=361.73s tid=0x0000023372af0e80 nid=26964 waiting on condition [0x00000039230fe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-2" #66 [9372] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.73s tid=0x0000023372af1510 nid=9372 waiting on condition [0x00000039231fe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-3" #67 [35544] daemon prio=5 os_prio=0 cpu=78.12ms elapsed=361.73s tid=0x0000023371823030 nid=35544 waiting on condition [0x0000003923dfe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-4" #68 [10336] daemon prio=5 os_prio=0 cpu=15.62ms elapsed=361.73s tid=0x00000233718236c0 nid=10336 waiting on condition [0x0000003923efe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-5" #69 [35160] daemon prio=5 os_prio=0 cpu=265.62ms elapsed=361.73s tid=0x0000023371822310 nid=35160 waiting on condition [0x0000003923ffe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-6" #70 [35128] daemon prio=5 os_prio=0 cpu=234.38ms elapsed=361.73s tid=0x00000233718229a0 nid=35128 waiting on condition [0x00000039240fe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-7" #71 [35860] daemon prio=5 os_prio=0 cpu=125.00ms elapsed=361.73s tid=0x00000233718243e0 nid=35860 runnable [0x00000039241f8000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.Net.poll(java.base@21.0.9/Native Method)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:191)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:201)
+ at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.9/NioSocketImpl.java:309)
+ at sun.nio.ch.NioSocketImpl.read(java.base@21.0.9/NioSocketImpl.java:346)
+ at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.9/NioSocketImpl.java:796)
+ at java.net.Socket$SocketInputStream.read(java.base@21.0.9/Socket.java:1099)
+ at com.mysql.cj.protocol.ReadAheadInputStream.fill(ReadAheadInputStream.java:107)
+ at com.mysql.cj.protocol.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:150)
+ at com.mysql.cj.protocol.ReadAheadInputStream.read(ReadAheadInputStream.java:180)
+ - locked <0x000000068e866a98> (a com.mysql.cj.protocol.ReadAheadInputStream)
+ at java.io.FilterInputStream.read(java.base@21.0.9/FilterInputStream.java:119)
+ at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:64)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeaderLocal(SimplePacketReader.java:81)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:63)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:45)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:52)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:41)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:54)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:44)
+ at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:576)
+ at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:762)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol.java:701)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:1050)
+ at com.mysql.cj.NativeSession.execSQL(NativeSession.java:660)
+ at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:889)
+ - locked <0x000000068e8652c8> (a com.mysql.cj.jdbc.ConnectionImpl)
+ at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:354)
+ - locked <0x000000068e8652c8> (a com.mysql.cj.jdbc.ConnectionImpl)
+ at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
+ at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java)
+ at java.lang.invoke.LambdaForm$DMH/0x00000233010d2c00.invokeInterface(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x00000233011f4400.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:153)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:58)
+ at jdk.proxy3.$Proxy198.execute(jdk.proxy3/Unknown Source)
+ at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:48)
+ at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:75)
+ at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
+ at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
+ at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
+ at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
+ at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301589800.invokeInterface(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301c5d800.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.LambdaForm$MH/0x00000233010c8000.invokeExact_MT(java.base@21.0.9/LambdaForm$MH)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:155)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:425)
+ at jdk.proxy2.$Proxy119.insert(jdk.proxy2/Unknown Source)
+ at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:59)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:152)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
+ at jdk.proxy2.$Proxy122.insert(jdk.proxy2/Unknown Source)
+ at com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService.run(DeleteBrandRunService.java:103)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301120c00.invokeVirtual(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301085000.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:154)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:716)
+ at com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService$$SpringCGLIB$$0.run()
+ at com.nanri.aiimage.modules.deletebrand.controller.DeleteBrandRunController.run(DeleteBrandRunController.java:54)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301120c00.invokeVirtual(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301085000.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:154)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
+ at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
+ at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
+ at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
+ at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
+ at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
+ at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
+ at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
+ at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
+ at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
+ at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
+ at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
+ at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at com.nanri.aiimage.config.RequestTraceFilter.doFilterInternal(RequestTraceFilter.java:48)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
+ at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
+ at org.springframework.security.web.FilterChainProxy$$Lambda/0x0000023301a9ade8.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479)
+ at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation$$Lambda/0x0000023301aa32d0.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation$$Lambda/0x0000023301aa34e8.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82)
+ at org.springframework.security.web.ObservationFilterChainDecorator$$Lambda/0x0000023301a9b218.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128)
+ at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
+ at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:181)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
+ at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
+ at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
+ at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter$$Lambda/0x0000023301a9cfd0.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation$$Lambda/0x0000023301a9d208.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
+ at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
+ at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
+ at org.springframework.web.servlet.handler.HandlerMappingIntrospector$$Lambda/0x0000023301915230.doFilter(Unknown Source)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
+ at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
+ at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)
+ at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362)
+ at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:113)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
+ at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
+ at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
+ at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
+ at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
+ at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
+ at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
+ at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
+ at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
+ at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
+ at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
+ at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-8" #72 [28396] daemon prio=5 os_prio=0 cpu=62.50ms elapsed=361.73s tid=0x0000023371826b40 nid=28396 runnable [0x00000039242f8000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.Net.poll(java.base@21.0.9/Native Method)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:191)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:201)
+ at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.9/NioSocketImpl.java:309)
+ at sun.nio.ch.NioSocketImpl.read(java.base@21.0.9/NioSocketImpl.java:346)
+ at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.9/NioSocketImpl.java:796)
+ at java.net.Socket$SocketInputStream.read(java.base@21.0.9/Socket.java:1099)
+ at com.mysql.cj.protocol.ReadAheadInputStream.fill(ReadAheadInputStream.java:107)
+ at com.mysql.cj.protocol.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:150)
+ at com.mysql.cj.protocol.ReadAheadInputStream.read(ReadAheadInputStream.java:180)
+ - locked <0x000000068e8aa5d0> (a com.mysql.cj.protocol.ReadAheadInputStream)
+ at java.io.FilterInputStream.read(java.base@21.0.9/FilterInputStream.java:119)
+ at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:64)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeaderLocal(SimplePacketReader.java:81)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:63)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:45)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:52)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:41)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:54)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:44)
+ at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:576)
+ at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:762)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol.java:701)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:1050)
+ at com.mysql.cj.NativeSession.execSQL(NativeSession.java:660)
+ at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:889)
+ - locked <0x000000068e8aa868> (a com.mysql.cj.jdbc.ConnectionImpl)
+ at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:354)
+ - locked <0x000000068e8aa868> (a com.mysql.cj.jdbc.ConnectionImpl)
+ at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
+ at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java)
+ at java.lang.invoke.LambdaForm$DMH/0x00000233010d2c00.invokeInterface(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x00000233011f4400.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:153)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:58)
+ at jdk.proxy3.$Proxy198.execute(jdk.proxy3/Unknown Source)
+ at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:48)
+ at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:75)
+ at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
+ at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
+ at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
+ at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
+ at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301589800.invokeInterface(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301c5dc00.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.LambdaForm$MH/0x00000233010c8000.invokeExact_MT(java.base@21.0.9/LambdaForm$MH)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:155)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:425)
+ at jdk.proxy2.$Proxy119.insert(jdk.proxy2/Unknown Source)
+ at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:59)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:152)
+ at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
+ at jdk.proxy2.$Proxy122.insert(jdk.proxy2/Unknown Source)
+ at com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService.run(DeleteBrandRunService.java:103)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301120c00.invokeVirtual(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301085000.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:154)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:716)
+ at com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService$$SpringCGLIB$$0.run()
+ at com.nanri.aiimage.modules.deletebrand.controller.DeleteBrandRunController.run(DeleteBrandRunController.java:54)
+ at java.lang.invoke.LambdaForm$DMH/0x0000023301120c00.invokeVirtual(java.base@21.0.9/LambdaForm$DMH)
+ at java.lang.invoke.LambdaForm$MH/0x0000023301085000.invoke(java.base@21.0.9/LambdaForm$MH)
+ at java.lang.invoke.Invokers$Holder.invokeExact_MT(java.base@21.0.9/Invokers$Holder)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.base@21.0.9/DirectMethodHandleAccessor.java:154)
+ at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.base@21.0.9/DirectMethodHandleAccessor.java:103)
+ at java.lang.reflect.Method.invoke(java.base@21.0.9/Method.java:580)
+ at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
+ at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
+ at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
+ at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
+ at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
+ at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
+ at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
+ at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
+ at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
+ at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
+ at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
+ at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
+ at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at com.nanri.aiimage.config.RequestTraceFilter.doFilterInternal(RequestTraceFilter.java:48)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
+ at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
+ at org.springframework.security.web.FilterChainProxy$$Lambda/0x0000023301a9ade8.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479)
+ at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation$$Lambda/0x0000023301aa32d0.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation$$Lambda/0x0000023301aa34e8.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82)
+ at org.springframework.security.web.ObservationFilterChainDecorator$$Lambda/0x0000023301a9b218.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128)
+ at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
+ at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:181)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
+ at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
+ at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
+ at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter$$Lambda/0x0000023301a9cfd0.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323)
+ at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation$$Lambda/0x0000023301a9d208.doFilter(Unknown Source)
+ at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224)
+ at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)
+ at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
+ at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
+ at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
+ at org.springframework.web.servlet.handler.HandlerMappingIntrospector$$Lambda/0x0000023301915230.doFilter(Unknown Source)
+ at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
+ at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
+ at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)
+ at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362)
+ at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:113)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
+ at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
+ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
+ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
+ at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
+ at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
+ at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
+ at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
+ at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
+ at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
+ at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
+ at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
+ at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
+ at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
+ at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
+ at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-9" #73 [31968] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.73s tid=0x0000023371825100 nid=31968 waiting on condition [0x00000039243fe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-exec-10" #74 [14256] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.73s tid=0x0000023371823d50 nid=14256 waiting on condition [0x00000039244ff000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e86b6b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.LinkedBlockingQueue.take(java.base@21.0.9/LinkedBlockingQueue.java:435)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
+ at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
+ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
+ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-Poller" #75 [25928] daemon prio=5 os_prio=0 cpu=31.25ms elapsed=361.72s tid=0x0000023371824a70 nid=25928 runnable [0x00000039245fe000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.WEPoll.wait(java.base@21.0.9/Native Method)
+ at sun.nio.ch.WEPollSelectorImpl.doSelect(java.base@21.0.9/WEPollSelectorImpl.java:114)
+ at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@21.0.9/SelectorImpl.java:130)
+ - locked <0x000000068e8ad8d0> (a sun.nio.ch.Util$2)
+ - locked <0x000000068e8ad870> (a sun.nio.ch.WEPollSelectorImpl)
+ at sun.nio.ch.SelectorImpl.select(java.base@21.0.9/SelectorImpl.java:142)
+ at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:757)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"http-nio-18080-Acceptor" #76 [14276] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.72s tid=0x0000023371828c10 nid=14276 runnable [0x00000039246ff000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.Net.accept(java.base@21.0.9/Native Method)
+ at sun.nio.ch.ServerSocketChannelImpl.implAccept(java.base@21.0.9/ServerSocketChannelImpl.java:433)
+ at sun.nio.ch.ServerSocketChannelImpl.accept(java.base@21.0.9/ServerSocketChannelImpl.java:399)
+ at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:518)
+ at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:71)
+ at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:128)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"aiimage-scheduling-1" #77 [12476] prio=5 os_prio=0 cpu=0.00ms elapsed=361.70s tid=0x00000233718271d0 nid=12476 waiting on condition [0x00000039247ff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e344418> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"aiimage-scheduling-2" #78 [34996] prio=5 os_prio=0 cpu=109.38ms elapsed=361.70s tid=0x00000233718264b0 nid=34996 waiting on condition [0x00000039248fe000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e344418> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1177)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"aiimage-scheduling-3" #79 [31044] prio=5 os_prio=0 cpu=234.38ms elapsed=361.70s tid=0x0000023371828580 nid=31044 waiting on condition [0x00000039249fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e344418> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"aiimage-scheduling-4" #80 [26972] prio=5 os_prio=0 cpu=46.88ms elapsed=361.70s tid=0x0000023371827860 nid=26972 waiting on condition [0x0000003924aff000]
+ java.lang.Thread.State: WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e344418> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
+ at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3778)
+ at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3723)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1712)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1177)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"ForkJoinPool.commonPool-worker-1" #81 [28900] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.60s tid=0x0000023371827ef0 nid=28900 runnable [0x0000003924bfe000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.Net.poll(java.base@21.0.9/Native Method)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:191)
+ at sun.nio.ch.NioSocketImpl.park(java.base@21.0.9/NioSocketImpl.java:201)
+ at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.9/NioSocketImpl.java:309)
+ at sun.nio.ch.NioSocketImpl.read(java.base@21.0.9/NioSocketImpl.java:346)
+ at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.9/NioSocketImpl.java:796)
+ at java.net.Socket$SocketInputStream.read(java.base@21.0.9/Socket.java:1099)
+ at com.mysql.cj.protocol.ReadAheadInputStream.fill(ReadAheadInputStream.java:107)
+ at com.mysql.cj.protocol.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:150)
+ at com.mysql.cj.protocol.ReadAheadInputStream.read(ReadAheadInputStream.java:180)
+ - locked <0x000000068e876e48> (a com.mysql.cj.protocol.ReadAheadInputStream)
+ at java.io.FilterInputStream.read(java.base@21.0.9/FilterInputStream.java:119)
+ at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:64)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeaderLocal(SimplePacketReader.java:81)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:63)
+ at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:45)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:52)
+ at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:41)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:54)
+ at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:44)
+ at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:576)
+ at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:762)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol.java:701)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:1050)
+ at com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol.java:997)
+ at com.mysql.cj.NativeSession.execSQL(NativeSession.java:658)
+ at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:730)
+ - locked <0x000000068e877800> (a com.mysql.cj.jdbc.ConnectionImpl)
+ at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:653)
+ at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94)
+ at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
+ at org.springframework.jdbc.core.JdbcTemplate$1ExecuteStatementCallback.doInStatement(JdbcTemplate.java:435)
+ at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:393)
+ at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:444)
+ at com.nanri.aiimage.modules.permission.service.PermissionMenuSchemaInitializer.executeQuietly(PermissionMenuSchemaInitializer.java:108)
+ at com.nanri.aiimage.modules.permission.service.PermissionMenuSchemaInitializer.initializeInternal(PermissionMenuSchemaInitializer.java:44)
+ at com.nanri.aiimage.modules.permission.service.PermissionMenuSchemaInitializer$$Lambda/0x000002330196b410.run(Unknown Source)
+ at java.util.concurrent.CompletableFuture$AsyncRun.run(java.base@21.0.9/CompletableFuture.java:1804)
+ at java.util.concurrent.CompletableFuture$AsyncRun.exec(java.base@21.0.9/CompletableFuture.java:1796)
+ at java.util.concurrent.ForkJoinTask.doExec(java.base@21.0.9/ForkJoinTask.java:387)
+ at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(java.base@21.0.9/ForkJoinPool.java:1310)
+ at java.util.concurrent.ForkJoinPool.scan(java.base@21.0.9/ForkJoinPool.java:1841)
+ at java.util.concurrent.ForkJoinPool.runWorker(java.base@21.0.9/ForkJoinPool.java:1806)
+ at java.util.concurrent.ForkJoinWorkerThread.run(java.base@21.0.9/ForkJoinWorkerThread.java:188)
+
+"lettuce-nioEventLoop-4-1" #82 [35608] daemon prio=5 os_prio=0 cpu=109.38ms elapsed=361.32s tid=0x0000023371825e20 nid=35608 runnable [0x0000003924dff000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.WEPoll.wait(java.base@21.0.9/Native Method)
+ at sun.nio.ch.WEPollSelectorImpl.doSelect(java.base@21.0.9/WEPollSelectorImpl.java:114)
+ at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@21.0.9/SelectorImpl.java:130)
+ - locked <0x000000068e890398> (a io.netty.channel.nio.SelectedSelectionKeySet)
+ - locked <0x000000068e890338> (a sun.nio.ch.WEPollSelectorImpl)
+ at sun.nio.ch.SelectorImpl.select(java.base@21.0.9/SelectorImpl.java:147)
+ at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
+ at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:887)
+ at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:526)
+ at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
+ at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
+ at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"HikariPool-1 housekeeper" #83 [34564] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=361.06s tid=0x000002336e8db070 nid=34564 waiting on condition [0x00000039227ff000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e881800> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"DestroyJavaVM" #85 [35408] prio=5 os_prio=0 cpu=7171.88ms elapsed=361.05s tid=0x000002336e8dc420 nid=35408 waiting on condition [0x0000000000000000]
+ java.lang.Thread.State: RUNNABLE
+
+"boundedElastic-evictor-1" #86 [20552] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=359.77s tid=0x000002336e8dd7d0 nid=20552 waiting on condition [0x00000039216fe000]
+ java.lang.Thread.State: TIMED_WAITING (parking)
+ at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)
+ - parking to wait for <0x000000068e8978b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
+ at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.9/LockSupport.java:269)
+ at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.9/AbstractQueuedSynchronizer.java:1763)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:1182)
+ at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.9/ScheduledThreadPoolExecutor.java:899)
+ at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.9/ThreadPoolExecutor.java:1070)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.9/ThreadPoolExecutor.java:1130)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.9/ThreadPoolExecutor.java:642)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"lettuce-nioEventLoop-4-2" #88 [26732] daemon prio=5 os_prio=0 cpu=15.62ms elapsed=359.76s tid=0x000002336e8dbd90 nid=26732 runnable [0x00000039221fe000]
+ java.lang.Thread.State: RUNNABLE
+ at sun.nio.ch.WEPoll.wait(java.base@21.0.9/Native Method)
+ at sun.nio.ch.WEPollSelectorImpl.doSelect(java.base@21.0.9/WEPollSelectorImpl.java:114)
+ at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@21.0.9/SelectorImpl.java:130)
+ - locked <0x000000068e89eed0> (a io.netty.channel.nio.SelectedSelectionKeySet)
+ - locked <0x000000068e89ee70> (a sun.nio.ch.WEPollSelectorImpl)
+ at sun.nio.ch.SelectorImpl.select(java.base@21.0.9/SelectorImpl.java:147)
+ at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
+ at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:887)
+ at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:526)
+ at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
+ at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
+ at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
+ at java.lang.Thread.runWith(java.base@21.0.9/Thread.java:1596)
+ at java.lang.Thread.run(java.base@21.0.9/Thread.java:1583)
+
+"G1 Conc#2" os_prio=2 cpu=62.50ms elapsed=371.46s tid=0x000002336d0b9c50 nid=13476 runnable
+
+"G1 Conc#1" os_prio=2 cpu=78.12ms elapsed=371.46s tid=0x000002336d0ba000 nid=28596 runnable
+
+"GC Thread#12" os_prio=2 cpu=31.25ms elapsed=371.73s tid=0x000002336c9d3da0 nid=32248 runnable
+
+"GC Thread#11" os_prio=2 cpu=31.25ms elapsed=371.73s tid=0x000002336ccfd490 nid=29844 runnable
+
+"GC Thread#10" os_prio=2 cpu=46.88ms elapsed=371.73s tid=0x000002336ccfd0e0 nid=30276 runnable
+
+"GC Thread#9" os_prio=2 cpu=31.25ms elapsed=371.73s tid=0x000002336d9ca340 nid=27712 runnable
+
+"GC Thread#8" os_prio=2 cpu=31.25ms elapsed=372.68s tid=0x000002336c0e1ed0 nid=34016 runnable
+
+"GC Thread#7" os_prio=2 cpu=31.25ms elapsed=372.68s tid=0x000002336c0e1b20 nid=31484 runnable
+
+"GC Thread#6" os_prio=2 cpu=46.88ms elapsed=372.68s tid=0x000002336c2db7d0 nid=28232 runnable
+
+"GC Thread#5" os_prio=2 cpu=31.25ms elapsed=372.68s tid=0x000002336c2dac10 nid=5780 runnable
+
+"GC Thread#4" os_prio=2 cpu=46.88ms elapsed=372.68s tid=0x000002336c219050 nid=10792 runnable
+
+"GC Thread#3" os_prio=2 cpu=46.88ms elapsed=372.68s tid=0x000002336c218ca0 nid=33432 runnable
+
+"GC Thread#2" os_prio=2 cpu=62.50ms elapsed=372.68s tid=0x000002336c2188f0 nid=29088 runnable
+
+"GC Thread#1" os_prio=2 cpu=31.25ms elapsed=372.68s tid=0x000002336bd51490 nid=33064 runnable
+
+"VM Thread" os_prio=2 cpu=15.62ms elapsed=373.00s tid=0x00000233662e8820 nid=22704 runnable
+
+"VM Periodic Task Thread" os_prio=2 cpu=140.62ms elapsed=373.00s tid=0x00000233662d1660 nid=28572 waiting on condition
+
+"G1 Service" os_prio=2 cpu=0.00ms elapsed=373.01s tid=0x00000233661a71d0 nid=30960 runnable
+
+"G1 Refine#0" os_prio=2 cpu=109.38ms elapsed=373.01s tid=0x000002334306e790 nid=35932 runnable
+
+"G1 Conc#0" os_prio=2 cpu=78.12ms elapsed=373.01s tid=0x0000023343042d40 nid=13768 runnable
+
+"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=373.01s tid=0x0000023343042320 nid=31632 runnable
+
+"GC Thread#0" os_prio=2 cpu=46.88ms elapsed=373.01s tid=0x0000023343034b90 nid=36916 runnable
+
+JNI global refs: 43, weak refs: 0
+
diff --git a/backend-java/pom.xml b/backend-java/pom.xml
index 71e4523..2fab57a 100644
--- a/backend-java/pom.xml
+++ b/backend-java/pom.xml
@@ -23,6 +23,8 @@
4.0.3
3.17.4
5.8.36
+ 2.3.5
+ 8.5.17
@@ -81,6 +83,16 @@
hutool-all
${hutool.version}
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+ ${rocketmq-spring.version}
+
+
+ io.minio
+ minio
+ ${minio.version}
+
org.projectlombok
lombok
diff --git a/backend-java/src/main/java/com/nanri/aiimage/common/exception/GlobalExceptionHandler.java b/backend-java/src/main/java/com/nanri/aiimage/common/exception/GlobalExceptionHandler.java
index 73d65c6..ae7caf4 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/common/exception/GlobalExceptionHandler.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/common/exception/GlobalExceptionHandler.java
@@ -13,6 +13,9 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse handleBusinessException(BusinessException ex) {
+ if (Integer.valueOf(40901).equals(ex.getCode())) {
+ return ApiResponse.success("任务已结束,忽略重复提交", null);
+ }
return ex.getCode() == null
? ApiResponse.fail(ex.getMessage())
: ApiResponse.fail(ex.getCode(), ex.getMessage());
diff --git a/backend-java/src/main/java/com/nanri/aiimage/common/service/DistributedJobLockService.java b/backend-java/src/main/java/com/nanri/aiimage/common/service/DistributedJobLockService.java
index 5ae7736..40f1772 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/common/service/DistributedJobLockService.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/common/service/DistributedJobLockService.java
@@ -36,7 +36,13 @@ public class DistributedJobLockService {
Duration actualTtl = (ttl == null || ttl.isNegative() || ttl.isZero()) ? Duration.ofMinutes(10) : ttl;
String key = buildLockKey(jobName);
String token = ownerPrefix + ":" + UUID.randomUUID();
- Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(key, token, actualTtl);
+ Boolean locked;
+ try {
+ locked = stringRedisTemplate.opsForValue().setIfAbsent(key, token, actualTtl);
+ } catch (Exception ex) {
+ log.warn("[job-lock] acquire skipped jobName={} key={} msg={}", jobName, key, ex.getMessage());
+ return null;
+ }
if (!Boolean.TRUE.equals(locked)) {
return null;
}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java b/backend-java/src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java
new file mode 100644
index 0000000..b75b930
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java
@@ -0,0 +1,16 @@
+package com.nanri.aiimage.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "aiimage.appearance-patent")
+public class AppearancePatentProperties {
+ private String cozeBaseUrl = "https://api.coze.cn";
+ private String cozeWorkflowPath = "/v1/workflow/run";
+ private String cozeWorkflowId = "7632683471312355338";
+ private String cozeToken = "";
+ private int cozeBatchSize = 10;
+ private int staleTimeoutMinutes = 20;
+ private String staleFinalizeCron = "0 */2 * * * *";
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/PropertiesConfig.java b/backend-java/src/main/java/com/nanri/aiimage/config/PropertiesConfig.java
index 6a48a49..5d1e0d5 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/config/PropertiesConfig.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/config/PropertiesConfig.java
@@ -4,6 +4,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration;
@Configuration
-@EnableConfigurationProperties({OssProperties.class, StorageProperties.class, BrandProgressProperties.class, DeleteBrandProgressProperties.class, ZiniaoProperties.class, ModuleCleanupProperties.class, TaskPressureProperties.class})
+@EnableConfigurationProperties({OssProperties.class, TransientStorageProperties.class, StorageProperties.class, BrandProgressProperties.class, DeleteBrandProgressProperties.class, ZiniaoProperties.class, ModuleCleanupProperties.class, TaskPressureProperties.class, AppearancePatentProperties.class})
public class PropertiesConfig {
}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/TaskFileJobConfig.java b/backend-java/src/main/java/com/nanri/aiimage/config/TaskFileJobConfig.java
new file mode 100644
index 0000000..6cee12c
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/config/TaskFileJobConfig.java
@@ -0,0 +1,27 @@
+package com.nanri.aiimage.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+@Configuration
+public class TaskFileJobConfig {
+
+ @Bean("taskFileJobDispatchExecutor")
+ public TaskExecutor taskFileJobDispatchExecutor(
+ @Value("${aiimage.result-file-job.local-dispatch-pool-size:2}") int poolSize,
+ @Value("${aiimage.result-file-job.local-dispatch-queue-capacity:200}") int queueCapacity) {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ int normalizedPoolSize = Math.max(1, poolSize);
+ executor.setCorePoolSize(normalizedPoolSize);
+ executor.setMaxPoolSize(normalizedPoolSize);
+ executor.setQueueCapacity(Math.max(10, queueCapacity));
+ executor.setThreadNamePrefix("task-file-job-dispatch-");
+ executor.setWaitForTasksToCompleteOnShutdown(true);
+ executor.setAwaitTerminationSeconds(30);
+ executor.initialize();
+ return executor;
+ }
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java b/backend-java/src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java
new file mode 100644
index 0000000..8458f3e
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java
@@ -0,0 +1,15 @@
+package com.nanri.aiimage.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "aiimage.transient-storage")
+public class TransientStorageProperties {
+ private boolean enabled = false;
+ private String endpoint;
+ private String bucket;
+ private String accessKeyId;
+ private String accessKeySecret;
+ private String region = "us-east-1";
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java
new file mode 100644
index 0000000..e1de001
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java
@@ -0,0 +1,156 @@
+package com.nanri.aiimage.modules.appearancepatent.client;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nanri.aiimage.config.AppearancePatentProperties;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentResultRowDto;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class AppearancePatentCozeClient {
+
+ private final AppearancePatentProperties properties;
+ private final ObjectMapper objectMapper;
+
+ public List inspect(List rows, String prompt) {
+ if (rows == null || rows.isEmpty()) {
+ return List.of();
+ }
+ if (properties.getCozeToken() == null || properties.getCozeToken().isBlank()) {
+ log.warn("[appearance-patent] coze token not configured, keep raw rows size={}", rows.size());
+ return rows.stream().map(this::copy).toList();
+ }
+ try {
+ String raw = postWorkflow(rows, prompt);
+ List results = parseResults(raw);
+ List merged = new ArrayList<>();
+ for (int i = 0; i < rows.size(); i++) {
+ AppearancePatentResultRowDto row = copy(rows.get(i));
+ if (i < results.size()) {
+ CozeResult result = results.get(i);
+ row.setTitleRisk(result.title());
+ row.setAppearanceRisk(result.appearance());
+ row.setPatentRisk(result.patent());
+ row.setConclusion(result.result());
+ }
+ merged.add(row);
+ }
+ return merged;
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] coze batch failed size={} err={}", rows.size(), ex.getMessage());
+ return rows.stream().map(this::copy).toList();
+ }
+ }
+
+ private String postWorkflow(List rows, String prompt) {
+ Map parameters = new LinkedHashMap<>();
+ parameters.put("title_list", rows.stream().map(row -> nonBlank(row.getTitle(), row.getAsin())).toList());
+ parameters.put("url_list", rows.stream().map(row -> nonBlank(row.getUrl(), "")).toList());
+ parameters.put("prompt", prompt == null ? "" : prompt);
+
+ Map body = new LinkedHashMap<>();
+ body.put("workflow_id", properties.getCozeWorkflowId());
+ body.put("parameters", parameters);
+
+ RestClient.RequestBodySpec request = restClient().post()
+ .uri(joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()))
+ .headers(headers -> {
+ headers.setBearerAuth(stripBearer(properties.getCozeToken()));
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ });
+ request.body(body);
+ return request.retrieve().body(String.class);
+ }
+
+ private List parseResults(String raw) throws Exception {
+ JsonNode root = objectMapper.readTree(raw);
+ if (root.path("code").asInt(-1) != 0) {
+ throw new IllegalStateException(root.path("msg").asText("Coze response code is not 0"));
+ }
+ String dataText = root.path("data").asText("");
+ if (dataText.isBlank()) {
+ return List.of();
+ }
+ JsonNode dataRoot = objectMapper.readTree(dataText);
+ JsonNode array = dataRoot.path("data");
+ List results = new ArrayList<>();
+ if (array.isArray()) {
+ for (JsonNode node : array) {
+ results.add(new CozeResult(
+ text(node.get("title")),
+ text(node.get("appearance")),
+ text(firstNonNull(node.get("patent"), node.get("patent "))),
+ text(node.get("result"))
+ ));
+ }
+ }
+ return results;
+ }
+
+ private RestClient restClient() {
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+ requestFactory.setConnectTimeout(10000);
+ requestFactory.setReadTimeout(60000);
+ return RestClient.builder().requestFactory(requestFactory).build();
+ }
+
+ private AppearancePatentResultRowDto copy(AppearancePatentResultRowDto source) {
+ AppearancePatentResultRowDto row = new AppearancePatentResultRowDto();
+ row.setId(source.getId());
+ row.setAsin(source.getAsin());
+ row.setCountry(source.getCountry());
+ row.setUrl(source.getUrl());
+ row.setTitle(source.getTitle());
+ row.setError(source.getError());
+ row.setDone(source.getDone());
+ row.setTitleRisk(source.getTitleRisk());
+ row.setAppearanceRisk(source.getAppearanceRisk());
+ row.setPatentRisk(source.getPatentRisk());
+ row.setConclusion(source.getConclusion());
+ return row;
+ }
+
+ private JsonNode firstNonNull(JsonNode left, JsonNode right) {
+ return left == null || left.isNull() ? right : left;
+ }
+
+ private String text(JsonNode node) {
+ return node == null || node.isNull() ? null : node.asText();
+ }
+
+ private String nonBlank(String value, String fallback) {
+ return value == null || value.isBlank() ? fallback : value;
+ }
+
+ private String stripBearer(String token) {
+ String normalized = token == null ? "" : token.trim();
+ return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7).trim() : normalized;
+ }
+
+ private String joinUrl(String baseUrl, String path) {
+ String base = baseUrl == null ? "" : baseUrl.trim();
+ String suffix = path == null ? "" : path.trim();
+ if (base.endsWith("/") && suffix.startsWith("/")) {
+ return base + suffix.substring(1);
+ }
+ if (!base.endsWith("/") && !suffix.startsWith("/")) {
+ return base + "/" + suffix;
+ }
+ return base + suffix;
+ }
+
+ private record CozeResult(String title, String appearance, String patent, String result) {
+ }
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java
new file mode 100644
index 0000000..8a7157a
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java
@@ -0,0 +1,143 @@
+package com.nanri.aiimage.modules.appearancepatent.controller;
+
+import com.nanri.aiimage.common.api.ApiResponse;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentParseRequest;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentSubmitResultRequest;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentTaskBatchRequest;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentDashboardVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentHistoryVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentParseVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentTaskBatchVo;
+import com.nanri.aiimage.modules.appearancepatent.service.AppearancePatentTaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/appearance-patent")
+@Tag(name = "外观专利检测", description = "外观专利检测任务接口。前端上传 Excel 后由 Java 解析并创建任务;Python 回传商品数据;Java 负责攒批调用 Coze、补齐子行、生成最终 xlsx 并上传 OSS。")
+public class AppearancePatentController {
+
+ private final AppearancePatentTaskService service;
+
+ @PostMapping("/parse")
+ @Operation(summary = "解析 Excel 并创建任务", description = "解析上传后的 Excel 文件,提取 id、ASIN、国家、URL、标题等字段。返回给前端的数据只包含整数 id 和 n_1 行;n_2、n_3 等子行会保存在 OSS 解析载荷中,用于最终结果补齐。创建后的任务状态为 PENDING,不会自动推送 Python。")
+ public ApiResponse parse(@Valid @RequestBody AppearancePatentParseRequest request) {
+ return ApiResponse.success(service.parseAndCreateTask(request));
+ }
+
+ @GetMapping("/dashboard")
+ @Operation(summary = "查询外观专利检测总览", description = "查询当前用户的运行中、成功、失败和已结束任务数量。页面进入时请求一次即可,不需要持续轮询。")
+ public ApiResponse dashboard(
+ @Parameter(description = "当前用户 ID,用于隔离不同用户的任务和历史记录。", required = true, example = "1")
+ @RequestParam("user_id") Long userId) {
+ return ApiResponse.success(service.dashboard(userId));
+ }
+
+ @GetMapping("/history")
+ @Operation(summary = "查询外观专利检测历史", description = "查询当前用户最近的外观专利检测历史记录,包含源文件名、任务状态、行数、错误信息和最终 xlsx 下载地址。")
+ public ApiResponse history(
+ @Parameter(description = "当前用户 ID,用于查询该用户自己的历史记录。", required = true, example = "1")
+ @RequestParam("user_id") Long userId) {
+ return ApiResponse.success(service.history(userId));
+ }
+
+ @PostMapping("/tasks/progress/batch")
+ @Operation(summary = "批量查询任务进度", description = "前端只对活跃任务调用该接口,建议 6 秒一次。接口只返回轻量任务状态,不返回明细结果。")
+ public ApiResponse progress(@Valid @RequestBody AppearancePatentTaskBatchRequest request) {
+ return ApiResponse.success(service.progressBatch(request.getTaskIds()));
+ }
+
+ @PostMapping("/tasks/{taskId}/activate")
+ @Operation(summary = "激活任务", description = "前端手动推送 Python 队列成功后调用,将任务从 PENDING 改为 RUNNING,并记录后端内部活跃时间。后续活跃时间由 Python 回传结果接口自动刷新,不需要单独心跳接口。")
+ public ApiResponse activate(
+ @Parameter(description = "外观专利检测任务 ID,即解析接口返回的 taskId。", required = true, example = "3938")
+ @PathVariable Long taskId,
+ @Parameter(description = "当前用户 ID,必须与创建任务的用户一致。", required = true, example = "1")
+ @RequestParam("user_id") Long userId) {
+ service.activateTask(taskId, userId);
+ return ApiResponse.success(null);
+ }
+
+ @PostMapping("/tasks/{taskId}/result")
+ @Operation(summary = "提交 Python 回传结果", description = "Python 回传商品数据接口。items 可以是一条或多条;Java 先原样保存回传数据,再内部攒够 10 条调用 Coze。done=true 表示 Python 已完成全部回传,Java 会强制处理剩余不足 10 条的数据并生成最终 xlsx。")
+ public ApiResponse result(
+ @Parameter(description = "外观专利检测任务 ID。任务必须处于 RUNNING 状态。", required = true, example = "3938")
+ @PathVariable Long taskId,
+ @Valid @RequestBody AppearancePatentSubmitResultRequest request) {
+ service.submitResult(taskId, request);
+ return ApiResponse.success(null);
+ }
+
+ @DeleteMapping("/tasks/{taskId}")
+ @Operation(summary = "删除任务", description = "删除当前用户的一条外观专利检测任务,同时清理任务结果、scope 状态和分片记录。")
+ public ApiResponse deleteTask(
+ @Parameter(description = "外观专利检测任务 ID。", required = true, example = "3938")
+ @PathVariable Long taskId,
+ @Parameter(description = "当前用户 ID,必须与创建任务的用户一致。", required = true, example = "1")
+ @RequestParam("user_id") Long userId) {
+ service.deleteTask(taskId, userId);
+ return ApiResponse.success(null);
+ }
+
+ @DeleteMapping("/history/{resultId}")
+ @Operation(summary = "删除历史记录", description = "删除当前用户的一条外观专利检测历史记录。只删除 biz_file_result 记录,不主动删除任务主记录。")
+ public ApiResponse deleteHistory(
+ @Parameter(description = "历史结果记录 ID,即 history 接口返回的 resultId。", required = true, example = "1001")
+ @PathVariable Long resultId,
+ @Parameter(description = "当前用户 ID。", required = true, example = "1")
+ @RequestParam("user_id") Long userId) {
+ service.deleteHistory(resultId, userId);
+ return ApiResponse.success(null);
+ }
+
+ @GetMapping("/results/{resultId}/download")
+ @Operation(summary = "下载外观专利检测结果文件")
+ public void downloadResult(
+ @Parameter(description = "结果记录 ID", required = true, example = "1001")
+ @PathVariable Long resultId,
+ @Parameter(description = "当前用户 ID", required = true, example = "1")
+ @RequestParam("user_id") Long userId,
+ jakarta.servlet.http.HttpServletResponse response) {
+ String url = service.resolveResultDownloadUrl(resultId, userId);
+ String filename = service.resolveResultDownloadFilename(resultId, userId);
+ if (url == null || url.isBlank()) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "暂无可下载结果");
+ }
+ try {
+ String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
+ response.setContentType("application/octet-stream");
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
+ "attachment; filename=\"" + filename + "\"; filename*=UTF-8''" + encodedFilename);
+ try (InputStream in = URI.create(url).toURL().openStream()) {
+ byte[] buffer = new byte[65536];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ response.getOutputStream().write(buffer, 0, read);
+ }
+ response.getOutputStream().flush();
+ }
+ } catch (Exception ex) {
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "下载失败");
+ }
+ }
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParseRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParseRequest.java
new file mode 100644
index 0000000..30d2a25
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParseRequest.java
@@ -0,0 +1,28 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测解析请求")
+public class AppearancePatentParseRequest {
+ @JsonProperty("user_id")
+ @NotNull
+ @Schema(description = "当前用户 ID。后端会把创建的任务、历史记录和结果文件归属到该用户。", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Long userId;
+
+ @NotEmpty
+ @Schema(description = "已上传的 Excel 文件列表。当前外观专利检测只读取第一个文件;文件对象来自统一上传接口返回值。", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List files;
+
+ @JsonProperty("ai_prompt")
+ @JsonAlias({"aiPrompt", "prompt"})
+ @Schema(description = "AI 提示词。非必填;为空时前端会使用默认提示词。后端会保存该提示词,并在调用 Coze workflow 时作为 prompt 参数传入。", example = "请排查这些亚马逊商品在英国及欧洲地区是否存在知识产权侵权风险。")
+ private String aiPrompt;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParsedPayloadDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParsedPayloadDto.java
new file mode 100644
index 0000000..b231d94
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentParsedPayloadDto.java
@@ -0,0 +1,21 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentParsedRowVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测解析载荷。通常保存到 OSS,数据库只保存 oss 指针。")
+public class AppearancePatentParsedPayloadDto {
+ @Schema(description = "AI 提示词。")
+ private String aiPrompt;
+ @Schema(description = "Excel 原始表头列表。")
+ private List headers = new ArrayList<>();
+ @Schema(description = "返回前端和推给 Python 的代表行,只包含整数 id 和 n_1 行。")
+ private List items = new ArrayList<>();
+ @Schema(description = "完整有效行,包含 n_2、n_3 等解析后被前端过滤但最终需要补回的子行。")
+ private List allItems = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentResultRowDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentResultRowDto.java
new file mode 100644
index 0000000..9be1d36
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentResultRowDto.java
@@ -0,0 +1,33 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "外观专利检测单行商品数据")
+public class AppearancePatentResultRowDto {
+ @Schema(description = "Excel 中的 id。代表行通常是整数 id 或 n_1,例如 2_1;最终生成 xlsx 时,2_2、2_3 会复用同组 2_1 的 Coze 检测结果。", example = "2_1")
+ private String id;
+ @Schema(description = "亚马逊 ASIN。后端会统一按大写处理和匹配。", example = "B0CJ8SNXXV")
+ private String asin;
+ @Schema(description = "站点或国家。来自 Excel 的国家列,例如英国、德国、法国。", example = "英国")
+ private String country;
+ @Schema(description = "商品主图或待检测图片 URL。Java 调用 Coze 时会放入 url_list。", example = "https://webstatic.aiproxy.vip/output/20260425/103322/demo.jpg")
+ private String url;
+ @Schema(description = "商品标题。Java 调用 Coze 时会放入 title_list;为空时会回退使用 ASIN。", example = "Women Floral Dress Summer Casual")
+ private String title;
+ @Schema(description = "单行错误信息。通常用于记录 Python 单行处理异常;Coze 失败时后端会尽量保留原始行,不强行写入风险结果。", example = "图片地址为空")
+ private String error;
+ @Schema(description = "单行完成标记。当前主要使用请求体顶层 done 控制任务收尾,该字段仅作兼容。", example = "true")
+ private Boolean done;
+ @Schema(description = "Java 调用 Coze 后生成的标题维度检测结果,对应最终 xlsx 的“标题维度(商标)”列。Python 回传请求不要传该字段;即使传入,后端也会以 Java/Coze 处理结果为准。", example = "标题未发现明显商标侵权风险。", accessMode = Schema.AccessMode.READ_ONLY)
+ private String titleRisk;
+ @Schema(description = "Java 调用 Coze 后生成的外观维度检测结果,对应最终 xlsx 的“外观维度(外观设计专利)”列。Python 回传请求不要传该字段。", example = "未发现明显外观设计专利侵权风险。", accessMode = Schema.AccessMode.READ_ONLY)
+ private String appearanceRisk;
+ @JsonAlias({"patent ", "patent"})
+ @Schema(description = "Java 调用 Coze 后生成的专利维度检测结果,对应最终 xlsx 的“专利维度(发明/实用新型专利)”列。兼容 Coze 返回字段 patent 和 patent 后带空格的情况;Python 回传请求不要传该字段。", example = "未发现明显发明或实用新型专利侵权风险。", accessMode = Schema.AccessMode.READ_ONLY)
+ private String patentRisk;
+ @Schema(description = "Java 调用 Coze 后生成的最终结论,对应最终 xlsx 的“结论”列。Python 回传请求不要传该字段。", example = "未发现明显侵权风险", accessMode = Schema.AccessMode.READ_ONLY)
+ private String conclusion;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSourceFileDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSourceFileDto.java
new file mode 100644
index 0000000..8a92056
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSourceFileDto.java
@@ -0,0 +1,15 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "外观专利检测源文件信息")
+public class AppearancePatentSourceFileDto {
+ @Schema(description = "上传接口返回的临时文件 key。后端根据该 key 查找本地临时 Excel 文件并解析。", example = "uploads/20260426/appearance_patent_17.xlsx", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String fileKey;
+ @Schema(description = "原始文件名。用于历史记录展示和最终结果文件命名。", example = "17.xlsx")
+ private String originalFilename;
+ @Schema(description = "相对目录路径。当前仅记录来源,外观专利检测不依赖该字段处理。", example = "xlsx/17.xlsx")
+ private String relativePath;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSubmitResultRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSubmitResultRequest.java
new file mode 100644
index 0000000..57685cc
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentSubmitResultRequest.java
@@ -0,0 +1,24 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "Python 回传外观专利检测结果请求")
+public class AppearancePatentSubmitResultRequest {
+ @Schema(description = "本次 Python 回传的提交批次标识。建议同一个任务固定使用同一个值,例如 appearance-patent-{taskId};后端会结合该值和 chunkIndex 做分片幂等。", example = "appearance-patent-3938")
+ private String submissionId;
+ @Schema(description = "当前回传分片序号。建议从 1 开始递增;同一个 submissionId 下相同 chunkIndex 重复提交会被后端识别为重复分片并跳过重复处理。", example = "1")
+ private Integer chunkIndex;
+ @Schema(description = "本任务预计总分片数。如果 Python 是一条一条回传,可设置为总商品数;如果无法预估,可传 0 或 1,最终以 done=true 触发收尾。", example = "458")
+ private Integer chunkTotal;
+ @Schema(description = "是否为最后一次回传。true 表示 Python 已完成该任务全部数据回传;Java 会处理剩余不足 10 条的数据、组装最终 xlsx、上传 OSS 并收尾任务。", example = "false")
+ private Boolean done;
+ @Schema(description = "Python 侧任务级错误信息。非空时 Java 会记录错误并按失败任务收尾;普通单行 Coze 失败不建议写这里。", example = "浏览器执行异常,任务提前结束")
+ private String error;
+ @Schema(description = "本次回传的商品原始数据列表。可以一条一条传,也可以一次多条传;Python 只需要传 id、asin、country、url、title、error、done 等原始/执行字段。titleRisk、appearanceRisk、patentRisk、conclusion 由 Java 攒够 10 条调用 Coze 后生成,Python 不要传。")
+ private List items = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentTaskBatchRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentTaskBatchRequest.java
new file mode 100644
index 0000000..799b1f8
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/dto/AppearancePatentTaskBatchRequest.java
@@ -0,0 +1,15 @@
+package com.nanri.aiimage.modules.appearancepatent.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测批量进度查询请求")
+public class AppearancePatentTaskBatchRequest {
+ @NotEmpty
+ @Schema(description = "需要查询进度的任务 ID 列表。前端只传正在轮询的活跃任务;后端会批量查询,避免每个任务单独请求。", example = "[3938,3939]", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List taskIds;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentDashboardVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentDashboardVo.java
new file mode 100644
index 0000000..c8c2254
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentDashboardVo.java
@@ -0,0 +1,17 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "外观专利检测总览统计")
+public class AppearancePatentDashboardVo {
+ @Schema(description = "运行中任务数量。这里统计 RUNNING 状态任务。", example = "1")
+ private Long pendingTaskCount;
+ @Schema(description = "已结束任务数量,等于成功任务数加失败任务数。", example = "12")
+ private Long processedTaskCount;
+ @Schema(description = "成功任务数量。", example = "10")
+ private Long successTaskCount;
+ @Schema(description = "失败任务数量。", example = "2")
+ private Long failedTaskCount;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryItemVo.java
new file mode 100644
index 0000000..cdd9e99
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryItemVo.java
@@ -0,0 +1,33 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "外观专利检测历史记录项")
+public class AppearancePatentHistoryItemVo {
+ @Schema(description = "结果记录 ID。删除历史、下载结果时使用。", example = "1001")
+ private Long resultId;
+ @Schema(description = "任务 ID。", example = "3938")
+ private Long taskId;
+ @Schema(description = "源 Excel 文件名。", example = "17.xlsx")
+ private String sourceFilename;
+ @Schema(description = "最终结果文件名。任务完成并生成 xlsx 后返回。", example = "17-result.xlsx")
+ private String resultFilename;
+ @Schema(description = "最终结果文件下载地址。后端基于 OSS objectKey 生成的新鲜预签名 URL。", example = "https://bucket.oss-cn-hangzhou.aliyuncs.com/result/appearance_patent/xxx/17-result.xlsx?Expires=...")
+ private String downloadUrl;
+ private Long fileJobId;
+ private String fileStatus;
+ private String fileError;
+ private Boolean fileReady;
+ @Schema(description = "任务状态:PENDING=已解析待推送,RUNNING=执行中,SUCCESS=成功,FAILED=失败。", example = "SUCCESS")
+ private String taskStatus;
+ @Schema(description = "结果是否成功。true 表示任务完成并生成结果文件;false 表示失败或未完成。", example = "true")
+ private Boolean success;
+ @Schema(description = "错误信息。任务失败时返回,例如 Python 超时、结果文件生成失败等。", example = "Python interrupted before uploading final appearance patent result")
+ private String error;
+ @Schema(description = "最终结果行数。包含解析阶段被过滤但最终需要补回的 2_2、2_3 等子行。", example = "716")
+ private Integer rowCount;
+ @Schema(description = "历史记录创建时间,ISO 本地时间字符串。", example = "2026-04-26T10:30:00")
+ private String createdAt;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryVo.java
new file mode 100644
index 0000000..dfa788e
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentHistoryVo.java
@@ -0,0 +1,14 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测历史记录列表")
+public class AppearancePatentHistoryVo {
+ @Schema(description = "历史记录项列表,默认返回最近 100 条。")
+ private List items = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParseVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParseVo.java
new file mode 100644
index 0000000..2532d12
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParseVo.java
@@ -0,0 +1,26 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测解析结果")
+public class AppearancePatentParseVo {
+ @Schema(description = "新创建的任务 ID。后续手动推 Python、激活任务、回传结果、查询进度都使用该 ID。", example = "3938")
+ private Long taskId;
+ @Schema(description = "源 Excel 文件名。", example = "17.xlsx")
+ private String sourceFilename;
+ @Schema(description = "Excel 中检测到的有效数据总行数,不含空行。", example = "716")
+ private Integer totalRows;
+ @Schema(description = "返回前端并准备推给 Python 的代表行数量。只包含整数 id 和 n_1 行。", example = "458")
+ private Integer acceptedRows;
+ @Schema(description = "解析时被过滤或缺少必要字段的行数。n_2、n_3 等子行会计入过滤数,但仍会保存在 OSS 解析载荷中用于最终补齐。", example = "258")
+ private Integer droppedRows;
+ @Schema(description = "本任务最终使用的 AI 提示词。", example = "请排查这些亚马逊商品在英国及欧洲地区是否存在知识产权侵权风险。")
+ private String aiPrompt;
+ @Schema(description = "返回前端的代表行列表。前端只展示样例,不展示完整明细;完整 allItems 已保存在 OSS 解析载荷中。")
+ private List items = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParsedRowVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParsedRowVo.java
new file mode 100644
index 0000000..a286e4f
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentParsedRowVo.java
@@ -0,0 +1,28 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Data
+@Schema(description = "外观专利检测解析出的单行数据")
+public class AppearancePatentParsedRowVo {
+ @Schema(description = "Excel 原始行号,从 1 开始。", example = "2")
+ private Integer rowIndex;
+ @Schema(description = "Excel 中原始 id 值。", example = "2_1")
+ private String sourceId;
+ @Schema(description = "前端展示和 Python 回传使用的 id。整数 id 原样保留,子数据第一条如 2_1 原样保留。", example = "2_1")
+ private String displayId;
+ @Schema(description = "亚马逊 ASIN。", example = "B0CJ8SNXXV")
+ private String asin;
+ @Schema(description = "国家或站点。", example = "英国")
+ private String country;
+ @Schema(description = "商品图片 URL 或商品 URL,供 Coze 检测使用。", example = "https://webstatic.aiproxy.vip/output/demo.jpg")
+ private String url;
+ @Schema(description = "商品标题。", example = "Women Floral Dress Summer Casual")
+ private String title;
+ @Schema(description = "该 Excel 行的原始列值映射。最终生成 xlsx 时可从这里读取价格等字段。", example = "{\"id\":\"2_1\",\"asin\":\"B0CJ8SNXXV\",\"国家\":\"英国\",\"价格\":\"12.99\"}")
+ private Map values = new LinkedHashMap<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskBatchVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskBatchVo.java
new file mode 100644
index 0000000..7dd79c1
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskBatchVo.java
@@ -0,0 +1,16 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测批量进度响应")
+public class AppearancePatentTaskBatchVo {
+ @Schema(description = "查询到的任务详情列表。顺序按请求 taskIds 处理。")
+ private List items = new ArrayList<>();
+ @Schema(description = "未找到或不属于外观专利检测模块的任务 ID 列表。", example = "[99999]")
+ private List missingTaskIds = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskDetailVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskDetailVo.java
new file mode 100644
index 0000000..f71e369
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskDetailVo.java
@@ -0,0 +1,16 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "外观专利检测任务进度详情")
+public class AppearancePatentTaskDetailVo {
+ @Schema(description = "任务主记录轻量信息。")
+ private AppearancePatentTaskItemVo task;
+ @Schema(description = "预留的任务明细列表。当前进度接口主要返回任务轻量状态,不返回完整结果明细。")
+ private List items = new ArrayList<>();
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskItemVo.java
new file mode 100644
index 0000000..955213a
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/model/vo/AppearancePatentTaskItemVo.java
@@ -0,0 +1,23 @@
+package com.nanri.aiimage.modules.appearancepatent.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "外观专利检测任务轻量信息")
+public class AppearancePatentTaskItemVo {
+ @Schema(description = "任务 ID。", example = "3938")
+ private Long id;
+ @Schema(description = "任务编号,后端自动生成。", example = "APPEARANCE_PATENT-1780000000000000000")
+ private String taskNo;
+ @Schema(description = "任务状态:PENDING=已解析待推送,RUNNING=执行中,SUCCESS=成功,FAILED=失败。", example = "RUNNING")
+ private String status;
+ @Schema(description = "任务级错误信息。失败时返回。", example = "生成外观专利检测结果失败")
+ private String errorMessage;
+ @Schema(description = "创建时间,ISO 本地时间字符串。", example = "2026-04-26T10:00:00")
+ private String createdAt;
+ @Schema(description = "最后更新时间,通常由 Python 回传结果或任务收尾更新。", example = "2026-04-26T10:05:00")
+ private String updatedAt;
+ @Schema(description = "完成时间。任务未结束时为空。", example = "2026-04-26T10:10:00")
+ private String finishedAt;
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskCacheService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskCacheService.java
new file mode 100644
index 0000000..31e71db
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskCacheService.java
@@ -0,0 +1,144 @@
+package com.nanri.aiimage.modules.appearancepatent.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentResultRowDto;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class AppearancePatentTaskCacheService {
+
+ private static final long TTL_HOURS = 24;
+
+ private final StringRedisTemplate stringRedisTemplate;
+ private final ObjectMapper objectMapper;
+
+ public void appendPendingRow(Long taskId, Integer chunkIndex, AppearancePatentResultRowDto row) {
+ if (taskId == null || taskId <= 0 || chunkIndex == null || row == null) {
+ return;
+ }
+ try {
+ stringRedisTemplate.opsForList().rightPush(
+ pendingRowsKey(taskId),
+ objectMapper.writeValueAsString(new PendingRow(chunkIndex, row))
+ );
+ stringRedisTemplate.expire(pendingRowsKey(taskId), Duration.ofHours(TTL_HOURS));
+ touchTaskHeartbeat(taskId);
+ } catch (Exception ignored) {
+ }
+ }
+
+ public long pendingRowCount(Long taskId) {
+ Long size;
+ try {
+ size = stringRedisTemplate.opsForList().size(pendingRowsKey(taskId));
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] pending row count degraded taskId={} msg={}", taskId, ex.getMessage());
+ return 0L;
+ }
+ return size == null ? 0L : size;
+ }
+
+ public List drainPendingRows(Long taskId, int limit) {
+ if (taskId == null || taskId <= 0 || limit <= 0) {
+ return List.of();
+ }
+ String key = pendingRowsKey(taskId);
+ List values;
+ try {
+ values = stringRedisTemplate.opsForList().range(key, 0, limit - 1L);
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] drain range degraded taskId={} msg={}", taskId, ex.getMessage());
+ return List.of();
+ }
+ if (values == null || values.isEmpty()) {
+ return List.of();
+ }
+ try {
+ stringRedisTemplate.opsForList().trim(key, values.size(), -1);
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] drain trim degraded taskId={} msg={}", taskId, ex.getMessage());
+ return List.of();
+ }
+ List rows = new ArrayList<>();
+ for (String value : values) {
+ if (value == null || value.isBlank()) {
+ continue;
+ }
+ try {
+ rows.add(objectMapper.readValue(value, PendingRow.class));
+ } catch (Exception ignored) {
+ }
+ }
+ touchTaskHeartbeat(taskId);
+ return rows;
+ }
+
+ public void touchTaskHeartbeat(Long taskId) {
+ if (taskId == null || taskId <= 0) {
+ return;
+ }
+ try {
+ stringRedisTemplate.opsForValue().set(
+ heartbeatKey(taskId),
+ String.valueOf(Instant.now().toEpochMilli()),
+ Duration.ofHours(TTL_HOURS)
+ );
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] touch heartbeat degraded taskId={} msg={}", taskId, ex.getMessage());
+ }
+ }
+
+ public long getTaskHeartbeatMillis(Long taskId) {
+ if (taskId == null || taskId <= 0) {
+ return 0L;
+ }
+ String raw;
+ try {
+ raw = stringRedisTemplate.opsForValue().get(heartbeatKey(taskId));
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] get heartbeat degraded taskId={} msg={}", taskId, ex.getMessage());
+ return 0L;
+ }
+ if (raw == null || raw.isBlank()) {
+ return 0L;
+ }
+ try {
+ return Long.parseLong(raw);
+ } catch (NumberFormatException ignored) {
+ return 0L;
+ }
+ }
+
+ public void deleteTaskCache(Long taskId) {
+ if (taskId == null || taskId <= 0) {
+ return;
+ }
+ try {
+ stringRedisTemplate.delete(pendingRowsKey(taskId));
+ stringRedisTemplate.delete(heartbeatKey(taskId));
+ } catch (Exception ex) {
+ log.warn("[appearance-patent-cache] delete degraded taskId={} msg={}", taskId, ex.getMessage());
+ }
+ }
+
+ private String pendingRowsKey(Long taskId) {
+ return "appearance-patent:task:pending-rows:" + taskId;
+ }
+
+ private String heartbeatKey(Long taskId) {
+ return "appearance-patent:task:heartbeat:" + taskId;
+ }
+
+ public record PendingRow(Integer chunkIndex, AppearancePatentResultRowDto row) {
+ }
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java
new file mode 100644
index 0000000..d64f984
--- /dev/null
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java
@@ -0,0 +1,1001 @@
+package com.nanri.aiimage.modules.appearancepatent.service;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nanri.aiimage.common.exception.BusinessException;
+import com.nanri.aiimage.config.AppearancePatentProperties;
+import com.nanri.aiimage.config.StorageProperties;
+import com.nanri.aiimage.modules.appearancepatent.client.AppearancePatentCozeClient;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentParseRequest;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentParsedPayloadDto;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentResultRowDto;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentSourceFileDto;
+import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentSubmitResultRequest;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentDashboardVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentHistoryItemVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentHistoryVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentParsedRowVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentParseVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentTaskBatchVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentTaskDetailVo;
+import com.nanri.aiimage.modules.appearancepatent.model.vo.AppearancePatentTaskItemVo;
+import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
+import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
+import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
+import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
+import com.nanri.aiimage.modules.task.mapper.TaskChunkMapper;
+import com.nanri.aiimage.modules.task.mapper.TaskScopeStateMapper;
+import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
+import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
+import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
+import com.nanri.aiimage.modules.task.model.entity.TaskChunkEntity;
+import com.nanri.aiimage.modules.task.model.entity.TaskScopeStateEntity;
+import com.nanri.aiimage.modules.task.service.TaskFileJobService;
+import com.nanri.aiimage.modules.task.service.TransientPayloadStorageService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class AppearancePatentTaskService {
+
+ public static final String MODULE_TYPE = "APPEARANCE_PATENT";
+ private static final String STATUS_PENDING = "PENDING";
+ private static final String STATUS_RUNNING = "RUNNING";
+ private static final String STATUS_SUCCESS = "SUCCESS";
+ private static final String STATUS_FAILED = "FAILED";
+ private static final String CONTENT_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+ private static final List RESULT_HEADERS = List.of(
+ "id",
+ "asin",
+ "国家",
+ "价格",
+ "标题维度(商标)",
+ "外观维度(外观设计专利)",
+ "专利维度(发明/实用新型专利)",
+ "结论"
+ );
+
+ private final LocalFileStorageService localFileStorageService;
+ private final OssStorageService ossStorageService;
+ private final StorageProperties storageProperties;
+ private final FileTaskMapper fileTaskMapper;
+ private final FileResultMapper fileResultMapper;
+ private final TaskScopeStateMapper taskScopeStateMapper;
+ private final TaskChunkMapper taskChunkMapper;
+ private final ObjectMapper objectMapper;
+ private final AppearancePatentCozeClient cozeClient;
+ private final AppearancePatentTaskCacheService taskCacheService;
+ private final AppearancePatentProperties properties;
+ private final TaskFileJobService taskFileJobService;
+ private final TransientPayloadStorageService transientPayloadStorageService;
+
+ @Transactional
+ public AppearancePatentParseVo parseAndCreateTask(AppearancePatentParseRequest request) {
+ if (request.getUserId() == null || request.getUserId() <= 0) {
+ throw new BusinessException("user_id 不合法");
+ }
+ if (request.getFiles() == null || request.getFiles().isEmpty()) {
+ throw new BusinessException("请先上传 Excel 文件");
+ }
+ AppearancePatentSourceFileDto source = request.getFiles().getFirst();
+ if (source.getFileKey() == null || source.getFileKey().isBlank()) {
+ throw new BusinessException("fileKey 不能为空");
+ }
+ File input = localFileStorageService.findLocalSourceFile(source.getFileKey());
+ if (input == null || !input.exists()) {
+ throw new BusinessException("源文件不存在");
+ }
+
+ ParsedWorkbook parsed = parseWorkbook(input);
+ if (parsed.acceptedRows.isEmpty()) {
+ throw new BusinessException("未解析到有效 ASIN 数据");
+ }
+
+ FileTaskEntity task = new FileTaskEntity();
+ task.setTaskNo(MODULE_TYPE + "-" + IdUtil.getSnowflakeNextIdStr());
+ task.setModuleType(MODULE_TYPE);
+ task.setTaskMode("PYTHON_QUEUE");
+ task.setStatus(STATUS_PENDING);
+ task.setSourceFileCount(parsed.acceptedRows.size());
+ task.setSuccessFileCount(0);
+ task.setFailedFileCount(0);
+ task.setCreatedBy("user:" + request.getUserId());
+ task.setUserId(request.getUserId());
+ task.setCreatedAt(LocalDateTime.now());
+ task.setUpdatedAt(LocalDateTime.now());
+ try {
+ task.setRequestJson(objectMapper.writeValueAsString(request));
+ task.setResultJson("{}");
+ } catch (Exception ex) {
+ throw new BusinessException("序列化解析结果失败");
+ }
+ fileTaskMapper.insert(task);
+
+ String sourceScopeHash = DigestUtil.sha256Hex(source.getFileKey());
+ String parsedPayload = buildParsedPayloadJson(request.getAiPrompt(), parsed.headers, parsed.acceptedRows, parsed.allRows);
+ String parsedPayloadPointer = storeParsedPayload(task.getId(), sourceScopeHash, parsedPayload);
+ task.setResultJson(buildTaskResultJson(request.getAiPrompt(), source.getFileKey(), parsedPayloadPointer));
+ task.setUpdatedAt(LocalDateTime.now());
+ fileTaskMapper.updateById(task);
+
+ String sourceFilename = firstNonBlank(source.getOriginalFilename(), input.getName());
+ FileResultEntity result = new FileResultEntity();
+ result.setTaskId(task.getId());
+ result.setModuleType(MODULE_TYPE);
+ result.setSourceFilename(sourceFilename);
+ result.setSourceFileUrl(source.getFileKey());
+ result.setRowCount(parsed.acceptedRows.size());
+ result.setUserId(request.getUserId());
+ result.setCreatedAt(LocalDateTime.now());
+ fileResultMapper.insert(result);
+
+ TaskScopeStateEntity scope = new TaskScopeStateEntity();
+ scope.setTaskId(task.getId());
+ scope.setModuleType(MODULE_TYPE);
+ scope.setScopeKey(source.getFileKey());
+ scope.setScopeHash(sourceScopeHash);
+ scope.setParsedPayloadJson(writeJson(parsedPayloadPointer, "保存解析载荷指针失败"));
+ scope.setStateJson("{\"phase\":\"PARSED\"}");
+ scope.setChunkTotal(0);
+ scope.setReceivedChunkCount(0);
+ scope.setCompleted(0);
+ scope.setCreatedAt(LocalDateTime.now());
+ scope.setUpdatedAt(LocalDateTime.now());
+ taskScopeStateMapper.insert(scope);
+
+ AppearancePatentParseVo vo = new AppearancePatentParseVo();
+ vo.setTaskId(task.getId());
+ vo.setSourceFilename(sourceFilename);
+ vo.setTotalRows(parsed.totalRows);
+ vo.setAcceptedRows(parsed.acceptedRows.size());
+ vo.setDroppedRows(parsed.droppedRows);
+ vo.setAiPrompt(normalize(request.getAiPrompt()));
+ vo.setItems(parsed.acceptedRows);
+ return vo;
+ }
+
+ @Transactional
+ public void activateTask(Long taskId, Long userId) {
+ FileTaskEntity task = fileTaskMapper.selectById(taskId);
+ if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || userId != null && !Objects.equals(userId, task.getUserId())) {
+ throw new BusinessException("任务不存在");
+ }
+ if (STATUS_SUCCESS.equals(task.getStatus()) || STATUS_FAILED.equals(task.getStatus())) {
+ throw new BusinessException("任务已结束");
+ }
+ task.setStatus(STATUS_RUNNING);
+ task.setUpdatedAt(LocalDateTime.now());
+ fileTaskMapper.updateById(task);
+ taskCacheService.touchTaskHeartbeat(taskId);
+ }
+
+ public AppearancePatentDashboardVo dashboard(Long userId) {
+ AppearancePatentDashboardVo vo = new AppearancePatentDashboardVo();
+ vo.setPendingTaskCount(countTask(userId, STATUS_RUNNING));
+ vo.setSuccessTaskCount(countTask(userId, STATUS_SUCCESS));
+ vo.setFailedTaskCount(countTask(userId, STATUS_FAILED));
+ vo.setProcessedTaskCount(vo.getSuccessTaskCount() + vo.getFailedTaskCount());
+ return vo;
+ }
+
+ public AppearancePatentHistoryVo history(Long userId) {
+ AppearancePatentHistoryVo vo = new AppearancePatentHistoryVo();
+ List rows = fileResultMapper.selectList(new LambdaQueryWrapper()
+ .eq(FileResultEntity::getModuleType, MODULE_TYPE)
+ .eq(FileResultEntity::getUserId, userId)
+ .orderByDesc(FileResultEntity::getCreatedAt)
+ .last("limit 100"));
+ Map statusMap = new LinkedHashMap<>();
+ List taskIds = rows.stream().map(FileResultEntity::getTaskId).filter(Objects::nonNull).distinct().toList();
+ if (!taskIds.isEmpty()) {
+ for (FileTaskEntity task : fileTaskMapper.selectBatchIds(taskIds)) {
+ statusMap.put(task.getId(), task.getStatus());
+ }
+ }
+ for (FileResultEntity row : rows) {
+ vo.getItems().add(toHistoryItem(row, statusMap.get(row.getTaskId())));
+ }
+ return vo;
+ }
+
+ public AppearancePatentTaskBatchVo progressBatch(List taskIds) {
+ AppearancePatentTaskBatchVo vo = new AppearancePatentTaskBatchVo();
+ List normalizedIds = taskIds == null ? List.of() : taskIds.stream()
+ .filter(id -> id != null && id > 0)
+ .distinct()
+ .toList();
+ if (normalizedIds.isEmpty()) {
+ return vo;
+ }
+ Map taskMap = new LinkedHashMap<>();
+ for (FileTaskEntity task : fileTaskMapper.selectBatchIds(normalizedIds)) {
+ if (task != null && MODULE_TYPE.equals(task.getModuleType())) {
+ taskMap.put(task.getId(), task);
+ }
+ }
+ for (Long taskId : normalizedIds) {
+ FileTaskEntity task = taskMap.get(taskId);
+ if (task == null) {
+ vo.getMissingTaskIds().add(taskId);
+ continue;
+ }
+ AppearancePatentTaskDetailVo detail = new AppearancePatentTaskDetailVo();
+ detail.setTask(toTaskItem(task));
+ vo.getItems().add(detail);
+ }
+ return vo;
+ }
+
+ @Transactional
+ public void submitResult(Long taskId, AppearancePatentSubmitResultRequest request) {
+ FileTaskEntity task = fileTaskMapper.selectById(taskId);
+ if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
+ throw new BusinessException("任务不存在");
+ }
+ if (!STATUS_RUNNING.equals(task.getStatus())) {
+ throw new BusinessException("任务不是运行中状态");
+ }
+
+ int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex();
+ int chunkTotal = request.getChunkTotal() == null ? 1 : request.getChunkTotal();
+ String scopeKey = firstNonBlank(request.getSubmissionId(), "task:" + taskId);
+ String scopeHash = DigestUtil.sha256Hex(scopeKey);
+ taskCacheService.touchTaskHeartbeat(taskId);
+
+ TaskChunkEntity existing = taskChunkMapper.selectOne(new LambdaQueryWrapper()
+ .eq(TaskChunkEntity::getTaskId, taskId)
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
+ .eq(TaskChunkEntity::getScopeHash, scopeHash)
+ .eq(TaskChunkEntity::getChunkIndex, chunkIndex)
+ .last("limit 1"));
+ if (existing == null) {
+ List rawRows = request.getItems() == null ? List.of() : request.getItems();
+ String payloadJson = writeJson(rawRows, "结果序列化失败");
+
+ TaskChunkEntity chunk = new TaskChunkEntity();
+ chunk.setTaskId(taskId);
+ chunk.setModuleType(MODULE_TYPE);
+ chunk.setScopeKey(scopeKey);
+ chunk.setScopeHash(scopeHash);
+ chunk.setChunkIndex(chunkIndex);
+ chunk.setChunkTotal(chunkTotal);
+ String storedPayload = transientPayloadStorageService.storeChunkPayload(MODULE_TYPE, taskId, scopeHash, chunkIndex, payloadJson);
+ chunk.setPayloadJson(storedPayload);
+ chunk.setPayloadHash(DigestUtil.sha256Hex(payloadJson));
+ chunk.setCreatedAt(LocalDateTime.now());
+ chunk.setUpdatedAt(LocalDateTime.now());
+ boolean inserted = true;
+ try {
+ taskChunkMapper.insert(chunk);
+ } catch (DuplicateKeyException ex) {
+ inserted = false;
+ transientPayloadStorageService.deletePayloadIfPresent(storedPayload);
+ log.info("[appearance-patent] duplicate chunk inserted concurrently taskId={} scope={} chunk={}", taskId, scopeKey, chunkIndex);
+ }
+ if (inserted) {
+ for (AppearancePatentResultRowDto rawRow : rawRows) {
+ taskCacheService.appendPendingRow(taskId, chunkIndex, rawRow);
+ }
+ }
+ } else {
+ log.info("[appearance-patent] duplicate chunk ignored taskId={} scope={} chunk={}", taskId, scopeKey, chunkIndex);
+ }
+
+ flushPendingRowsIfNeeded(task, scopeHash, Boolean.TRUE.equals(request.getDone()));
+
+ TaskScopeStateEntity scope = taskScopeStateMapper.selectOne(new LambdaQueryWrapper()
+ .eq(TaskScopeStateEntity::getTaskId, taskId)
+ .eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE)
+ .eq(TaskScopeStateEntity::getScopeHash, scopeHash)
+ .last("limit 1"));
+ if (scope == null) {
+ scope = new TaskScopeStateEntity();
+ scope.setTaskId(taskId);
+ scope.setModuleType(MODULE_TYPE);
+ scope.setScopeKey(scopeKey);
+ scope.setScopeHash(scopeHash);
+ scope.setCreatedAt(LocalDateTime.now());
+ }
+ scope.setChunkTotal(chunkTotal);
+ scope.setReceivedChunkCount(countChunks(taskId, scopeHash));
+ scope.setLastChunkAt(LocalDateTime.now());
+ scope.setLastError(request.getError());
+ scope.setCompleted(Boolean.TRUE.equals(request.getDone()) ? 1 : 0);
+ scope.setUpdatedAt(LocalDateTime.now());
+ scope.setStateJson("{\"phase\":\"RECEIVED\",\"coze\":\"DONE\"}");
+ if (scope.getId() == null) {
+ taskScopeStateMapper.insert(scope);
+ } else {
+ taskScopeStateMapper.updateById(scope);
+ }
+
+ if (Boolean.TRUE.equals(request.getDone()) || request.getError() != null && !request.getError().isBlank()) {
+ finalizeTask(task, request.getError(), allRowCount(task), true);
+ } else {
+ task.setUpdatedAt(LocalDateTime.now());
+ fileTaskMapper.updateById(task);
+ }
+ }
+
+ @Transactional
+ public void deleteTask(Long taskId, Long userId) {
+ FileTaskEntity task = fileTaskMapper.selectById(taskId);
+ if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || !Objects.equals(userId, task.getUserId())) {
+ throw new BusinessException("任务不存在");
+ }
+ fileResultMapper.delete(new LambdaQueryWrapper().eq(FileResultEntity::getTaskId, taskId).eq(FileResultEntity::getModuleType, MODULE_TYPE));
+ deleteTransientTaskPayloads(taskId);
+ taskScopeStateMapper.delete(new LambdaQueryWrapper().eq(TaskScopeStateEntity::getTaskId, taskId).eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE));
+ taskChunkMapper.delete(new LambdaQueryWrapper().eq(TaskChunkEntity::getTaskId, taskId).eq(TaskChunkEntity::getModuleType, MODULE_TYPE));
+ taskCacheService.deleteTaskCache(taskId);
+ fileTaskMapper.deleteById(taskId);
+ }
+
+ public void deleteHistory(Long resultId, Long userId) {
+ FileResultEntity row = fileResultMapper.selectById(resultId);
+ if (row == null || !MODULE_TYPE.equals(row.getModuleType()) || !Objects.equals(userId, row.getUserId())) {
+ throw new BusinessException("记录不存在");
+ }
+ fileResultMapper.deleteById(resultId);
+ }
+
+ public String resolveResultDownloadUrl(Long resultId, Long userId) {
+ FileResultEntity row = fileResultMapper.selectById(resultId);
+ if (row == null || !MODULE_TYPE.equals(row.getModuleType()) || !Objects.equals(userId, row.getUserId())) {
+ throw new BusinessException("记录不存在");
+ }
+ if (row.getResultFileUrl() == null || row.getResultFileUrl().isBlank()) {
+ throw new BusinessException("暂无可下载文件");
+ }
+ return ossStorageService.generateFreshDownloadUrl(row.getResultFileUrl());
+ }
+
+ public String resolveResultDownloadFilename(Long resultId, Long userId) {
+ FileResultEntity row = fileResultMapper.selectById(resultId);
+ if (row == null || !MODULE_TYPE.equals(row.getModuleType()) || !Objects.equals(userId, row.getUserId())) {
+ throw new BusinessException("记录不存在");
+ }
+ return row.getResultFilename() == null || row.getResultFilename().isBlank()
+ ? "appearance-patent-" + resultId + ".xlsx"
+ : row.getResultFilename();
+ }
+
+ @Scheduled(cron = "${aiimage.appearance-patent.stale-finalize-cron:0 */2 * * * *}")
+ @Transactional
+ public void finalizeStaleTasks() {
+ LocalDateTime threshold = LocalDateTime.now().minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
+ long thresholdMillis = threshold.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ List tasks = fileTaskMapper.selectList(new LambdaQueryWrapper()
+ .eq(FileTaskEntity::getModuleType, MODULE_TYPE)
+ .eq(FileTaskEntity::getStatus, STATUS_RUNNING)
+ .lt(FileTaskEntity::getUpdatedAt, threshold)
+ .last("limit 50"));
+ for (FileTaskEntity task : tasks) {
+ long heartbeatMillis = taskCacheService.getTaskHeartbeatMillis(task.getId());
+ if (heartbeatMillis > thresholdMillis) {
+ continue;
+ }
+ flushPendingRowsIfNeeded(task, DigestUtil.sha256Hex("appearance-patent-" + task.getId()), true);
+ finalizeTask(task, "Python interrupted before uploading final appearance patent result", allRowCount(task), true);
+ }
+ }
+
+ private List applyCozeInBatches(List items, FileTaskEntity task) {
+ if (items == null || items.isEmpty()) {
+ return List.of();
+ }
+ String prompt = readAiPrompt(task);
+ int batchSize = Math.max(1, properties.getCozeBatchSize());
+ List result = new ArrayList<>();
+ for (int i = 0; i < items.size(); i += batchSize) {
+ result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt));
+ }
+ return result;
+ }
+
+ private void flushPendingRowsIfNeeded(FileTaskEntity task, String scopeHash, boolean force) {
+ int batchSize = Math.max(1, properties.getCozeBatchSize());
+ while (force ? taskCacheService.pendingRowCount(task.getId()) > 0 : taskCacheService.pendingRowCount(task.getId()) >= batchSize) {
+ int drainSize = force ? batchSize : Math.max(1, batchSize);
+ List pendingRows = taskCacheService.drainPendingRows(task.getId(), drainSize);
+ if (pendingRows.isEmpty()) {
+ return;
+ }
+ List rawRows = pendingRows.stream()
+ .map(AppearancePatentTaskCacheService.PendingRow::row)
+ .toList();
+ List cozeRows = cozeClient.inspect(rawRows, readAiPrompt(task));
+ for (int i = 0; i < pendingRows.size(); i++) {
+ AppearancePatentTaskCacheService.PendingRow pending = pendingRows.get(i);
+ AppearancePatentResultRowDto resultRow = i < cozeRows.size() ? cozeRows.get(i) : pending.row();
+ List persistedRows = expandRows(task, List.of(resultRow));
+ updateChunkPayload(task.getId(), scopeHash, pending.chunkIndex(), persistedRows);
+ }
+ }
+ }
+
+ private void updateChunkPayload(Long taskId, String scopeHash, Integer chunkIndex, List rows) {
+ TaskChunkEntity chunk = taskChunkMapper.selectOne(new LambdaQueryWrapper()
+ .eq(TaskChunkEntity::getTaskId, taskId)
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
+ .eq(TaskChunkEntity::getScopeHash, scopeHash)
+ .eq(TaskChunkEntity::getChunkIndex, chunkIndex)
+ .last("limit 1"));
+ if (chunk == null) {
+ return;
+ }
+ String payloadJson = writeJson(rows == null ? List.of() : rows, "结果序列化失败");
+ String storedPayload = transientPayloadStorageService.storeChunkPayload(MODULE_TYPE, taskId, scopeHash, chunkIndex, payloadJson);
+ transientPayloadStorageService.deleteReplacedPayloadIfNeeded(chunk.getPayloadJson(), storedPayload);
+ chunk.setPayloadJson(storedPayload);
+ chunk.setPayloadHash(DigestUtil.sha256Hex(payloadJson));
+ chunk.setUpdatedAt(LocalDateTime.now());
+ taskChunkMapper.updateById(chunk);
+ }
+
+ private List expandRows(FileTaskEntity task, List representatives) {
+ if (representatives == null || representatives.isEmpty()) {
+ return List.of();
+ }
+ Map> allRowsByBaseId = loadAllRowsByBaseId(task);
+ List result = new ArrayList<>();
+ Set emittedKeys = new LinkedHashSet<>();
+ for (AppearancePatentResultRowDto representative : representatives) {
+ String baseId = baseId(representative.getId());
+ List siblings = allRowsByBaseId.get(baseId);
+ if (siblings == null || siblings.isEmpty()) {
+ result.add(representative);
+ continue;
+ }
+ for (AppearancePatentParsedRowVo sibling : siblings) {
+ String key = sibling.getDisplayId() + "::" + sibling.getAsin() + "::" + sibling.getCountry();
+ if (!emittedKeys.add(key)) {
+ continue;
+ }
+ result.add(copyResultToSibling(representative, sibling));
+ }
+ }
+ return result;
+ }
+
+ private Map> loadAllRowsByBaseId(FileTaskEntity task) {
+ Map> result = new LinkedHashMap<>();
+ try {
+ for (AppearancePatentParsedRowVo row : readParsedPayload(task).getAllItems()) {
+ result.computeIfAbsent(baseId(row.getDisplayId()), key -> new ArrayList<>()).add(row);
+ }
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] read all rows failed taskId={} err={}", task.getId(), ex.getMessage());
+ }
+ return result;
+ }
+
+ private int allRowCount(FileTaskEntity task) {
+ try {
+ return readParsedPayload(task).getAllItems().size();
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] read all row count failed taskId={} err={}", task.getId(), ex.getMessage());
+ return 0;
+ }
+ }
+
+ private AppearancePatentResultRowDto copyResultToSibling(AppearancePatentResultRowDto representative, AppearancePatentParsedRowVo sibling) {
+ AppearancePatentResultRowDto row = new AppearancePatentResultRowDto();
+ row.setId(sibling.getDisplayId());
+ row.setAsin(sibling.getAsin());
+ row.setCountry(sibling.getCountry());
+ row.setUrl(sibling.getUrl());
+ row.setTitle(sibling.getTitle());
+ row.setError(representative.getError());
+ row.setTitleRisk(representative.getTitleRisk());
+ row.setAppearanceRisk(representative.getAppearanceRisk());
+ row.setPatentRisk(representative.getPatentRisk());
+ row.setConclusion(representative.getConclusion());
+ row.setDone(representative.getDone());
+ return row;
+ }
+
+ private String readAiPrompt(FileTaskEntity task) {
+ try {
+ return readParsedPayload(task).getAiPrompt();
+ } catch (Exception ignored) {
+ return "";
+ }
+ }
+
+ private void finalizeTask(FileTaskEntity task, String error, int rowCount, boolean assembleWorkbook) {
+ String finalError = error;
+ FileResultEntity result = null;
+ List rows = fileResultMapper.selectList(new LambdaQueryWrapper()
+ .eq(FileResultEntity::getTaskId, task.getId())
+ .eq(FileResultEntity::getModuleType, MODULE_TYPE)
+ .last("limit 1"));
+ if (!rows.isEmpty()) {
+ result = rows.getFirst();
+ if (assembleWorkbook && shouldAssembleSynchronously()) {
+ try {
+ assembleResultWorkbook(task, result);
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] assemble result workbook failed taskId={} err={}", task.getId(), ex.getMessage());
+ finalError = firstNonBlank(finalError, "生成外观专利检测结果失败");
+ }
+ }
+ }
+ boolean failed = finalError != null && !finalError.isBlank();
+ task.setStatus(failed ? STATUS_FAILED : STATUS_SUCCESS);
+ task.setSuccessFileCount(failed ? 0 : 1);
+ task.setFailedFileCount(failed ? 1 : 0);
+ task.setErrorMessage(finalError);
+ task.setUpdatedAt(LocalDateTime.now());
+ task.setFinishedAt(LocalDateTime.now());
+ fileTaskMapper.updateById(task);
+ if (result != null) {
+ result.setSuccess(failed ? 0 : 1);
+ result.setErrorMessage(finalError);
+ result.setRowCount(rowCount > 0 ? rowCount : result.getRowCount());
+ if (!failed && assembleWorkbook) {
+ result.setResultFilename(safeFileStem(result.getSourceFilename()) + "-result.xlsx");
+ result.setResultFileUrl(null);
+ result.setResultFileSize(0L);
+ result.setResultContentType(CONTENT_TYPE_XLSX);
+ }
+ fileResultMapper.updateById(result);
+ if (!failed && assembleWorkbook) {
+ taskFileJobService.enqueueAssembleResult(task.getId(), MODULE_TYPE, result.getId(), "task:" + task.getId());
+ }
+ }
+ taskCacheService.deleteTaskCache(task.getId());
+ }
+
+ private boolean shouldAssembleSynchronously() {
+ return false;
+ }
+
+ public void processResultFileJob(TaskFileJobEntity job) {
+ if (job == null || job.getTaskId() == null || job.getResultId() == null) {
+ throw new BusinessException("结果文件任务参数不完整");
+ }
+ FileTaskEntity task = fileTaskMapper.selectById(job.getTaskId());
+ if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
+ throw new BusinessException("任务不存在");
+ }
+ FileResultEntity result = fileResultMapper.selectById(job.getResultId());
+ if (result == null || !MODULE_TYPE.equals(result.getModuleType())) {
+ throw new BusinessException("结果记录不存在");
+ }
+ assembleResultWorkbook(task, result);
+ fileResultMapper.updateById(result);
+ }
+
+ public void cleanupResultFileJob(TaskFileJobEntity job) {
+ if (job == null || job.getTaskId() == null) {
+ return;
+ }
+ deleteTransientTaskPayloads(job.getTaskId());
+ taskChunkMapper.delete(new LambdaQueryWrapper()
+ .eq(TaskChunkEntity::getTaskId, job.getTaskId())
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE));
+ taskScopeStateMapper.delete(new LambdaQueryWrapper()
+ .eq(TaskScopeStateEntity::getTaskId, job.getTaskId())
+ .eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE));
+ }
+
+ private void assembleResultWorkbook(FileTaskEntity task, FileResultEntity result) {
+ AppearancePatentParsedPayloadDto parsed = readParsedPayload(task);
+ Map resultMap = loadPersistedResultRows(task.getId());
+ File outputDir = new File(storageProperties.getLocalTempDir(), "appearance-patent-result");
+ if (!outputDir.exists() && !outputDir.mkdirs()) {
+ throw new BusinessException("创建结果目录失败");
+ }
+ String filename = safeFileStem(result.getSourceFilename()) + "-result.xlsx";
+ File xlsx = new File(outputDir, filename);
+ try {
+ writeResultWorkbook(xlsx, parsed, resultMap);
+ String objectKey = ossStorageService.uploadResultFile(xlsx, MODULE_TYPE);
+ result.setResultFilename(filename);
+ result.setResultFileUrl(objectKey);
+ result.setResultFileSize(xlsx.length());
+ result.setResultContentType(CONTENT_TYPE_XLSX);
+ result.setRowCount(parsed.getAllItems().size());
+ } finally {
+ if (xlsx.exists() && !xlsx.delete()) {
+ log.warn("[appearance-patent] delete temp xlsx failed file={}", xlsx);
+ }
+ }
+ }
+
+ private Map loadPersistedResultRows(Long taskId) {
+ Map result = new LinkedHashMap<>();
+ List chunks = taskChunkMapper.selectList(new LambdaQueryWrapper()
+ .eq(TaskChunkEntity::getTaskId, taskId)
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
+ .orderByAsc(TaskChunkEntity::getChunkIndex));
+ for (TaskChunkEntity chunk : chunks) {
+ if (chunk.getPayloadJson() == null || chunk.getPayloadJson().isBlank()) {
+ continue;
+ }
+ try {
+ String payloadJson = transientPayloadStorageService.resolvePayload(chunk.getPayloadJson(), "read appearance patent chunk failed");
+ JsonNode array = objectMapper.readTree(payloadJson);
+ if (!array.isArray()) {
+ continue;
+ }
+ for (JsonNode node : array) {
+ AppearancePatentResultRowDto row = objectMapper.treeToValue(node, AppearancePatentResultRowDto.class);
+ result.put(rowKey(row.getId(), row.getAsin(), row.getCountry()), row);
+ }
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] read chunk payload failed taskId={} chunk={} err={}", taskId, chunk.getChunkIndex(), ex.getMessage());
+ }
+ }
+ return result;
+ }
+
+ private void writeResultWorkbook(File xlsx, AppearancePatentParsedPayloadDto parsed, Map resultMap) {
+ try (SXSSFWorkbook workbook = new SXSSFWorkbook(200); FileOutputStream fos = new FileOutputStream(xlsx)) {
+ Sheet sheet = workbook.createSheet("外观专利检测结果");
+ CellStyle headerStyle = workbook.createCellStyle();
+ Font font = workbook.createFont();
+ font.setBold(true);
+ headerStyle.setFont(font);
+
+ Row header = sheet.createRow(0);
+ for (int i = 0; i < RESULT_HEADERS.size(); i++) {
+ Cell cell = header.createCell(i);
+ cell.setCellValue(RESULT_HEADERS.get(i));
+ cell.setCellStyle(headerStyle);
+ }
+
+ int rowIndex = 1;
+ for (AppearancePatentParsedRowVo parsedRow : parsed.getAllItems()) {
+ AppearancePatentResultRowDto resultRow = resultMap.get(rowKey(parsedRow.getDisplayId(), parsedRow.getAsin(), parsedRow.getCountry()));
+ Row row = sheet.createRow(rowIndex++);
+ int col = 0;
+ row.createCell(col++).setCellValue(firstNonBlank(parsedRow.getDisplayId(), parsedRow.getSourceId()));
+ row.createCell(col++).setCellValue(firstNonBlank(parsedRow.getAsin(), ""));
+ row.createCell(col++).setCellValue(firstNonBlank(parsedRow.getCountry(), ""));
+ row.createCell(col++).setCellValue(readValueByHeader(parsedRow, "价格", "price"));
+ row.createCell(col++).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getTitleRisk(), ""));
+ row.createCell(col++).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getAppearanceRisk(), ""));
+ row.createCell(col++).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getPatentRisk(), ""));
+ row.createCell(col).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getConclusion(), ""));
+ }
+ workbook.write(fos);
+ workbook.dispose();
+ } catch (Exception ex) {
+ throw new BusinessException("生成外观专利检测结果失败");
+ }
+ }
+
+ private ParsedWorkbook parseWorkbook(File input) {
+ DataFormatter formatter = new DataFormatter();
+ try (FileInputStream fis = new FileInputStream(input); Workbook workbook = WorkbookFactory.create(fis)) {
+ Sheet sheet = workbook.getSheetAt(0);
+ Row header = sheet.getRow(0);
+ if (header == null) {
+ throw new BusinessException("Excel 表头为空");
+ }
+ Map headerMap = buildHeaderMap(header, formatter);
+ List headers = readHeaders(header, formatter);
+ int idCol = findRequiredHeader(headerMap, "id");
+ int asinCol = findRequiredHeader(headerMap, "asin");
+ int countryCol = findRequiredHeader(headerMap, "国家", "country");
+ int urlCol = findOptionalHeader(headerMap, "url", "rul", "链接", "商品链接");
+ int titleCol = findOptionalHeader(headerMap, "标题", "title", "商品标题");
+
+ List accepted = new ArrayList<>();
+ List allRows = new ArrayList<>();
+ int total = 0;
+ int dropped = 0;
+ for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+ Row row = sheet.getRow(i);
+ if (row == null) {
+ continue;
+ }
+ String id = cell(row, idCol, formatter);
+ String asin = cell(row, asinCol, formatter).toUpperCase(Locale.ROOT);
+ String country = cell(row, countryCol, formatter);
+ if (id.isBlank() && asin.isBlank() && country.isBlank()) {
+ continue;
+ }
+ total++;
+ if (id.isBlank() || asin.isBlank() || country.isBlank()) {
+ dropped++;
+ continue;
+ }
+ AppearancePatentParsedRowVo vo = new AppearancePatentParsedRowVo();
+ vo.setRowIndex(i + 1);
+ vo.setSourceId(id);
+ vo.setDisplayId(normalizeDisplayId(id));
+ vo.setAsin(asin);
+ vo.setCountry(country);
+ vo.setUrl(urlCol >= 0 ? cell(row, urlCol, formatter) : "");
+ vo.setTitle(titleCol >= 0 ? cell(row, titleCol, formatter) : "");
+ vo.setValues(readRowValues(row, headers, formatter));
+ allRows.add(vo);
+ if (isAcceptedId(id)) {
+ accepted.add(vo);
+ } else {
+ dropped++;
+ }
+ }
+ return new ParsedWorkbook(total, dropped, headers, accepted, allRows);
+ } catch (BusinessException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ log.warn("[appearance-patent] parse failed file={} err={}", input, ex.getMessage());
+ throw new BusinessException("解析 Excel 失败");
+ }
+ }
+
+ private Map buildHeaderMap(Row header, DataFormatter formatter) {
+ Map map = new LinkedHashMap<>();
+ for (int i = 0; i < header.getLastCellNum(); i++) {
+ String val = normalize(formatter.formatCellValue(header.getCell(i)));
+ if (!val.isBlank()) {
+ map.putIfAbsent(val.toLowerCase(Locale.ROOT), i);
+ }
+ }
+ return map;
+ }
+
+ private List readHeaders(Row header, DataFormatter formatter) {
+ List headers = new ArrayList<>();
+ for (int i = 0; i < header.getLastCellNum(); i++) {
+ String val = normalize(formatter.formatCellValue(header.getCell(i)));
+ headers.add(val.isBlank() ? "列" + (i + 1) : val);
+ }
+ return headers;
+ }
+
+ private Map readRowValues(Row row, List headers, DataFormatter formatter) {
+ Map values = new LinkedHashMap<>();
+ for (int i = 0; i < headers.size(); i++) {
+ values.put(headers.get(i), cell(row, i, formatter));
+ }
+ return values;
+ }
+
+ private int findRequiredHeader(Map map, String... names) {
+ int idx = findOptionalHeader(map, names);
+ if (idx < 0) {
+ throw new BusinessException("缺少必要表头: " + String.join("/", names));
+ }
+ return idx;
+ }
+
+ private int findOptionalHeader(Map map, String... names) {
+ for (Map.Entry entry : map.entrySet()) {
+ for (String name : names) {
+ if (entry.getKey().contains(name.toLowerCase(Locale.ROOT))) {
+ return entry.getValue();
+ }
+ }
+ }
+ return -1;
+ }
+
+ private String cell(Row row, int col, DataFormatter formatter) {
+ return col < 0 ? "" : normalize(formatter.formatCellValue(row.getCell(col)));
+ }
+
+ private boolean isAcceptedId(String id) {
+ String s = id.trim();
+ return s.matches("\\d+") || s.matches("\\d+_1");
+ }
+
+ private String baseId(String id) {
+ String s = normalize(id);
+ int idx = s.indexOf('_');
+ return idx > 0 ? s.substring(0, idx) : s;
+ }
+
+ private String normalizeDisplayId(String id) {
+ return id == null ? "" : id.trim();
+ }
+
+ private long countTask(Long userId, String status) {
+ Long count = fileTaskMapper.selectCount(new LambdaQueryWrapper()
+ .eq(FileTaskEntity::getModuleType, MODULE_TYPE)
+ .eq(FileTaskEntity::getUserId, userId)
+ .eq(FileTaskEntity::getStatus, status));
+ return count == null ? 0L : count;
+ }
+
+ private int countChunks(Long taskId, String scopeHash) {
+ Long count = taskChunkMapper.selectCount(new LambdaQueryWrapper()
+ .eq(TaskChunkEntity::getTaskId, taskId)
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
+ .eq(TaskChunkEntity::getScopeHash, scopeHash));
+ return count == null ? 0 : count.intValue();
+ }
+
+ private AppearancePatentTaskItemVo toTaskItem(FileTaskEntity task) {
+ AppearancePatentTaskItemVo vo = new AppearancePatentTaskItemVo();
+ vo.setId(task.getId());
+ vo.setTaskNo(task.getTaskNo());
+ vo.setStatus(task.getStatus());
+ vo.setErrorMessage(task.getErrorMessage());
+ vo.setCreatedAt(fmt(task.getCreatedAt()));
+ vo.setUpdatedAt(fmt(task.getUpdatedAt()));
+ vo.setFinishedAt(fmt(task.getFinishedAt()));
+ return vo;
+ }
+
+ private AppearancePatentHistoryItemVo toHistoryItem(FileResultEntity row, String taskStatus) {
+ AppearancePatentHistoryItemVo vo = new AppearancePatentHistoryItemVo();
+ vo.setResultId(row.getId());
+ vo.setTaskId(row.getTaskId());
+ vo.setSourceFilename(row.getSourceFilename());
+ vo.setResultFilename(row.getResultFilename());
+ vo.setDownloadUrl(null);
+ attachFileJobState(vo, row);
+ vo.setTaskStatus(taskStatus);
+ vo.setSuccess(row.getSuccess() != null && row.getSuccess() == 1);
+ vo.setError(row.getErrorMessage());
+ vo.setRowCount(row.getRowCount());
+ vo.setCreatedAt(fmt(row.getCreatedAt()));
+ return vo;
+ }
+
+ private void attachFileJobState(AppearancePatentHistoryItemVo vo, FileResultEntity row) {
+ TaskFileJobEntity job = taskFileJobService.findAssembleJob(row.getTaskId(), MODULE_TYPE, row.getId());
+ vo.setFileReady(row.getResultFileUrl() != null && !row.getResultFileUrl().isBlank());
+ if (job == null) {
+ vo.setFileStatus(Boolean.TRUE.equals(vo.getFileReady()) ? "SUCCESS" : null);
+ return;
+ }
+ vo.setFileJobId(job.getId());
+ vo.setFileStatus(job.getStatus());
+ vo.setFileError(job.getErrorMessage());
+ }
+
+ private String fmt(LocalDateTime t) {
+ return t == null ? null : t.toString();
+ }
+
+ private String firstNonBlank(String preferred, String fallback) {
+ return preferred == null || preferred.isBlank() ? fallback : preferred.trim();
+ }
+
+ private String normalize(String val) {
+ return val == null ? "" : val.replace("\ufeff", "").replace("\u3000", " ").trim().replaceAll("\\s+", " ");
+ }
+
+ private String buildParsedPayloadJson(String aiPrompt, List headers, List rows, List allRows) {
+ AppearancePatentParsedPayloadDto payload = new AppearancePatentParsedPayloadDto();
+ payload.setAiPrompt(normalize(aiPrompt));
+ payload.setHeaders(headers == null ? List.of() : headers);
+ payload.setItems(rows == null ? List.of() : rows);
+ payload.setAllItems(allRows == null ? List.of() : allRows);
+ return writeJson(payload, "保存解析结果失败");
+ }
+
+ private String buildTaskResultJson(String aiPrompt, String sourceFileKey, String parsedPayloadPointer) {
+ Map payload = new LinkedHashMap<>();
+ payload.put("aiPrompt", normalize(aiPrompt));
+ payload.put("sourceFileKey", sourceFileKey);
+ payload.put("parsedPayloadRef", parsedPayloadPointer);
+ return writeJson(payload, "保存任务结果索引失败");
+ }
+
+ private String storeParsedPayload(Long taskId, String scopeHash, String parsedPayloadJson) {
+ return transientPayloadStorageService.storeParsedPayload(MODULE_TYPE, taskId, scopeHash, parsedPayloadJson, true);
+ }
+
+ private AppearancePatentParsedPayloadDto readParsedPayload(FileTaskEntity task) {
+ try {
+ JsonNode root = objectMapper.readTree(task.getResultJson());
+ String pointer = root.path("parsedPayloadRef").asText("");
+ if (!pointer.isBlank()) {
+ String json = transientPayloadStorageService.resolvePayload(pointer, "read appearance patent parsed payload failed");
+ return objectMapper.readValue(json, AppearancePatentParsedPayloadDto.class);
+ }
+ if (root.path("allItems").isArray()) {
+ return objectMapper.treeToValue(root, AppearancePatentParsedPayloadDto.class);
+ }
+ } catch (Exception ex) {
+ throw new BusinessException("读取解析载荷失败");
+ }
+ return new AppearancePatentParsedPayloadDto();
+ }
+
+ private String rowKey(String id, String asin, String country) {
+ return normalize(id) + "::" + normalize(asin).toUpperCase(Locale.ROOT) + "::" + normalize(country);
+ }
+
+ private String readValueByHeader(AppearancePatentParsedRowVo row, String... candidates) {
+ if (row == null || row.getValues() == null || row.getValues().isEmpty() || candidates == null) {
+ return "";
+ }
+ for (Map.Entry entry : row.getValues().entrySet()) {
+ String header = normalize(entry.getKey()).toLowerCase(Locale.ROOT);
+ for (String candidate : candidates) {
+ String expected = normalize(candidate).toLowerCase(Locale.ROOT);
+ if (!expected.isBlank() && header.contains(expected)) {
+ return entry.getValue() == null ? "" : entry.getValue();
+ }
+ }
+ }
+ return "";
+ }
+
+ private String safeFileStem(String filename) {
+ String name = filename == null || filename.isBlank() ? "appearance-patent" : filename;
+ int idx = name.lastIndexOf('.');
+ if (idx > 0) {
+ name = name.substring(0, idx);
+ }
+ String safe = name.replaceAll("[\\\\/:*?\"<>|]", "_").trim();
+ return safe.isBlank() ? "appearance-patent" : safe;
+ }
+
+ private String writeJson(Object value, String message) {
+ try {
+ return objectMapper.writeValueAsString(value);
+ } catch (Exception ex) {
+ throw new BusinessException(message);
+ }
+ }
+
+ private void deleteTransientTaskPayloads(Long taskId) {
+ if (taskId == null || taskId <= 0) {
+ return;
+ }
+ List scopes = taskScopeStateMapper.selectList(new LambdaQueryWrapper()
+ .select(TaskScopeStateEntity::getParsedPayloadJson, TaskScopeStateEntity::getStateJson)
+ .eq(TaskScopeStateEntity::getTaskId, taskId)
+ .eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE));
+ if (scopes != null) {
+ for (TaskScopeStateEntity scope : scopes) {
+ transientPayloadStorageService.deletePayloadIfPresent(scope.getParsedPayloadJson());
+ transientPayloadStorageService.deletePayloadIfPresent(scope.getStateJson());
+ }
+ }
+ List chunks = taskChunkMapper.selectList(new LambdaQueryWrapper()
+ .select(TaskChunkEntity::getPayloadJson)
+ .eq(TaskChunkEntity::getTaskId, taskId)
+ .eq(TaskChunkEntity::getModuleType, MODULE_TYPE));
+ if (chunks != null) {
+ for (TaskChunkEntity chunk : chunks) {
+ transientPayloadStorageService.deletePayloadIfPresent(chunk.getPayloadJson());
+ }
+ }
+ }
+
+ private record ParsedWorkbook(int totalRows, int droppedRows, List headers, List acceptedRows, List allRows) {
+ }
+}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskProgressCacheService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskProgressCacheService.java
index be8759c..26f35bd 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskProgressCacheService.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskProgressCacheService.java
@@ -2,6 +2,7 @@ package com.nanri.aiimage.modules.brand.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.config.BrandProgressProperties;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@@ -11,6 +12,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
@Service
+@Slf4j
public class BrandTaskProgressCacheService {
public static final String PHASE_CRAWLING = "crawling";
@@ -53,8 +55,12 @@ public class BrandTaskProgressCacheService {
values.put("finished_files", String.valueOf(Math.max(finishedFiles, 0)));
values.put("updated_at", now);
values.put("last_heartbeat_at", now);
- stringRedisTemplate.opsForHash().putAll(key, values);
- stringRedisTemplate.expire(key, ttl());
+ try {
+ stringRedisTemplate.opsForHash().putAll(key, values);
+ stringRedisTemplate.expire(key, ttl());
+ } catch (Exception ex) {
+ log.warn("[brand-progress-cache] save progress degraded taskId={} msg={}", taskId, ex.getMessage());
+ }
}
public void updatePhase(Long taskId, String phase, int finishedFiles, int fileTotal) {
@@ -66,8 +72,12 @@ public class BrandTaskProgressCacheService {
values.put("file_total", String.valueOf(Math.max(fileTotal, 0)));
values.put("updated_at", now);
values.put("last_heartbeat_at", now);
- stringRedisTemplate.opsForHash().putAll(key, values);
- stringRedisTemplate.expire(key, ttl());
+ try {
+ stringRedisTemplate.opsForHash().putAll(key, values);
+ stringRedisTemplate.expire(key, ttl());
+ } catch (Exception ex) {
+ log.warn("[brand-progress-cache] update phase degraded taskId={} msg={}", taskId, ex.getMessage());
+ }
}
public void markFailed(Long taskId, String message) {
@@ -77,27 +87,49 @@ public class BrandTaskProgressCacheService {
values.put("phase", PHASE_FAILED);
values.put("updated_at", now);
values.put("error_message", blankToEmpty(message));
- stringRedisTemplate.opsForHash().putAll(key, values);
- stringRedisTemplate.expire(key, Duration.ofHours(brandProgressProperties.getFailedTtlHours()));
+ try {
+ stringRedisTemplate.opsForHash().putAll(key, values);
+ stringRedisTemplate.expire(key, Duration.ofHours(brandProgressProperties.getFailedTtlHours()));
+ } catch (Exception ex) {
+ log.warn("[brand-progress-cache] mark failed degraded taskId={} msg={}", taskId, ex.getMessage());
+ }
}
public Map