From b3162c6d69fc62980f29e6609915fce1d583142c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sun, 20 Sep 2026 14:26:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- file/AGENTS.md | 1 + .../file/service/PdaPackageStorage.java | 3 +- .../resources/openapi/pda-releases-v1.yaml | 77 +++++++++++++++++++ scripts/gateway-route-contract.test.mjs | 5 +- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 file/src/main/resources/openapi/pda-releases-v1.yaml diff --git a/file/AGENTS.md b/file/AGENTS.md index 063fc77f7..7d16583d5 100644 --- a/file/AGENTS.md +++ b/file/AGENTS.md @@ -26,6 +26,7 @@ | 派送单三类资料 | `src/main/resources/openapi/delivery-files-v1.yaml`;依次为装车照片 `DELIVERY_LOADING_PHOTO`、BOL文件(含签名)`DELIVERY_BOL`、签收文件(POD签收/快递签收)`DELIVERY_RECEIPT`,按客户、派送内部ID、类别隔离,任意格式单文件50MiB;Lastleg持有分类绑定、保存、替换删除和历史可见性,按非空类别计数x/3,不强制必传或识别签名;File保留不可变对象与真实createdAt | | 下游 OCR | `client/WeightOcrClient.java` | | SQL/配置 | `src/main/resources/db/file.sql`、`application.yml` | +| 国内/海外 PDA 版本 | `controller/PdaReleaseController.java`、`service/PdaReleaseService.java`、`service/PdaPackageStorage.java`、`src/main/resources/openapi/pda-releases-v1.yaml`;平台上传草稿、发布和停发,匿名清单与包下载只暴露当前已发布/历史未停发文件;国内和海外 AppID、指针独立 | | 测试/验证 | `src/test/java/com/baiyue/file/`;`mvn -pl file -am test` | ## 修改约束 diff --git a/file/src/main/java/com/baiyue/file/service/PdaPackageStorage.java b/file/src/main/java/com/baiyue/file/service/PdaPackageStorage.java index 688664cc9..9f7f3df5f 100644 --- a/file/src/main/java/com/baiyue/file/service/PdaPackageStorage.java +++ b/file/src/main/java/com/baiyue/file/service/PdaPackageStorage.java @@ -42,7 +42,8 @@ public class PdaPackageStorage { Files.createDirectories(target.getParent()); file.transferTo(target); try (ZipFile zip = new ZipFile(target.toFile())) { - if (zip.size() == 0) throw new IOException("empty archive"); + String required = type.equals("apk") ? "AndroidManifest.xml" : "manifest.json"; + if (zip.getEntry(required) == null) throw new IOException("missing " + required); } return new Stored(key, Files.size(target), sha256(target)); } catch (IOException | IllegalStateException exception) { diff --git a/file/src/main/resources/openapi/pda-releases-v1.yaml b/file/src/main/resources/openapi/pda-releases-v1.yaml new file mode 100644 index 000000000..eae49d46c --- /dev/null +++ b/file/src/main/resources/openapi/pda-releases-v1.yaml @@ -0,0 +1,77 @@ +openapi: 3.0.3 +info: + title: PDA version releases + version: 1.0.0 + description: Domestic and overseas PDA packages have independent AppIDs and publication pointers. +paths: + /api/platform/v1/files/pda-releases/{app}: + parameters: + - $ref: '#/components/parameters/app' + get: + summary: List recent releases for one PDA application + security: [{ bearerAuth: [] }] + description: Requires platform:pda-release:list and PLATFORM membership. + responses: + '200': { description: ApiResponse containing release records and current flags } + post: + summary: Upload a draft APK or WGT (does not publish) + security: [{ bearerAuth: [] }] + description: Requires platform:pda-release:manage and PLATFORM membership. Maximum 200 MiB. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [packageType, versionName, versionCode, minAppVersionCode, file] + properties: + packageType: { type: string, enum: [apk, wgt] } + versionName: { type: string, example: 0.0.4 } + versionCode: { type: integer, minimum: 1 } + minAppVersionCode: { type: integer, minimum: 1 } + mandatory: { type: boolean } + releaseNotes: { type: string, maxLength: 1000 } + file: { type: string, format: binary } + responses: + '200': { description: ApiResponse containing a DRAFT release } + /api/platform/v1/files/pda-releases/{id}/publish: + post: + summary: Publish a draft and switch the current package pointer + security: [{ bearerAuth: [] }] + description: Requires platform:pda-release:manage and PLATFORM membership. + parameters: [{ $ref: '#/components/parameters/id' }] + responses: + '200': { description: ApiResponse containing the published release } + /api/platform/v1/files/pda-releases/{id}/withdraw: + post: + summary: Stop serving an active release + security: [{ bearerAuth: [] }] + description: Requires platform:pda-release:manage and PLATFORM membership. + parameters: [{ $ref: '#/components/parameters/id' }] + responses: + '200': { description: ApiResponse containing the withdrawn release } + /api/pda-updates/{app}/latest: + get: + summary: Anonymous update manifest for one PDA application + parameters: + - $ref: '#/components/parameters/app' + - { name: appid, in: query, required: true, schema: { type: string } } + - { name: platform, in: query, required: true, schema: { type: string, enum: [android] } } + - { name: appVersionCode, in: query, required: true, schema: { type: integer, minimum: 1 } } + - { name: wgtVersion, in: query, required: true, schema: { type: string } } + responses: + '200': + description: Plain JSON with update=false or an APK/WGT manifest including appid and minAppVersionCode. + /api/pda-updates/{app}/{id}/package: + get: + summary: Download a published package + parameters: [{ $ref: '#/components/parameters/app' }, { $ref: '#/components/parameters/id' }] + responses: + '200': { description: APK or WGT binary } + '404': { description: Draft or withdrawn package is not available } +components: + securitySchemes: + bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } + parameters: + app: { name: app, in: path, required: true, schema: { type: string, enum: [domestic, overseas] } } + id: { name: id, in: path, required: true, schema: { type: string, format: uuid } } diff --git a/scripts/gateway-route-contract.test.mjs b/scripts/gateway-route-contract.test.mjs index 585001bfb..75b40ca37 100644 --- a/scripts/gateway-route-contract.test.mjs +++ b/scripts/gateway-route-contract.test.mjs @@ -105,7 +105,10 @@ test('Controller prefixes route to their owning service; internal prefixes stay ? [overseas] : ['file', 'print'].includes(module) ? [domestic, overseas] : [domestic] for (const file of javaFiles(resolve(root, module, 'src/main/java'))) { for (const path of controllerPaths(readFileSync(file, 'utf8'), file)) { - for (const table of tables) verify(path, service, table, file) + // PDA release metadata lives in the domestic File control plane. Both + // PDA apps use the domestic Gateway for update checks and downloads. + const targetTables = path.startsWith('/api/pda-updates/') ? [domestic] : tables + for (const table of targetTables) verify(path, service, table, file) count++ } }