feat: gateway store.

This commit is contained in:
2026-05-01 20:29:53 +08:00
parent 942b0eeae1
commit cd490eea36
6 changed files with 105 additions and 1 deletions
+9 -1
View File
@@ -30,6 +30,7 @@ const (
analysisTypeHeartRateWithSteps = "heart_rate_with_steps" analysisTypeHeartRateWithSteps = "heart_rate_with_steps"
sourceUpload = "upload" sourceUpload = "upload"
sourceCloud = "cloud" sourceCloud = "cloud"
sourceWechat = "wechat"
) )
// readDocxContent 读取 .docx 文件并将其转换为结构化文本 // readDocxContent 读取 .docx 文件并将其转换为结构化文本
@@ -449,6 +450,13 @@ func resolveTeachingPlanContent(c *gin.Context, form *multipart.Form, source str
} }
content, err := readDocxContent(docxFiles[0]) content, err := readDocxContent(docxFiles[0])
return content, docxFiles[0].Size, err 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: case sourceCloud:
lessonPlanID := c.PostForm("lesson_plan_id") lessonPlanID := c.PostForm("lesson_plan_id")
if strings.TrimSpace(lessonPlanID) == "" { if strings.TrimSpace(lessonPlanID) == "" {
@@ -461,6 +469,6 @@ func resolveTeachingPlanContent(c *gin.Context, form *multipart.Form, source str
content, err := readDocxContentFromPath(fileRecord.FilePath) content, err := readDocxContentFromPath(fileRecord.FilePath)
return content, fileRecord.FileSize, err return content, fileRecord.FileSize, err
default: 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)
} }
} }
+2
View File
@@ -577,6 +577,8 @@ func translateSourceType(sourceType string) string {
switch strings.TrimSpace(strings.ToLower(sourceType)) { switch strings.TrimSpace(strings.ToLower(sourceType)) {
case "upload": case "upload":
return "上传文件" return "上传文件"
case "wechat":
return "微信分享"
case "cloud": case "cloud":
return "云端文件" return "云端文件"
default: default:
+60
View File
@@ -74,6 +74,47 @@ func (gc *GatewayAdminController) List(c *gin.Context) {
writeSuccess(c, http.StatusOK, "查询成功", items) 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 创建新网关 // Create 创建新网关
// POST /api/gateways // POST /api/gateways
func (gc *GatewayAdminController) Create(c *gin.Context) { func (gc *GatewayAdminController) Create(c *gin.Context) {
@@ -341,3 +382,22 @@ func respondGatewayLookupError(c *gin.Context, err error) {
} }
writeError(c, http.StatusInternalServerError, "查询网关时出错") 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
}
+7
View File
@@ -121,6 +121,7 @@ type regionStatisticsItem struct {
TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"` TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"`
TotalDurationMs int64 `json:"totalDurationMs"` TotalDurationMs int64 `json:"totalDurationMs"`
AvgDurationMs float64 `json:"avgDurationMs"` AvgDurationMs float64 `json:"avgDurationMs"`
AvgTotalCost float64 `json:"avgTotalCost"`
TotalOriginalFileSize int64 `json:"totalOriginalFileSize"` TotalOriginalFileSize int64 `json:"totalOriginalFileSize"`
TotalCompressedSize int64 `json:"totalCompressedSize"` TotalCompressedSize int64 `json:"totalCompressedSize"`
TotalCost float64 `json:"totalCost"` TotalCost float64 `json:"totalCost"`
@@ -272,6 +273,10 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
if r.Count > 0 { if r.Count > 0 {
avgDuration = float64(r.TotalDurationMs) / float64(r.Count) avgDuration = float64(r.TotalDurationMs) / float64(r.Count)
} }
avgTotalCost := float64(0)
if r.Count > 0 {
avgTotalCost = r.TotalCost / float64(r.Count)
}
kgName := "" kgName := ""
if regionID > 0 { if regionID > 0 {
kgName = kindergartenMap[regionID] kgName = kindergartenMap[regionID]
@@ -297,6 +302,7 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
TotalOutputSizeBytes: r.TotalOutputSizeBytes, TotalOutputSizeBytes: r.TotalOutputSizeBytes,
TotalDurationMs: r.TotalDurationMs, TotalDurationMs: r.TotalDurationMs,
AvgDurationMs: avgDuration, AvgDurationMs: avgDuration,
AvgTotalCost: avgTotalCost,
TotalOriginalFileSize: r.TotalOriginalFileSize, TotalOriginalFileSize: r.TotalOriginalFileSize,
TotalCompressedSize: r.TotalCompressedSize, TotalCompressedSize: r.TotalCompressedSize,
TotalCost: r.TotalCost, TotalCost: r.TotalCost,
@@ -343,6 +349,7 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
if overall.Count > 0 { if overall.Count > 0 {
overall.AvgDurationMs = float64(overall.TotalDurationMs) / float64(overall.Count) overall.AvgDurationMs = float64(overall.TotalDurationMs) / float64(overall.Count)
overall.AvgTotalCost = overall.TotalCost / float64(overall.Count)
} }
writeSuccess(c, http.StatusOK, "query success", gin.H{ writeSuccess(c, http.StatusOK, "query success", gin.H{
+25
View File
@@ -7,6 +7,31 @@ import (
"github.com/gin-gonic/gin" "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 { func RequireStepTrainingAccess() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
roleValue, exists := c.Get("role") roleValue, exists := c.Get("role")
+2
View File
@@ -135,6 +135,8 @@ func SetupRouter() *gin.Engine {
public.POST("/register", controllers.Register) public.POST("/register", controllers.Register)
public.POST("/login", controllers.Login) public.POST("/login", controllers.Login)
} }
v1.GET("/gateways/by-mac", middleware.JWTAuth(), middleware.RequireOperatorOrHigher(), gatewayController.GetByMACForUser)
auth := v1.Group("/auth") auth := v1.Group("/auth")
{ {
auth.GET("/token", deviceTokenHandler) auth.GET("/token", deviceTokenHandler)