From 68195a94f6520d5fac0c284a51f76388b6e6a4e6 Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 16 Jul 2026 13:57:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(submitter):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8F=90=E4=BA=A4=E6=A0=A1=E9=AA=8C=E4=B8=8E?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=8C=85=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 重构提交器的分类处理,移除硬编码的分类映射,改用后端返回的type字段 2. 添加categoryId同步逻辑,完善表单校验和提交数据 3. 优化图标处理逻辑,支持base64格式图标 4. 完善deb包元数据处理,自动生成规范的文件名 5. 新增更全面的提交字段校验和API业务错误处理 6. 补充安装脚本的目录复制规则 --- Makefile | 10 ++ electron/main/backend/submitter.ts | 147 ++++++++++++++++++++--------- src/components/SubmitterWindow.vue | 60 ++++++------ 3 files changed, 141 insertions(+), 76 deletions(-) diff --git a/Makefile b/Makefile index 47855bd7..54a7c2ea 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ install: mkdir -p $(DESTDIR)/usr/share/icons/ mkdir -p $(DESTDIR)/usr/lib/ mkdir -p $(DESTDIR)/usr/bin/ + mkdir -p $(DESTDIR)/etc/apt/ + mkdir -p $(DESTDIR)/lib/systemd/ + mkdir -p $(DESTDIR)/tmp/ cp -rv release/*/linux*-unpacked/* $(DESTDIR)/opt/spark-store/bin/ cp -rv release/*/linux*-unpacked/extras/* $(DESTDIR)/opt/spark-store/extras/ cp -rv tool/* $(DESTDIR)/opt/durapps/spark-store/bin/ @@ -39,5 +42,12 @@ install: cp -rv pkg/usr/share/applications/ $(DESTDIR)/usr/share/ cp -rv pkg/usr/share/polkit-1 $(DESTDIR)/usr/share/ cp -rv pkg/usr/share/aptss $(DESTDIR)/usr/share/ + cp -rv pkg/usr/share/ssinstall/ $(DESTDIR)/usr/share/ + cp -rv pkg/usr/share/ssinstall-local/ $(DESTDIR)/usr/share/ + cp -rv pkg/usr/share/dsg/ $(DESTDIR)/usr/share/ + cp -rv pkg/usr/share/bash-completion/ $(DESTDIR)/usr/share/ + cp -rv pkg/etc/apt/ $(DESTDIR)/etc/ + cp -rv pkg/tmp/spark-store-install/ $(DESTDIR)/tmp/ + cp -rv pkg/lib/systemd/ $(DESTDIR)/lib/ cp -rv tool/spark-store.asc $(DESTDIR)/opt/durapps/spark-store/bin/ ln -s ../../../spark-store/extras/spark-store $(DESTDIR)/opt/durapps/spark-store/bin/spark-store diff --git a/electron/main/backend/submitter.ts b/electron/main/backend/submitter.ts index 33321248..7e2da43e 100644 --- a/electron/main/backend/submitter.ts +++ b/electron/main/backend/submitter.ts @@ -45,25 +45,6 @@ interface OssUploadMetadata { }; } -const categoryNameToIdMap: Record = { - network: 3, - chat: 9, - music: 2, - video: 12, - image_graphics: 6, - games: 1, - office: 4, - reading: 8, - development: 7, - tools: 11, - themes: 10, - others: 5, -}; - -function getCategoryIdByName(categoryName: string): number { - return categoryNameToIdMap[categoryName]; -} - function generateUUID(): string { const hexChars = "0123456789abcdef"; let uuid = ""; @@ -1083,7 +1064,33 @@ export function registerSubmitterHandlers( ); let iconFilePath = iconPath; - if (iconPath.startsWith("http://") || iconPath.startsWith("https://")) { + if (iconPath.startsWith("data:")) { + logger.info("[Submitter] Icon is a Base64 data URL, decoding"); + const base64Data = iconPath.split(",")[1]; + if (!base64Data) { + return { success: false, message: "图标数据格式错误" }; + } + const tempDir = fs.mkdtempSync( + path.join(os.tmpdir(), "spark-store-submitter-"), + ); + iconFilePath = path.join(tempDir, "icon.png"); + try { + fs.writeFileSync(iconFilePath, Buffer.from(base64Data, "base64")); + logger.info( + { iconFilePath }, + "[Submitter] Icon decoded from data URL", + ); + } catch (err) { + logger.error({ err }, "[Submitter] Failed to decode icon data URL"); + return { + success: false, + message: `图标解码失败: ${(err as Error).message}`, + }; + } + } else if ( + iconPath.startsWith("http://") || + iconPath.startsWith("https://") + ) { logger.info( { iconPath }, "[Submitter] Icon is a remote URL, downloading first", @@ -1140,7 +1147,8 @@ export function registerSubmitterHandlers( if ( iconPath.startsWith("http://") || - iconPath.startsWith("https://") + iconPath.startsWith("https://") || + iconPath.startsWith("data:") ) { fs.unlinkSync(iconFilePath); fs.rmdirSync(path.dirname(iconFilePath)); @@ -1297,8 +1305,16 @@ export function registerSubmitterHandlers( const debFileStat = fs.statSync(debFilePath); + // 参考老 Qt 投稿器:根据 deb 元数据构造 file_name: {pkgname}_{version}_{arch}.deb + // arch 由前端解析 deb 时获取并传入,无需再次调用 dpkg-deb + const debArch = String(dataObj.arch || "amd64"); + const pkgVersion = String(dataObj.version || "0.0.0"); + const debPkgName = String(dataObj.pkgname || "unknown"); + const formFileName = `${debPkgName}_${pkgVersion}_${debArch}.deb`; + logger.info({ formFileName, debArch }, "[Submitter] Constructed file_name"); + const categoryName = String(dataObj.category || ""); - const categoryId = getCategoryIdByName(categoryName); + const categoryId = Number(dataObj.categoryId) || 0; logger.info( { categoryName, categoryId }, "[Submitter] Category name and ID", @@ -1313,55 +1329,80 @@ export function registerSubmitterHandlers( : []; logger.info({ tagsString, tagsArray }, "[Submitter] Tags conversion"); - const submitData = { - application_name: String(dataObj.pkgname || ""), + const remark = + ((dataObj.remark as string) || "") + + " - (来自于投稿器_v" + + getAppVersion() + + ")"; + + const submitData: Record = { + application_name: debPkgName, application_name_zh: String(dataObj.name || ""), contributor: String(dataObj.contributor || ""), icons: iconUrl, size: debFileStat.size, - file_name: path.basename(debFilePath).replace(/\s+/g, "_plus_"), + file_name: formFileName, website: String(dataObj.website || ""), - version: String(dataObj.version || ""), + version: pkgVersion, more: String(dataObj.description || ""), type_id: categoryId, author: String(dataObj.author || ""), - remark: - ((dataObj.remark as string) || "") + - " - (来自于投稿器_v" + - getAppVersion() + - ")", + remark, img_urls: screenshotUrls, deb_url: debUrl, mail: String(dataObj.mail || dataObj.contributor || ""), tags: tagsArray, + architecture: debArch, }; logger.info( "[Submitter] ============== VALIDATING SUBMISSION DATA ==============", ); - const requiredFields = [ - "application_name", - "application_name_zh", - "contributor", - "icons", - "size", - "file_name", - "version", - "type_id", - "author", - "deb_url", + // 对齐老 Qt 投稿器 isReadySubmit() 的完整校验 + const checks: Array<{ field: keyof typeof submitData; label: string }> = [ + { field: "application_name", label: "包名" }, + { field: "application_name_zh", label: "应用名称" }, + { field: "contributor", label: "贡献者" }, + { field: "icons", label: "图标URL" }, + { field: "size", label: "文件大小" }, + { field: "file_name", label: "文件名" }, + { field: "website", label: "官网地址" }, + { field: "version", label: "版本号" }, + { field: "more", label: "应用描述" }, + { field: "type_id", label: "分类ID" }, + { field: "author", label: "作者" }, + { field: "remark", label: "测试情况" }, + { field: "deb_url", label: "安装包URL" }, + { field: "mail", label: "联系邮箱" }, ]; - const missingFields = requiredFields.filter( - (field) => !submitData[field as keyof typeof submitData], - ); + const missingFields: string[] = []; + for (const { field, label } of checks) { + const val = submitData[field]; + if (val === undefined || val === null || val === "" || val === 0) { + missingFields.push(`${label}(${field})`); + } + } + if (screenshotUrls.length === 0) { + missingFields.push("截图(img_urls)"); + } + if (tagsArray.length === 0) { + missingFields.push("标签(tags)"); + } if (missingFields.length > 0) { logger.error({ missingFields }, "[Submitter] Missing required fields"); + return { + success: false, + message: `缺少必填字段: ${missingFields.join(", ")}`, + }; } logger.info( "[Submitter] ============== PREPARING SUBMISSION REQUEST ==============", ); logger.info({ submitData }, "[Submitter] Final submission data"); + logger.info( + `[Submitter] ============== FULL SUBMIT JSON ==============\n${JSON.stringify(submitData, null, 2)}`, + ); const submitterApiUrl = "https://upload.deepinos.org.cn/api/index/upload_application"; @@ -1471,6 +1512,22 @@ export function registerSubmitterHandlers( result = responseText; } + // 检查响应体中是否有业务错误码(部分 API HTTP 200 但业务失败) + if (result && typeof result === "object" && !Array.isArray(result)) { + const respObj = result as Record; + if (respObj.code !== undefined && respObj.code !== 0) { + logger.error( + { respCode: respObj.code, respMsg: respObj.msg }, + "[Submitter] API returned business error", + ); + return { + success: false, + message: String(respObj.msg || respObj.message || "提交失败"), + apiResponse: result, + }; + } + } + logger.info( "[Submitter] ============== SUBMISSION SUCCESSFUL ==============", ); diff --git a/src/components/SubmitterWindow.vue b/src/components/SubmitterWindow.vue index d8bbb226..2db193bd 100644 --- a/src/components/SubmitterWindow.vue +++ b/src/components/SubmitterWindow.vue @@ -559,7 +559,7 @@