chore: 初始开源发布

iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。

- Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件)
- 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、
  库名 xiujie_admin → iotlcm、哨兵名占位
- 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异,
  不破坏「校验 MSISDN 格式」等测试断言)
- 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像
  digest 记录
- Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23),
  移除写死的 git 凭据
- README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0
  LICENSE,LF 行尾 + .gitattributes

已知:3 个测试包在源仓同一提交上同样失败(client/upstream、
service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
MiFaZhan
2026-10-22 16:37:56 +08:00
commit 2bec257289
361 changed files with 71524 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
# Full-sync HTTP control API
The handler imports no queue or storage implementations. Inject the five
contract interfaces in `Dependencies`: `RunLister`, `RunReader`,
`FailurePager`, `ReplayRequester`, and `ActionReader`. The replay service owns durable
idempotency and execution; the HTTP layer does not cache requests or launch work.
Use `NewRunReader(live, archive, rounds)` to compose `LiveRunReader`,
`ArchivedRunReader`, and `RoundStatsReader` into a `RunReader`. Live metadata
wins and returns `source: "live"`; only `ErrNotFound` from live metadata permits
archive fallback, returning `source: "archive"`. Live storage errors and round
lookup errors propagate. Archive and round readers may be nil to disable those
lookups. A failure listing always calls `FailurePager` directly, never archives.
## Routes and responses
All routes require `Authorization: Bearer <configured token>`. Missing, wrong,
or ambiguous credentials return 401 with a `WWW-Authenticate` challenge. An
empty configured token is a startup configuration error. Responses use JSON and
`Cache-Control: no-store`.
| Method | Route | Success body |
| --- | --- | --- |
| GET | `/internal/v1/full-sync-runs?syncKind=&limit=` | 200 `contract.RunList` |
| GET | `/internal/v1/full-sync-runs/{runId}` | 200 `contract.RunView` |
| GET | `/internal/v1/full-sync-runs/{runId}/failures?cursor=&limit=` | 200 `contract.FailurePage` |
| POST | `/internal/v1/full-sync-runs/{runId}/replay` | 202 `contract.ReplayAccepted` |
| GET | `/internal/v1/full-sync-actions/{actionId}` | 200 `contract.ReplayAction` |
Run list results merge active Redis metadata with flat `run-<runId>.json`
archives under the configured archive root. Active Redis metadata wins when an
ID exists in both sources. `syncKind` accepts `traffic_usage` or `sim_status`;
`limit` defaults to 50 and is capped at 100.
Failure limits use the contract rules: absent, empty, or zero selects 100;
positive values up to 500 are accepted; other values are rejected. Repeated
cursor/limit parameters and malformed query encoding are rejected. Cursors are
opaque and passed unchanged after URL decoding. The pager validates cursor
contents and run binding. Empty pages serialize `items: []` and preserve
`nextCursor`; only an empty next cursor signals completion.
Replay requires a single `Idempotency-Key` header and a JSON body such as
`{"requestedBy":"operator"}`. The body is limited to 64 KiB, must contain one
JSON object with no unknown fields, and must satisfy `ReplayRequest.Validate`.
If supplied, Content-Type must be `application/json` (parameters are allowed).
The exact `requestedBy` value is preserved for fingerprinting. Repeated accepted
requests return the service's existing action/target and current status with
202. The adapter constructs `statusUrl` and the `Location` header from actionId.
All application errors use this envelope, without storage error details:
```json
{"error":{"code":"invalid_argument","message":"Invalid request"}}
```
| Condition | HTTP status | Error code |
| --- | --- | --- |
| Invalid path, query, header, body, or `ErrInvalidArgument` | 400 | `invalid_argument` |
| Missing/wrong bearer credentials | 401 | `unauthorized` |
| Unknown route/resource or `ErrNotFound` | 404 | `not_found` |
| Unsupported route method | 405 | `method_not_allowed` |
| `ErrConflict` (including changed idempotent request), `ErrLeaseLost` | 409 | `conflict` |
| `ErrFailureDetailsUnavailable` | 410 | `failure_details_unavailable` |
| Oversized replay body | 413 | `body_too_large` |
| `ErrBusy` | 503 | `busy` |
| Other service errors or unencodable service output | 500 | `internal_error` |
## Lifecycle
`NewHandler(token, deps)` can be mounted by an existing HTTP server. For an
independent listener, construct `NewServer(Config{Addr: address, BearerToken:
token}, deps)` and call `Start(workerCtx)`. `Start` binds synchronously and
returns address-in-use and other startup errors directly. `Addr()` exposes the
bound address, including an OS-assigned port when configured with port zero.
Call `Wait()` after Start, normally in the worker's supervision goroutine. It
returns serving or shutdown failures; it also returns startup failures if Start
failed. The server is single-use and rejects a second Start. Wait can have
multiple callers but must not be called before Start.
Canceling `workerCtx` closes the listener and drains active HTTP requests.
Active request contexts stay usable during the drain. `ShutdownTimeout` defaults
to ten seconds; if it expires, active connections are closed and Wait returns
an error wrapping `context.DeadlineExceeded`. Normal cancellation with a
successful drain returns nil. No worker wiring or configuration-file changes
are part of this package.
## Verification
```text
gofmt -l internal/fullsync/httpapi
go test ./internal/fullsync/...
go test ./internal/fullsync/httpapi -cover
go vet ./internal/fullsync/...
```
Tests use contract mocks and local HTTP listeners, requiring no Redis or archive
files. `go test -race ./internal/fullsync/...` additionally requires a supported
C compiler and CGO enabled.