Files
cmautobuy/admin/model/model.go
T
chengmaandClaude Opus 5 799ee33045 feat: 搭建 Admin 项目骨架(Go + Gin + html/template)
可运行的骨架:四个页面能打开、数据库自动建表、给 Client 的三个接口
按契约响应。业务逻辑留 35 处 TODO(骨架),每处标明要点和文档章节。

结构(对应 docs/admin/02-architecture.md §2/§3)
- handler/web + handler/api 分开:页面要 CSRF、出错渲染错误页;
  接口要认证、出错返回 JSON。混在一起迟早写错
- service 不认识 *gin.Context,方便不起服务器直接单测
- 只有 repository 能写 SQL
- 模板用 {{define "目录/名字"}},一次全部 ParseFS 进来不冲突
- 模板和静态资源用 //go:embed 打进二进制

已实测(Go 1.23.0,本机验证)
- go build / go vet / gofmt 全部通过
- 单文件产物 34MB,无需 cgo
- 四个页面 + 静态资源均 200,/ 正确 302 到 /shopee
- 建表 7 张 + 索引 10 个,user_version=1,二次启动不重复建表
- claim 带 X-Client-Id 返回 204(无任务可领),
  缺 X-Client-Id 返回 400 + 契约格式的 JSON 错误体

依赖版本被 Go 1.23.0 卡死,已钉住并写进文档
- gin v1.11.0        (v1.12.0 起要求 Go >= 1.25.0)
- modernc.org/sqlite v1.38.0(v1.40.0 起要求 >= 1.24.0,v1.48.0 起 >= 1.25.0)
- excelize v2.9.1    (尚未入 go.mod,写导入功能时再加)
直接 go get 不带版本会拉到最新版并报 requires go >= 1.25.0,
所以文档里的命令一律带版本号。要升依赖就得先升 Go。

一处主动调整:迁移语句从「一个字符串装多条 SQL」改成 [][]string 逐条执行。
database/sql 的 Exec 对一次多条语句的支持因驱动而异,拆开最稳妥,
报错还能精确到第几条。

说明:Gitea 尚未配置,本次无对应工单号。
admin/testdata/ 只有 README,脱敏小样本需人工制作。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:33:45 +08:00

171 lines
5.0 KiB
Go

// Package model 只放数据结构,不导入 Gin,也不导入数据库驱动。
//
// 这样领域概念才能被单元测试直接使用,不用起服务器、不用连数据库。
// 字段含义的权威定义在 docs/admin/03-data-model.md。
package model
// ---------- 蝦皮 ----------
// CollectStatus 是某个蝦皮商品对应的 PDD 商品数据采到没有。
// 取值见 docs/admin/01-requirements.md §6.1。
type CollectStatus string
const (
CollectNoLink CollectStatus = "no_link" // 未填 PDD 链接
CollectPending CollectStatus = "pending" // 已填链接,未发起采集
CollectCollecting CollectStatus = "collecting" // 采集中,不允许再建任务
CollectCollected CollectStatus = "collected" // 已采集
CollectFailed CollectStatus = "failed" // 采集失败,可重新采集
)
// ShopeeProduct 是蝦皮商品(商品级)。
//
// PddGoodsURL 和 PddData 是我们自己维护的,蝦皮报表里没有,
// Excel 导入时**绝不能覆盖**,见 docs/admin/03-data-model.md §3.3。
type ShopeeProduct struct {
GoodsID string
Title string
ShopeeStatus string
MainSKUCode string
PddGoodsURL string
PddGoodsID string
PddData string // 采集回来的 PDD 商品 JSON
CollectStatus CollectStatus
CollectError string
CollectedAt string
CreatedAt string
UpdatedAt string
}
// ShopeeSKU 是蝦皮的一个规格(SKU 级)。
//
// SpecRaw 是报表里的规格原文,例如「黑色,M【建議40-50公斤】」,
// **永远原样保留**。Color/Size/Advice 是尽力解析的结果,
// 解析失败时留空并把 ParseOK 置 false,不要瞎猜。
type ShopeeSKU struct {
SKUID string
GoodsID string
SpecRaw string
Color string
Size string
Advice string // 建议体重,如「40-50公斤」
ParseOK bool
SKUCode string
IsManual bool // 人工新增的,后续导入不得删除
CreatedAt string
UpdatedAt string
}
// ---------- 顺运宝 ----------
// SybOrder 是一张顺运宝货运单。
//
// PriceTwdCent 是**台币分**,跟采购任务的人民币价格上限没有换算关系,
// 不要互相赋值,见 docs/admin/01-requirements.md §7。
type SybOrder struct {
SybID string
OrderNo string
Title string
ShopeeGoodsID string
ShopeeSKUID string
Quantity int
PriceTwdCent int64
ImageURL string
SybData string // 完整货运单 JSON,原样保留
CreatedAt string
UpdatedAt string
}
// SKUMapping 是「蝦皮的这个规格 = 拼多多的那个规格」。
//
// 匹配一次以后可以复用:下次遇到同一个蝦皮 SKU 自动带出,
// 操作员只需确认。这是省人工的关键。
type SKUMapping struct {
ShopeeSKUID string
GoodsID string
PddOptions string // JSON,如 {"color":"黑色","size":"M码"}
MappedAt string
MappedBy string
}
// ---------- 任务 ----------
// TaskType 区分采集任务和采购任务。
type TaskType string
const (
TaskCollect TaskType = "collect"
TaskPurchase TaskType = "purchase"
)
// TaskStatus 是 **Admin 侧**的任务状态。
//
// 注意它和 Client 本地的 8 个状态是两套,不要混。
// Admin 看不到客户端执行到哪一步(没有心跳,是有意的),
// claimed 之后就只能等结果。
type TaskStatus string
const (
TaskPending TaskStatus = "pending" // 待分配
TaskAssigned TaskStatus = "assigned" // 待领取
TaskClaimed TaskStatus = "claimed" // 已领取,正在执行
TaskSucceeded TaskStatus = "succeeded" // 成功
TaskManualReview TaskStatus = "manual_review" // 需人工
TaskFailed TaskStatus = "failed" // 失败
TaskCancelled TaskStatus = "cancelled" // 已取消
)
// Task 是发给 Client 执行的一个任务。
//
// PddGoodsURL 必填——Client 那边是 NOT NULL,空了它执行不了。
// 采购任务的 Quantity 和 MaxPriceCent 也必填,这是价格保护,
// 见 docs/client/04-admin-api-contract.md §4。
type Task struct {
TaskID string
TaskType TaskType
Status TaskStatus
Version int
Priority int
AssignedClient string
ClaimedAt string
SybID string
OrderNo string
GoodsID string
ShopeeSKUID string
PddGoodsURL string
PddGoodsID string
PddOptions string // JSON,采购任务的目标规格
Quantity int
MaxPriceCent int64 // 人民币分
ResultData string
ErrorCode string
ErrorMessage string
FinishedAt string
CreatedAt string
UpdatedAt string
}
// ---------- 客户端 ----------
// Client 是一台执行任务的客户端。
//
// 注意**没有 Status 字段**:在线状态是算出来的,
// LastSeenAt 在 N 分钟内算在线,否则离线。
// 存成字段会和真实情况不同步。
type Client struct {
ClientID string
Name string
DeviceAddress string
Platform string
PddPackage string
Capabilities string
LastSeenAt string
CreatedAt string
UpdatedAt string
}