Files
silk/server-go/internal/handler/inspection.go
T

137 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"silk-server-go/internal/middleware"
"silk-server-go/internal/model"
"silk-server-go/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// 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))
rg.GET("/inspections", middleware.RequirePermission(db, "inspection:read"), listInspections(db))
}
// currentUserID 从 JWT 上下文取用户 ID
func currentUserID(c *gin.Context) *string {
if user, ok := c.Get("user"); ok {
if m, ok := user.(map[string]interface{}); ok {
if s, ok := m["sub"].(string); ok && s != "" {
return &s
}
}
}
return nil
}
// createInspection 拍照巡检:图片存 S3 → 调 AI /detect → 写记录。
// 幂等:客户端传 Idempotency-Key 头时,重复请求返回已有记录。
func createInspection(db *gorm.DB, s3 *service.S3Service, ai *service.AIClient, bucket string) gin.HandlerFunc {
return func(c *gin.Context) {
idemKey := strings.TrimSpace(c.GetHeader("Idempotency-Key"))
roomID := strings.TrimSpace(c.PostForm("roomId"))
if idemKey != "" {
var exist model.InspectionRecord
if db.Where("idempotency_key = ?", idemKey).First(&exist).Error == nil {
c.JSON(http.StatusOK, exist)
return
}
}
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择图片文件(字段名 file"})
return
}
defer file.Close()
if err := validateImageFile(header.Filename, header.Size); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
imageBytes, err := io.ReadAll(file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取图片失败"})
return
}
key, ext, err := buildObjectKey("inspections", header.Filename)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
contentType, _ := imageContentType(ext)
if err := s3.EnsureBucket(bucket); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片存储不可用: " + err.Error()})
return
}
if err := s3.UploadImage(bucket, key, bytes.NewReader(imageBytes), contentType); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片上传失败: " + err.Error()})
return
}
imageURL := s3.Endpoint() + "/" + bucket + "/" + key
rec := model.InspectionRecord{
UserID: currentUserID(c),
ImageURL: &imageURL,
AIStatus: "done",
}
if roomID != "" {
rec.RoomID = &roomID
}
if idemKey != "" {
rec.IdempotencyKey = &idemKey
}
detRes, aiErr := ai.Detect(c.Request.Context(), imageBytes, header.Filename)
if aiErr != nil {
rec.AIStatus = "failed"
} else {
raw, _ := json.Marshal(detRes.Detections)
rec.Detections = raw
}
if err := db.Create(&rec).Error; err != nil {
// 并发幂等:唯一索引冲突时返回已有记录
if idemKey != "" {
var exist model.InspectionRecord
if db.Where("idempotency_key = ?", idemKey).First(&exist).Error == nil {
c.JSON(http.StatusOK, exist)
return
}
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建巡检记录失败"})
return
}
c.JSON(http.StatusCreated, rec)
}
}
// listInspections 巡检记录列表(roomId/limit 过滤)
func listInspections(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
q := db.Model(&model.InspectionRecord{})
if room := c.Query("roomId"); room != "" {
q = q.Where("room_id = ?", room)
}
limit := 50
if l, err := strconv.Atoi(c.Query("limit")); err == nil && l > 0 && l <= 200 {
limit = l
}
var list []model.InspectionRecord
q.Order("created_at DESC").Limit(limit).Find(&list)
c.JSON(http.StatusOK, list)
}
}