Skip to content

Commit 6ad3917

Browse files
CopilotLuSrackhall
andcommitted
Add formal contract documentation for signature endpoints (T010-T014)
Co-authored-by: LuSrackhall <142690689+LuSrackhall@users.noreply.github.com>
1 parent 4d07314 commit 6ad3917

File tree

4 files changed

+398
-5
lines changed

4 files changed

+398
-5
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Contract: POST /export/sign-bridge
2+
3+
## 描述
4+
导出流程中的签名桥接端点。将前端导出流程中选择的签名写入专辑配置的 `album_signatures`,并返回导出继续所需数据。
5+
6+
## 端点
7+
`POST /export/sign-bridge`
8+
9+
## 请求体
10+
```json
11+
{
12+
"albumId": "album-uuid-123",
13+
"signatureName": "Zhang San"
14+
}
15+
```
16+
17+
**字段说明:**
18+
- `albumId` (string, required): 专辑UUID或标识
19+
- `signatureName` (string, required): 签名名称
20+
21+
## 成功响应
22+
23+
### 200 OK
24+
```json
25+
{
26+
"ok": true,
27+
"signedAtAppended": "2025-10-02T16:45:00Z"
28+
}
29+
```
30+
31+
**字段说明:**
32+
- `ok` (boolean): 操作是否成功
33+
- `signedAtAppended` (string): 追加的签名时间戳(ISO8601 格式)
34+
35+
## 错误响应
36+
37+
### 400 Bad Request - 请求数据无效
38+
```json
39+
{
40+
"error": "invalid_request",
41+
"message": "Invalid request body: ..."
42+
}
43+
```
44+
45+
### 404 Not Found - 签名不存在
46+
```json
47+
{
48+
"error": "not_found",
49+
"message": "Signature not found"
50+
}
51+
```
52+
53+
### 404 Not Found - 专辑不存在
54+
```json
55+
{
56+
"error": "not_found",
57+
"message": "Album not found"
58+
}
59+
```
60+
61+
### 500 Internal Server Error - 内部错误
62+
```json
63+
{
64+
"error": "internal_error",
65+
"message": "Invalid signature_manager format"
66+
}
67+
```
68+
69+
## 处理流程
70+
71+
1. 接收并验证请求体(albumId, signatureName)
72+
2.`/store/get` 读取全局 `signature_manager`
73+
3. 在签名列表中查找匹配的签名(按 name 匹配)
74+
4. 如果签名不存在,返回 404 错误
75+
5. 获取当前时间戳(ISO8601 格式)
76+
6. (TODO Stage 1)通过 `/keytone_pkg/get` 获取专辑的 `album_signatures`
77+
7. (TODO Stage 1)合并/去重/排序签名时间戳到 `signedAt` 数组
78+
8. (TODO Stage 1)通过 `/keytone_pkg/set` 保存更新后的专辑配置
79+
9. 返回成功响应和时间戳
80+
81+
## 说明
82+
83+
### Stage 1 实现范围
84+
- 验证签名存在性
85+
- 生成并返回时间戳
86+
- 基础响应结构
87+
88+
### 后续增强(T026)
89+
- 实际写入专辑配置的 `album_signatures`
90+
- 实现时间戳数组的合并、去重、排序
91+
- 支持签名历史记录
92+
93+
### `album_signatures` 字段结构
94+
```json
95+
{
96+
"album_signatures": {
97+
"<encrypt(sha256(decrypt(protectCode) + name))>": "<encrypt(JSON_payload)>"
98+
}
99+
}
100+
```
101+
102+
**JSON_payload (明文结构,加密前):**
103+
```json
104+
{
105+
"name": "Zhang San",
106+
"intro": "My signature",
107+
"cardImagePath": "uuid.png",
108+
"signedAt": ["2025-10-01T10:00:00Z", "2025-10-02T16:45:00Z"],
109+
"authorization": {
110+
"authCode": "sha256...",
111+
"authorizedList": ["code1", "code2"]
112+
}
113+
}
114+
```
115+
116+
**字段说明:**
117+
- `signedAt` (string[]): 每次导出的时间戳数组,自动去重并按时间排序
118+
- `authorization` (object, optional): 仅在原始作者签名中包含,Stage 2 实现
119+
120+
### 合并规则(T026)
121+
1. 从专辑配置读取现有 `album_signatures`
122+
2. 解密对应签名的 key 和 value
123+
3. 获取现有 `signedAt` 数组
124+
4. 追加新时间戳
125+
5. 去重(相同时间戳只保留一个)
126+
6. 按时间排序(从早到晚)
127+
7. 加密并保存回专辑配置
128+
129+
## 使用场景
130+
131+
### 场景1: 首次签名
132+
- 专辑无任何签名记录
133+
- 导出时用户选择签名
134+
- 创建新的签名记录,`signedAt` 包含一个时间戳
135+
136+
### 场景2: 追加签名
137+
- 专辑已有该签名的记录
138+
- 导出时用户再次选择同一签名
139+
- 追加新时间戳到 `signedAt` 数组
140+
141+
### 场景3: 多人签名
142+
- 专辑已有其他人的签名
143+
- 当前用户选择自己的签名
144+
- 创建新的签名记录(不同的 key)
145+
146+
## 测试用例
147+
148+
### Happy Path - 首次签名
149+
```bash
150+
POST /export/sign-bridge
151+
Body: { albumId: "album-123", signatureName: "Zhang San" }
152+
Response: 200 OK with timestamp
153+
Verify: album_signatures contains new entry with signedAt: [timestamp]
154+
```
155+
156+
### Happy Path - 追加签名
157+
```bash
158+
POST /export/sign-bridge
159+
Body: { albumId: "album-123", signatureName: "Zhang San" }
160+
Response: 200 OK with new timestamp
161+
Verify: album_signatures entry has signedAt: [old_timestamp, new_timestamp]
162+
```
163+
164+
### Error Path - 签名不存在
165+
```bash
166+
POST /export/sign-bridge
167+
Body: { albumId: "album-123", signatureName: "NonExistent" }
168+
Response: 404 Not Found
169+
```
170+
171+
### Edge Case - 重复时间戳
172+
- 同一签名在极短时间内导出两次
173+
- 应该去重,`signedAt` 数组不包含重复时间戳
174+
175+
### Edge Case - 乱序时间戳
176+
- 手动修改配置导致时间戳乱序
177+
- 合并后应该自动排序
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Contract: POST /sdk/signatures/:name/export
2+
3+
## 描述
4+
导出指定签名为 .ktsign 文件格式。
5+
6+
## 端点
7+
`POST /sdk/signatures/:name/export`
8+
9+
## 路径参数
10+
- `name` (string, required): 签名名称
11+
12+
## 请求体
13+
```json
14+
{}
15+
```
16+
空对象
17+
18+
## 成功响应
19+
20+
### 200 OK
21+
```json
22+
{
23+
"fileNameSuggested": "My Signature.ktsign",
24+
"blobBase64": "SGVsbG8gV29ybGQ..."
25+
}
26+
```
27+
28+
**字段说明:**
29+
- `fileNameSuggested` (string): 建议的文件名
30+
- `blobBase64` (string): Base64 编码的加密签名数据
31+
32+
## 错误响应
33+
34+
### 400 Bad Request - 签名名称缺失
35+
```json
36+
{
37+
"error": "invalid_request",
38+
"message": "Signature name is required"
39+
}
40+
```
41+
42+
### 404 Not Found - 签名不存在
43+
```json
44+
{
45+
"error": "not_found",
46+
"message": "Signature not found"
47+
}
48+
```
49+
50+
### 500 Internal Server Error - 编码失败
51+
```json
52+
{
53+
"error": "encode_error",
54+
"message": "Failed to encode signature file: ..."
55+
}
56+
```
57+
58+
## 处理流程
59+
60+
1. 从路径参数获取签名名称
61+
2.`/store/get` 读取 `signature_manager`
62+
3. 在签名列表中查找匹配的签名(按 name 字段匹配)
63+
4. 构建签名文件载荷(SignatureFilePayload)
64+
5. 使用 XOR 加密和 Base64 编码生成 .ktsign 数据
65+
6. 返回文件名建议和 Base64 数据
66+
67+
## 说明
68+
69+
- 导出的 .ktsign 文件是二进制格式,内部为加密后的 JSON
70+
- JSON 结构等价于全局配置中的单一签名项(key/value 对)
71+
- 前端接收到 base64 数据后需解码为二进制并触发下载
72+
- 保护码(protectCode)在导出文件中加密存储,不可见
73+
74+
## 测试用例
75+
76+
### Happy Path
77+
```bash
78+
POST /sdk/signatures/Zhang%20San/export
79+
Response: 200 OK with valid base64 data
80+
```
81+
82+
### Error Path - 签名不存在
83+
```bash
84+
POST /sdk/signatures/NonExistent/export
85+
Response: 404 Not Found
86+
```
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Contract: POST /sdk/signatures/import
2+
3+
## 描述
4+
从 .ktsign 文件导入签名到签名管理器。
5+
6+
## 端点
7+
`POST /sdk/signatures/import`
8+
9+
## 请求体
10+
```json
11+
{
12+
"fileName": "My Signature.ktsign",
13+
"blobBase64": "SGVsbG8gV29ybGQ...",
14+
"overwrite": true
15+
}
16+
```
17+
18+
**字段说明:**
19+
- `fileName` (string, required): 文件名
20+
- `blobBase64` (string, required): Base64 编码的文件内容
21+
- `overwrite` (boolean, optional): 是否覆盖已存在的签名,默认 false
22+
23+
## 成功响应
24+
25+
### 201 Created
26+
```json
27+
{
28+
"name": "My Signature",
29+
"overwritten": false
30+
}
31+
```
32+
33+
**字段说明:**
34+
- `name` (string): 导入的签名名称
35+
- `overwritten` (boolean): 是否覆盖了已存在的签名
36+
37+
## 错误响应
38+
39+
### 400 Bad Request - 请求数据无效
40+
```json
41+
{
42+
"error": "invalid_request",
43+
"message": "Invalid request body: ..."
44+
}
45+
```
46+
47+
### 400 Bad Request - 文件格式无效(Base64解码失败)
48+
```json
49+
{
50+
"error": "invalid_format",
51+
"message": "invalid_format: failed to decode base64"
52+
}
53+
```
54+
55+
### 400 Bad Request - 文件格式无效(JSON解析失败)
56+
```json
57+
{
58+
"error": "invalid_format",
59+
"message": "invalid_format: failed to parse JSON"
60+
}
61+
```
62+
63+
### 400 Bad Request - 缺少必填字段
64+
```json
65+
{
66+
"error": "invalid_format",
67+
"message": "Missing or invalid name field"
68+
}
69+
```
70+
71+
### 409 Conflict - 签名已存在且未指定覆盖
72+
```json
73+
{
74+
"error": "exists_without_overwrite",
75+
"message": "Signature already exists"
76+
}
77+
```
78+
79+
## 处理流程
80+
81+
1. 接收并验证请求体(fileName, blobBase64)
82+
2. 解码 Base64 数据
83+
3. 解密并解析 JSON(获取 key/value 和可选 assets)
84+
4. 验证 JSON 结构和必填字段(name)
85+
5.`/store/get` 读取现有 `signature_manager`
86+
6. 检查签名是否已存在(按 name 匹配)
87+
7. 如果存在且未指定 overwrite,返回 409 错误
88+
8. 如果存在且指定 overwrite,删除旧条目
89+
9. 添加新签名到签名管理器
90+
10. 通过 `/store/set` 保存更新后的签名管理器
91+
11. 触发 SSE 刷新(自动通过 `/store/set` 实现)
92+
12. 返回导入结果
93+
94+
## 说明
95+
96+
- 签名名称(name)作为唯一标识
97+
- 保护码(protectCode)由前端在创建时生成,导入时保持不变
98+
- 如果签名已存在但 intro 或 cardImagePath 不同,需要用户确认是否覆盖
99+
- 导入成功后会自动触发 SSE 刷新,前端签名列表自动更新
100+
- 资源文件(如名片图片)的处理在 Stage 1 简化实现
101+
102+
## 测试用例
103+
104+
### Happy Path - 新建签名
105+
```bash
106+
POST /sdk/signatures/import
107+
Body: { fileName: "test.ktsign", blobBase64: "...", overwrite: false }
108+
Response: 201 Created with name and overwritten: false
109+
```
110+
111+
### Happy Path - 覆盖签名
112+
```bash
113+
POST /sdk/signatures/import
114+
Body: { fileName: "test.ktsign", blobBase64: "...", overwrite: true }
115+
Response: 201 Created with name and overwritten: true
116+
```
117+
118+
### Error Path - 签名已存在且未指定覆盖
119+
```bash
120+
POST /sdk/signatures/import
121+
Body: { fileName: "existing.ktsign", blobBase64: "...", overwrite: false }
122+
Response: 409 Conflict
123+
```
124+
125+
### Error Path - 无效的 Base64
126+
```bash
127+
POST /sdk/signatures/import
128+
Body: { fileName: "test.ktsign", blobBase64: "invalid!@#$", overwrite: false }
129+
Response: 400 Bad Request with invalid_format error
130+
```

0 commit comments

Comments
 (0)