From 22b6179c850c35cf25f11385264dd9146efef6cc Mon Sep 17 00:00:00 2001 From: weijuesen Date: Wed, 12 Aug 2026 16:57:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(server-go):=20=E5=B7=A1=E6=A3=80=20roomId?= =?UTF-8?q?=20UUID=20=E6=A0=A1=E9=AA=8C=EF=BC=88400=EF=BC=89=EF=BC=9B?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-go/internal/handler/inspection.go | 12 ++++++++ server-go/internal/handler/inspection_test.go | 21 +++++++++++++ 变更记录.md | 12 ++++++++ 开发交接记录.md | 30 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 server-go/internal/handler/inspection_test.go diff --git a/server-go/internal/handler/inspection.go b/server-go/internal/handler/inspection.go index cb7fe58..812dbbd 100644 --- a/server-go/internal/handler/inspection.go +++ b/server-go/internal/handler/inspection.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io" "net/http" + "regexp" "strconv" "strings" @@ -16,6 +17,13 @@ import ( "gorm.io/gorm" ) +var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) + +// isUUID 判断字符串是否为标准 UUID 格式(roomId 等外键校验用) +func isUUID(s string) bool { + return uuidPattern.MatchString(s) +} + // RegisterInspectionRoutes 注册 AI 巡检路由 func RegisterInspectionRoutes(rg *gin.RouterGroup, db *gorm.DB, s3 *service.S3Service, ai *service.AIClient, imageBucket string) { rg.POST("/inspections", middleware.RequirePermission(db, "inspection:create"), createInspection(db, s3, ai, imageBucket)) @@ -88,6 +96,10 @@ func createInspection(db *gorm.DB, s3 *service.S3Service, ai *service.AIClient, AIStatus: "done", } if roomID != "" { + if !isUUID(roomID) { + c.JSON(http.StatusBadRequest, gin.H{"error": "roomId 不是合法的 UUID"}) + return + } rec.RoomID = &roomID } if idemKey != "" { diff --git a/server-go/internal/handler/inspection_test.go b/server-go/internal/handler/inspection_test.go new file mode 100644 index 0000000..fd6a3e7 --- /dev/null +++ b/server-go/internal/handler/inspection_test.go @@ -0,0 +1,21 @@ +package handler + +import "testing" + +func TestIsUUID(t *testing.T) { + valid := []string{ + "1271e550-ccf1-443e-8964-980c6047e2f0", + "00000000-0000-0000-0000-000000000000", + } + invalid := []string{"smoke-room", "", "not-a-uuid", "1271e550ccf1443e8964980c6047e2f0"} + for _, s := range valid { + if !isUUID(s) { + t.Errorf("%q 应为合法 UUID", s) + } + } + for _, s := range invalid { + if isUUID(s) { + t.Errorf("%q 不应被当作 UUID", s) + } + } +} diff --git a/变更记录.md b/变更记录.md index c576112..22acd6c 100644 --- a/变更记录.md +++ b/变更记录.md @@ -107,6 +107,18 @@ - 第五次部署:`sers=1`、`seasonal=4`、`diseases=10`(含 other 类别);回滚点见服务器 README(原位旧二进制 `server-go-linux.old-20260812-7`) - 待办:专家经验沉淀(P1)依赖专家会诊(#18)落地,暂缓 +### AI 巡检闭环后端(计划 #5 ai-service + #6 Go 对接) + +- `ai-service/`(新增):FastAPI + ONNX Runtime 检测服务骨架——`POST /detect`(multipart 图片 → 框/类别/置信度)、`GET /health`;`MockDetector`(默认,开发联调)+ `ONNXDetector`(`MODEL_MODE=onnx` 加载 `best.onnx`,YOLOv8 后处理含 letterbox/NMS);pytest 6 个用例(TDD 红→绿) +- `server-go/internal/service/ai_client.go`(新增):AI 服务 HTTP 客户端(multipart 上传、超时、非 200/非法 JSON 报错),httptest 3 个用例 +- `server-go/internal/model/inspection.go`(新增):`inspection_records` 表(图片 URL、检测结果 jsonb、风险字段预留、`idempotency_key` 唯一索引) +- `server-go/internal/handler/inspection.go`(新增):`POST/GET /api/v1/inspections`——图片存 S3(`silk-images/inspections/…`)→ 调 AI `/detect` → 写记录;**`Idempotency-Key` 头幂等**(重复请求返回已有记录,并发冲突兜底);`roomId` 非 UUID 返回 400 +- 权限:新增 `inspection:create`(farmer/admin/operator)、`inspection:read`(全角色读) +- 配置:`AI_SERVICE_BASE`(默认 `http://localhost:8000`);`.gitignore` 增加 ai-service Python 文件例外(原全局 `*.py` 规则) +- 部署:ai-service 部署至开发服务器 `/home/pan/ai-service`(:8000,mock 模式,`start.sh` 启动;服务器补装 `python3.10-venv`,pip 走清华镜像);Go 后端第六次部署(schema 变更前 pg_dump `silk-20260812-1635.dump`) +- 验证:ai-service 服务器 pytest 6/6;巡检冒烟——创建 201(mock 检测 healthy/0.95)→ 同 key 重试返回同记录 → 列表 1 条 → 图片 URL 200;非法 roomId 400 +- 回滚点:Go 旧二进制 `server-go-linux.old-20260812-9`;ai-service 停 uvicorn 即可;记录见服务器 README + ## 2026-07-17 安全与界面优化整改 ### 变更背景 diff --git a/开发交接记录.md b/开发交接记录.md index 3bf9a5a..50c234d 100644 --- a/开发交接记录.md +++ b/开发交接记录.md @@ -169,6 +169,36 @@ --- +## 2026-08-12 模块:AI 巡检闭环后端(计划 #5 + #6) + +### 做了什么 + +- `ai-service/`:FastAPI + ONNX Runtime 检测服务骨架(`GET /health`、`POST /detect` 返回框/类别/置信度)。默认 `MODEL_MODE=mock`(MockDetector 返回固定结果并校验图片可解析);`ONNXDetector` 已实现 YOLOv8 常见输出格式解析(letterbox + NMS),训练恢复后放 `models/best.onnx` 即切真实推理,接口不变。 +- Go 后端:AI client(`AIClient.Detect`,multipart 上传 + 超时/错误处理);`inspection_records` 表;`POST/GET /api/v1/inspections`——图片存 S3 `silk-images/inspections/` → 调 `/detect` → 写记录;`Idempotency-Key` 幂等;`roomId` 校验。 +- 权限:`inspection:create` / `inspection:read`;配置 `AI_SERVICE_BASE`。 + +### 思路与决策 + +- 按规格书 3.1:AI 服务只做检测(框/类别/置信度),**风险评分留在 Go 端**(#9 公式),职责单一; +- 幂等性:`/detect` 无状态天然幂等;`POST /inspections` 用客户端 `Idempotency-Key`(唯一索引)+ 并发冲突兜底,重试不会产生重复巡检记录; +- YOLO 训练挂起不阻塞:mock 模式先跑通端到端链路,模型就位后只改环境变量; +- 图片复用 `silk-images` 桶(`inspections/` 前缀),不新建桶; +- 踩坑记录:服务器缺 `python3.10-venv`(需 apt 安装);pip 直连慢,改用清华镜像;原 `.gitignore` 全局 `*.py` 规则会忽略 ai-service,已加例外。 + +### 验证与部署 + +- 本地:ai-service pytest 6/6;Go 新增 AI client 3 用例 + UUID 校验用例,`go test/build/vet` 通过; +- 服务器:ai-service 部署 `/home/pan/ai-service`(:8000,mock,`start.sh`),pytest 6/6,health 200;Go 第六次部署(schema 变更前 pg_dump `silk-20260812-1635.dump`); +- 冒烟:巡检创建 201(mock healthy/0.95)→ 同 key 重试返回同记录 → 列表 → 图片 URL 200;非法 roomId 400(测试数据已清理); +- 相关提交:`99137a6`、`b89ad5e`(ai-service + gitignore)、`210bd6c`(Go 对接);回滚见服务器 README(旧二进制 `server-go-linux.old-20260812-9`)。 + +### 说明 + +- 巡检风险分级(绿/黄/橙/红)与评分引擎属计划 #9,本期只落数据字段; +- 小程序拍照巡检 UI(#8)下一轮再做。 + +--- + ## 环境与踩坑备忘(新同事必读) - **VPN**:开发服务器经 NetBird 访问;服务 Running 后需 `netbird up`,登录走 SSO(管理端 `115.191.19.95:6680`)。