feat(osi): 实现 OSI JSON 传输层(T-003)
This commit is contained in:
+2
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
- [x] 初始化 Go module、单 `main.go`(`-mode server|deliver`)、`config/`(viper)。
|
- [x] 初始化 Go module、单 `main.go`(`-mode server|deliver`)、`config/`(viper)。
|
||||||
- [x] 落地 `osi/sign.go`(MD5 签名)+ 单测:用文档约定的 `ts/ask` 校验 `password` 形态(32 位小写)。
|
- [x] 落地 `osi/sign.go`(MD5 签名)+ 单测:用文档约定的 `ts/ask` 校验 `password` 形态(32 位小写)。
|
||||||
- [ ] 移植旧项目 `transport.go`/`http_client.go` → `osi/transport.go`(保留 SOCKS5/超时,去 cookiejar 与拟态头)。
|
- [x] 移植旧项目 `transport.go`/`http_client.go` → `osi/transport.go`(保留 SOCKS5/超时,去 cookiejar 与拟态头)。
|
||||||
- [ ] `osi/client.go` 的 `Call(serviceId, body, out)`:注入头+信封+发送+判码(成功码实测 `"01"`,按去前导零判定;`405` 可重试,见 docs/01 §1)。
|
- [ ] `osi/client.go` 的 `Call(serviceId, body, out)`:注入头+信封+发送+判码(成功码实测 `"01"`,按去前导零判定;`405` 可重试,见 docs/01 §1)。
|
||||||
- [ ] `contract/envelope.go` + `osi/codes.go`(serviceId 常量 + `pathOf` 路由)。
|
- [ ] `contract/envelope.go` + `osi/codes.go`(serviceId 常量 + `pathOf` 路由)。
|
||||||
- **验收**:对任一最简查询接口(如机构查询 CXJG00002)发真实请求,拿到 `code/message`。
|
- **验收**:对任一最简查询接口(如机构查询 CXJG00002)发真实请求,拿到 `code/message`。
|
||||||
@@ -93,3 +93,4 @@
|
|||||||
- 净效果:总复杂度显著下降,且代码意图清晰可交接。
|
- 净效果:总复杂度显著下降,且代码意图清晰可交接。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-07-06
|
- 日期:2026-07-06
|
||||||
- 阶段:**Phase 0 进行中**;T-001/T-002 已完成,Go module、CLI/config 骨架与 MD5 签名已建立
|
- 阶段:**Phase 0 进行中**;T-001/T-002/T-003 已完成,Go module、CLI/config 骨架、MD5 签名与 OSI 传输层已建立
|
||||||
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper
|
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper;OSI 传输支持超时和可选 SOCKS5 代理
|
||||||
- 生产代码:已有 `main.go`、`config/`、`osi/sign.go`、`go.mod`/`go.sum`;`contract/ mapping/ pipeline/` 等业务目录仍待后续任务建立
|
- 生产代码:已有 `main.go`、`config/`、`osi/sign.go`、`osi/transport.go`、`go.mod`/`go.sum`;`contract/ mapping/ pipeline/` 等业务目录仍待后续任务建立
|
||||||
- 联调现实:**JKDA00002 个人档案查询已用 Python 脚本打通真实沙箱**(`code="01"`),实测契约沉淀在 `docs/04 §8`
|
- 联调现实:**JKDA00002 个人档案查询已用 Python 脚本打通真实沙箱**(`code="01"`),实测契约沉淀在 `docs/04 §8`
|
||||||
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、`config.yaml.example` 加载、MD5 签名和头部组装
|
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、`config.yaml.example` 加载、MD5 签名、请求头组装、JSON POST 传输、超时配置和 SOCKS5 代理地址校验
|
||||||
- 标准启动路径:`./init.sh`(需要 bash/WSL 环境;当前 Windows 环境未安装 WSL,直接运行会失败)
|
- 标准启动路径:`./init.sh`(需要 bash/WSL 环境;当前 Windows 环境未安装 WSL,直接运行会失败)
|
||||||
- 标准验证路径:`go test ./...`、`go build ./...`
|
- 标准验证路径:`go test ./...`、`go build ./...`
|
||||||
- 当前 blocker:无硬 blocker。软阻塞:厂家侧 B1/B2/B4/B5 契约缺口(见 `docs/06`),只影响阶段 4,不阻塞阶段 0~3
|
- 当前 blocker:无硬 blocker。软阻塞:厂家侧 B1/B2/B4/B5 契约缺口(见 `docs/06`),只影响阶段 4,不阻塞阶段 0~3
|
||||||
@@ -22,8 +22,9 @@
|
|||||||
| `main.go` | 已有 | 单入口,解析 `-mode server|deliver` 与 `-config` |
|
| `main.go` | 已有 | 单入口,解析 `-mode server|deliver` 与 `-config` |
|
||||||
| `config/` | 已有 | viper 配置加载,含最小单测 |
|
| `config/` | 已有 | viper 配置加载,含最小单测 |
|
||||||
| `osi/sign.go` | 已有 | OSI 请求头 MD5 签名与 headers 组装 |
|
| `osi/sign.go` | 已有 | OSI 请求头 MD5 签名与 headers 组装 |
|
||||||
|
| `osi/transport.go` | 已有 | JSON POST 传输层;支持超时与可选 SOCKS5;不带 cookiejar/网页拟态头 |
|
||||||
| `config.yaml.example` | 已有 | 占位配置,不含真实凭据 |
|
| `config.yaml.example` | 已有 | 占位配置,不含真实凭据 |
|
||||||
| `go.mod` `go.sum` | 已有 | module `chis_osi`,依赖 viper |
|
| `go.mod` `go.sum` | 已有 | module `chis_osi`,依赖 viper 与 `golang.org/x/net/proxy` |
|
||||||
| `docs/` | 已有 | 设计文档集 01~06 + 本快照;`账号.txt` 本地留存不入库 |
|
| `docs/` | 已有 | 设计文档集 01~06 + 本快照;`账号.txt` 本地留存不入库 |
|
||||||
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
||||||
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
- 成功码:字符串 `"01"`(判定按去前导零 == `"1"`),见 `docs/01 §1`。
|
- 成功码:字符串 `"01"`(判定按去前导零 == `"1"`),见 `docs/01 §1`。
|
||||||
- 机构码分层:请求头 `orgCode`=18 位统信码 ≠ `manaUnitId`=9 位机构码 ≠ 12 位区划码,见 `docs/01 §3`。
|
- 机构码分层:请求头 `orgCode`=18 位统信码 ≠ `manaUnitId`=9 位机构码 ≠ 12 位区划码,见 `docs/01 §3`。
|
||||||
- `deviceSN` 查询可空。
|
- `deviceSN` 查询可空。
|
||||||
|
- OSI 代理语义:`socks5_proxy` 非空即只对 OSI 客户端走 SOCKS5,不做直连回退。
|
||||||
|
|
||||||
## 当前可运行内容
|
## 当前可运行内容
|
||||||
|
|
||||||
@@ -57,8 +59,8 @@ python3 scripts/query_health_record.py
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. T-003:`osi/transport.go` 传输层骨架,保留 SOCKS5/超时。
|
1. T-004:`osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go`。
|
||||||
2. T-004:`osi/client.go` + `contract/envelope.go` + `osi/codes.go`。
|
2. T-201:查询响应契约结构体(T-004 后)。
|
||||||
|
|
||||||
## 维护规则
|
## 维护规则
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ module chis_osi
|
|||||||
|
|
||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require github.com/spf13/viper v1.20.1
|
require (
|
||||||
|
github.com/spf13/viper v1.20.1
|
||||||
|
golang.org/x/net v0.34.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package osi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransportConfig struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
Socks5Proxy string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(config TransportConfig) (*Transport, error) {
|
||||||
|
timeout := config.Timeout
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = 20 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
roundTripper := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
if strings.TrimSpace(config.Socks5Proxy) != "" {
|
||||||
|
dialer, err := socks5Dialer(config.Socks5Proxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roundTripper.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
if contextDialer, ok := dialer.(proxy.ContextDialer); ok {
|
||||||
|
return contextDialer.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
return dialer.Dial(network, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Transport{client: &http.Client{Timeout: timeout, Transport: roundTripper}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) PostJSON(ctx context.Context, targetURL string, payload any, headers map[string]string) (int, []byte, error) {
|
||||||
|
body, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("marshal json payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("build request: %w", err)
|
||||||
|
}
|
||||||
|
for name, value := range headers {
|
||||||
|
req.Header.Set(name, value)
|
||||||
|
}
|
||||||
|
if req.Header.Get("Content-Type") == "" {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := t.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
raw, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return resp.StatusCode, nil, fmt.Errorf("read response body: %w", err)
|
||||||
|
}
|
||||||
|
return resp.StatusCode, raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func socks5Dialer(rawProxy string) (proxy.Dialer, error) {
|
||||||
|
proxyURL, err := normalizeSocks5Proxy(rawProxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, nil, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create socks5 dialer: %w", err)
|
||||||
|
}
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSocks5Proxy(rawProxy string) (*url.URL, error) {
|
||||||
|
rawProxy = strings.TrimSpace(rawProxy)
|
||||||
|
if !strings.Contains(rawProxy, "://") {
|
||||||
|
rawProxy = "socks5://" + rawProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(rawProxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse socks5 proxy: %w", err)
|
||||||
|
}
|
||||||
|
if proxyURL.Scheme != "socks5" && proxyURL.Scheme != "socks5h" {
|
||||||
|
return nil, fmt.Errorf("unsupported proxy scheme %q", proxyURL.Scheme)
|
||||||
|
}
|
||||||
|
if proxyURL.User != nil {
|
||||||
|
return nil, fmt.Errorf("socks5 proxy authentication is not supported yet")
|
||||||
|
}
|
||||||
|
if _, _, err := net.SplitHostPort(proxyURL.Host); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid socks5 proxy host %q: %w", proxyURL.Host, err)
|
||||||
|
}
|
||||||
|
return proxyURL, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package osi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTransportPostJSONSendsPayloadAndHeaders(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
t.Fatalf("method = %s, want POST", r.Method)
|
||||||
|
}
|
||||||
|
if r.Header.Get("Content-Type") != "application/json" {
|
||||||
|
t.Fatalf("Content-Type = %q", r.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
if r.Header.Get("orgCode") != "org-001" {
|
||||||
|
t.Fatalf("orgCode = %q", r.Header.Get("orgCode"))
|
||||||
|
}
|
||||||
|
for _, name := range []string{"Cookie", "Origin", "Referer", "X-Requested-With"} {
|
||||||
|
if got := r.Header.Get(name); got != "" {
|
||||||
|
t.Fatalf("unexpected browser/cookie header %s=%q", name, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload map[string]any
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
t.Fatalf("decode payload: %v", err)
|
||||||
|
}
|
||||||
|
if payload["serviceId"] != "JKDA00002" {
|
||||||
|
t.Fatalf("serviceId = %v", payload["serviceId"])
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
_, _ = w.Write([]byte(`{"code":"01"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
transport, err := NewTransport(TransportConfig{Timeout: time.Second})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewTransport: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, raw, err := transport.PostJSON(
|
||||||
|
context.Background(),
|
||||||
|
server.URL,
|
||||||
|
map[string]any{"serviceId": "JKDA00002"},
|
||||||
|
map[string]string{"Content-Type": "application/json", "orgCode": "org-001"},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PostJSON: %v", err)
|
||||||
|
}
|
||||||
|
if status != http.StatusAccepted {
|
||||||
|
t.Fatalf("status = %d, want %d", status, http.StatusAccepted)
|
||||||
|
}
|
||||||
|
if string(raw) != `{"code":"01"}` {
|
||||||
|
t.Fatalf("raw = %s", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTransportKeepsTimeout(t *testing.T) {
|
||||||
|
transport, err := NewTransport(TransportConfig{Timeout: 3 * time.Second})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewTransport: %v", err)
|
||||||
|
}
|
||||||
|
if transport.client.Timeout != 3*time.Second {
|
||||||
|
t.Fatalf("timeout = %s, want 3s", transport.client.Timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTransportRejectsInvalidSOCKS5Proxy(t *testing.T) {
|
||||||
|
if _, err := NewTransport(TransportConfig{Socks5Proxy: "missing-port"}); err == nil {
|
||||||
|
t.Fatal("NewTransport accepted invalid SOCKS5 proxy")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,3 +72,11 @@
|
|||||||
- 变更:`tasks.md` 把 Phase 1(字典)/Phase 2(档案) 重排为 Phase Q(查询)→M(映射)→D(字典+创建);`osi/jkda.go` 拆为 T-203(Find/FindRqbj) 与 T-206(Create/Update);`docs/05` 加执行顺序注记。任务 ID 不变。
|
- 变更:`tasks.md` 把 Phase 1(字典)/Phase 2(档案) 重排为 Phase Q(查询)→M(映射)→D(字典+创建);`osi/jkda.go` 拆为 T-203(Find/FindRqbj) 与 T-206(Create/Update);`docs/05` 加执行顺序注记。任务 ID 不变。
|
||||||
- 决策:查询档案(Find)不依赖字典,映射纯函数单测只需码表+注入假字典快照,只有真实创建闭环才需 T-101/T-103 字典反查——故把字典接口降到创建之前、查询之后。顺带把"Create 依赖字典缓存"从隐性依赖显式化到 T-206。
|
- 决策:查询档案(Find)不依赖字典,映射纯函数单测只需码表+注入假字典快照,只有真实创建闭环才需 T-101/T-103 字典反查——故把字典接口降到创建之前、查询之后。顺带把"Create 依赖字典缓存"从隐性依赖显式化到 T-206。
|
||||||
- 下一步:仍是 T-003(Phase 0 未变),Phase 0 完成后按 Q→M→D 领取。
|
- 下一步:仍是 T-003(Phase 0 未变),Phase 0 完成后按 Q→M→D 领取。
|
||||||
|
|
||||||
|
## 2026-07-06 T-003 OSI 传输层骨架
|
||||||
|
|
||||||
|
- 状态:DONE
|
||||||
|
- 变更:新增 `osi/transport.go` 与 `osi/transport_test.go`;传输层支持 JSON POST、请求超时、可选 SOCKS5 代理;不设置 Cookie/Origin/Referer/X-Requested-With 等网页拟态头;`tasks.md` 标记 T-003 完成;`docs/05` 勾选阶段 0 传输层项。
|
||||||
|
- 验证:先运行 `go test ./osi` 看到 `NewTransport`/`TransportConfig` 未定义的预期失败;实现后 `go test ./osi` 通过;`go test ./...` 通过;`go build ./...` 通过。
|
||||||
|
- 决策:`socks5_proxy` 支持 `host:port` 和 `socks5://host:port` 两种写法;非空时只配置 OSI 客户端代理,不做直连回退;传输层只返回 HTTP status 和原始响应体,业务判码留给 T-004 的 client/codes。
|
||||||
|
- 下一步:T-004(`osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go`)。
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| T-001 | 初始化 go module + `main.go` 双子命令骨架 + `config/`(viper) | - | `go build ./...` 通过;`-mode server\|deliver` 可解析;`config.yaml.example` 占位值就绪 | DONE |
|
| T-001 | 初始化 go module + `main.go` 双子命令骨架 + `config/`(viper) | - | `go build ./...` 通过;`-mode server\|deliver` 可解析;`config.yaml.example` 占位值就绪 | DONE |
|
||||||
| T-002 | `osi/sign.go` MD5 头签名 + 单测 | T-001 | 单测校验 `password=md5("ts=<ts>&ask=<ask>")` 32 位小写;与 Python 脚本(scripts/,本地)产出比对一致 | DONE |
|
| T-002 | `osi/sign.go` MD5 头签名 + 单测 | T-001 | 单测校验 `password=md5("ts=<ts>&ask=<ask>")` 32 位小写;与 Python 脚本(scripts/,本地)产出比对一致 | DONE |
|
||||||
| T-003 | `osi/transport.go`:从 chis_upload 移植传输层 | T-001 | 保留 SOCKS5/超时;去掉 cookiejar 与网页拟态头;单测或最小连通验证 | TODO |
|
| T-003 | `osi/transport.go`:从 chis_upload 移植传输层 | T-001 | 保留 SOCKS5/超时;去掉 cookiejar 与网页拟态头;单测或最小连通验证 | DONE |
|
||||||
| T-004 | `osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go` | T-002, T-003 | 信封为 `serviceId`+`uploadinfo{baseInfo,manageInfo,...}`(docs/04 §8);成功码按去前导零 == `"1"` 判定(实测 `"01"`,docs/01 §1);`405` 归类可重试 | TODO |
|
| T-004 | `osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go` | T-002, T-003 | 信封为 `serviceId`+`uploadinfo{baseInfo,manageInfo,...}`(docs/04 §8);成功码按去前导零 == `"1"` 判定(实测 `"01"`,docs/01 §1);`405` 归类可重试 | TODO |
|
||||||
| T-005 | 阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh` | T-004 | 用 JKDA00002(Python 已验证的同一查询)发真实请求拿到 `code="01"`;`./init.sh` 三命令替换完成且可运行 | TODO |
|
| T-005 | 阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh` | T-004 | 用 JKDA00002(Python 已验证的同一查询)发真实请求拿到 `code="01"`;`./init.sh` 三命令替换完成且可运行 | TODO |
|
||||||
|
|
||||||
@@ -79,3 +79,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user