From cd490eea36199e516b615d4c04cd303eefa0f403 Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Fri, 1 May 2026 20:29:53 +0800 Subject: [PATCH] feat: gateway store. --- controllers/ai.go | 10 +++++- controllers/ai_analysis_pdf.go | 2 ++ controllers/gateway.go | 60 ++++++++++++++++++++++++++++++++++ controllers/statistics.go | 7 ++++ middleware/user_permission.go | 25 ++++++++++++++ routes/routes.go | 2 ++ 6 files changed, 105 insertions(+), 1 deletion(-) diff --git a/controllers/ai.go b/controllers/ai.go index 0a055f5..c537fb1 100644 --- a/controllers/ai.go +++ b/controllers/ai.go @@ -30,6 +30,7 @@ const ( analysisTypeHeartRateWithSteps = "heart_rate_with_steps" sourceUpload = "upload" sourceCloud = "cloud" + sourceWechat = "wechat" ) // readDocxContent 读取 .docx 文件并将其转换为结构化文本 @@ -449,6 +450,13 @@ func resolveTeachingPlanContent(c *gin.Context, form *multipart.Form, source str } content, err := readDocxContent(docxFiles[0]) return content, docxFiles[0].Size, err + case sourceWechat: + docxFiles := form.File["teaching_plan"] + if len(docxFiles) == 0 { + return "", 0, fmt.Errorf("Missing required file: teaching_plan (.docx)") + } + content, err := readDocxContent(docxFiles[0]) + return content, docxFiles[0].Size, err case sourceCloud: lessonPlanID := c.PostForm("lesson_plan_id") if strings.TrimSpace(lessonPlanID) == "" { @@ -461,6 +469,6 @@ func resolveTeachingPlanContent(c *gin.Context, form *multipart.Form, source str content, err := readDocxContentFromPath(fileRecord.FilePath) return content, fileRecord.FileSize, err default: - return "", 0, fmt.Errorf("invalid teaching_plan_source, expected %s or %s", sourceUpload, sourceCloud) + return "", 0, fmt.Errorf("invalid teaching_plan_source, expected %s, %s or %s", sourceUpload, sourceWechat, sourceCloud) } } diff --git a/controllers/ai_analysis_pdf.go b/controllers/ai_analysis_pdf.go index 64cc73e..2ab0672 100644 --- a/controllers/ai_analysis_pdf.go +++ b/controllers/ai_analysis_pdf.go @@ -577,6 +577,8 @@ func translateSourceType(sourceType string) string { switch strings.TrimSpace(strings.ToLower(sourceType)) { case "upload": return "上传文件" + case "wechat": + return "微信分享" case "cloud": return "云端文件" default: diff --git a/controllers/gateway.go b/controllers/gateway.go index 4a7daae..088b021 100644 --- a/controllers/gateway.go +++ b/controllers/gateway.go @@ -74,6 +74,47 @@ func (gc *GatewayAdminController) List(c *gin.Context) { writeSuccess(c, http.StatusOK, "查询成功", items) } +// GetByMACForUser 按 MAC 查询网关信息 +// GET /api/v1/gateways/by-mac?mac= +func (gc *GatewayAdminController) GetByMACForUser(c *gin.Context) { + macInput := strings.ToUpper(strings.TrimSpace(c.Query("mac"))) + if macInput == "" { + writeError(c, http.StatusBadRequest, "mac参数不能为空") + return + } + // 规范化:去掉冒号和横线,兼容不同存储格式 + macClean := strings.ReplaceAll(strings.ReplaceAll(macInput, ":", ""), "-", "") + + query := gc.DB.Where("REPLACE(REPLACE(UPPER(mac), ':', ''), '-', '') = ?", macClean) + + roleValue, _ := c.Get("role") + role, _ := roleValue.(models.UserRole) + if role != models.UserRoleSuperAdmin { + regionIDs, err := getUserRegionIDsFromContext(c) + if err != nil { + writeError(c, http.StatusForbidden, err.Error()) + return + } + if len(regionIDs) == 0 { + writeError(c, http.StatusForbidden, "当前用户未配置可访问区域") + return + } + query = query.Where("region_id IN ?", regionIDs) + } + + var item models.Gateway + if err := query.First(&item).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + writeError(c, http.StatusNotFound, "未找到该网关") + return + } + writeError(c, http.StatusInternalServerError, "查询网关失败") + return + } + + writeSuccess(c, http.StatusOK, "查询成功", item) +} + // Create 创建新网关 // POST /api/gateways func (gc *GatewayAdminController) Create(c *gin.Context) { @@ -341,3 +382,22 @@ func respondGatewayLookupError(c *gin.Context, err error) { } writeError(c, http.StatusInternalServerError, "查询网关时出错") } + +func getUserRegionIDsFromContext(c *gin.Context) ([]uint32, error) { + regionValue, exists := c.Get("regionIDs") + if !exists { + return nil, errors.New("missing user regions") + } + regionIDs, ok := regionValue.([]uint32) + if !ok { + return nil, errors.New("invalid user regions") + } + filtered := make([]uint32, 0, len(regionIDs)) + for _, regionID := range regionIDs { + if regionID == 0 { + continue + } + filtered = append(filtered, regionID) + } + return filtered, nil +} diff --git a/controllers/statistics.go b/controllers/statistics.go index d148d78..556a0d6 100644 --- a/controllers/statistics.go +++ b/controllers/statistics.go @@ -121,6 +121,7 @@ type regionStatisticsItem struct { TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"` TotalDurationMs int64 `json:"totalDurationMs"` AvgDurationMs float64 `json:"avgDurationMs"` + AvgTotalCost float64 `json:"avgTotalCost"` TotalOriginalFileSize int64 `json:"totalOriginalFileSize"` TotalCompressedSize int64 `json:"totalCompressedSize"` TotalCost float64 `json:"totalCost"` @@ -272,6 +273,10 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) { if r.Count > 0 { avgDuration = float64(r.TotalDurationMs) / float64(r.Count) } + avgTotalCost := float64(0) + if r.Count > 0 { + avgTotalCost = r.TotalCost / float64(r.Count) + } kgName := "" if regionID > 0 { kgName = kindergartenMap[regionID] @@ -297,6 +302,7 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) { TotalOutputSizeBytes: r.TotalOutputSizeBytes, TotalDurationMs: r.TotalDurationMs, AvgDurationMs: avgDuration, + AvgTotalCost: avgTotalCost, TotalOriginalFileSize: r.TotalOriginalFileSize, TotalCompressedSize: r.TotalCompressedSize, TotalCost: r.TotalCost, @@ -343,6 +349,7 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) { if overall.Count > 0 { overall.AvgDurationMs = float64(overall.TotalDurationMs) / float64(overall.Count) + overall.AvgTotalCost = overall.TotalCost / float64(overall.Count) } writeSuccess(c, http.StatusOK, "query success", gin.H{ diff --git a/middleware/user_permission.go b/middleware/user_permission.go index a6c6639..7ed588b 100644 --- a/middleware/user_permission.go +++ b/middleware/user_permission.go @@ -7,6 +7,31 @@ import ( "github.com/gin-gonic/gin" ) +func RequireOperatorOrHigher() gin.HandlerFunc { + return func(c *gin.Context) { + roleValue, exists := c.Get("role") + if !exists { + c.JSON(http.StatusForbidden, gin.H{"error": "missing user role"}) + c.Abort() + return + } + role, ok := roleValue.(models.UserRole) + if !ok { + c.JSON(http.StatusForbidden, gin.H{"error": "invalid user role"}) + c.Abort() + return + } + if role != models.UserRoleOperator && + role != models.UserRoleRegionAdmin && + role != models.UserRoleSuperAdmin { + c.JSON(http.StatusForbidden, gin.H{"error": "insufficient role permissions"}) + c.Abort() + return + } + c.Next() + } +} + func RequireStepTrainingAccess() gin.HandlerFunc { return func(c *gin.Context) { roleValue, exists := c.Get("role") diff --git a/routes/routes.go b/routes/routes.go index c69b4cd..6486ebe 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -135,6 +135,8 @@ func SetupRouter() *gin.Engine { public.POST("/register", controllers.Register) public.POST("/login", controllers.Login) } + v1.GET("/gateways/by-mac", middleware.JWTAuth(), middleware.RequireOperatorOrHigher(), gatewayController.GetByMACForUser) + auth := v1.Group("/auth") { auth.GET("/token", deviceTokenHandler)