feat: project_type.
This commit is contained in:
+42
-3
@@ -36,7 +36,7 @@ func NewGatewayAdminController() *GatewayAdminController {
|
||||
}
|
||||
|
||||
// List 获取网关列表
|
||||
// GET /api/gateways?keyword=&isSold=&projectType=
|
||||
// GET /api/gateways?keyword=&isSold=&projectType=®ionId=
|
||||
func (gc *GatewayAdminController) List(c *gin.Context) {
|
||||
var items []models.Gateway
|
||||
query := gc.DB.Model(&models.Gateway{}).Order("region_id ASC, created_at DESC")
|
||||
@@ -57,6 +57,14 @@ func (gc *GatewayAdminController) List(c *gin.Context) {
|
||||
if projectType := c.Query("projectType"); projectType != "" {
|
||||
query = query.Where("project_type = ?", projectType)
|
||||
}
|
||||
if regionIDStr := strings.TrimSpace(c.Query("regionId")); regionIDStr != "" {
|
||||
regionID, err := strconv.ParseUint(regionIDStr, 10, 32)
|
||||
if err != nil || regionID == 0 {
|
||||
writeError(c, http.StatusBadRequest, "regionId参数无效")
|
||||
return
|
||||
}
|
||||
query = query.Where("region_id = ?", uint32(regionID))
|
||||
}
|
||||
|
||||
if err := query.Find(&items).Error; err != nil {
|
||||
writeError(c, http.StatusInternalServerError, "查询网关列表失败")
|
||||
@@ -78,6 +86,11 @@ func (gc *GatewayAdminController) Create(c *gin.Context) {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
projectType, err := gc.findGatewayProjectType(payload.ProjectType)
|
||||
if err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 处理售出逻辑:如果已售出但未提供时间,默认为当前时间 (2026-04-29)
|
||||
var soldAt *time.Time
|
||||
@@ -96,7 +109,7 @@ func (gc *GatewayAdminController) Create(c *gin.Context) {
|
||||
Name: strings.TrimSpace(payload.Name),
|
||||
RegionID: payload.RegionID,
|
||||
Location: strings.TrimSpace(payload.Location),
|
||||
ProjectType: strings.TrimSpace(payload.ProjectType),
|
||||
ProjectType: projectType.Code,
|
||||
IsSold: payload.IsSold,
|
||||
SoldAt: soldAt,
|
||||
}
|
||||
@@ -131,12 +144,17 @@ func (gc *GatewayAdminController) Update(c *gin.Context) {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
projectType, err := gc.findGatewayProjectType(payload.ProjectType)
|
||||
if err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
record.Name = strings.TrimSpace(payload.Name)
|
||||
record.RegionID = payload.RegionID
|
||||
record.Location = strings.TrimSpace(payload.Location)
|
||||
record.ProjectType = strings.TrimSpace(payload.ProjectType)
|
||||
record.ProjectType = projectType.Code
|
||||
record.IsSold = payload.IsSold
|
||||
|
||||
// 核心逻辑:处理售出时间
|
||||
@@ -196,6 +214,24 @@ func (gc *GatewayAdminController) findByID(id string) (models.Gateway, error) {
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (gc *GatewayAdminController) findGatewayProjectType(projectTypeCode string) (models.ProjectType, error) {
|
||||
var projectType models.ProjectType
|
||||
code := strings.TrimSpace(strings.ToLower(projectTypeCode))
|
||||
if code == "" {
|
||||
return projectType, errors.New("projectType is required")
|
||||
}
|
||||
if err := gc.DB.Where("code = ? AND is_active = ?", code, true).First(&projectType).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return projectType, errors.New("projectType is invalid")
|
||||
}
|
||||
return projectType, errors.New("failed to query project type")
|
||||
}
|
||||
if !projectType.SupportsGateway {
|
||||
return projectType, errors.New("current project type does not support gateways")
|
||||
}
|
||||
return projectType, nil
|
||||
}
|
||||
|
||||
// 辅助方法:验证输入数据
|
||||
func validateGatewayPayload(payload gatewayPayload) error {
|
||||
mac := strings.TrimSpace(payload.MAC)
|
||||
@@ -208,6 +244,9 @@ func validateGatewayPayload(payload gatewayPayload) error {
|
||||
if strings.TrimSpace(payload.Name) == "" {
|
||||
return errors.New("网关名称是必填项")
|
||||
}
|
||||
if strings.TrimSpace(payload.ProjectType) == "" {
|
||||
return errors.New("项目类型是必填项")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hr_receiver/config"
|
||||
"hr_receiver/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ProjectTypeAdminController struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
type projectTypePayload struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SupportsGateway bool `json:"supportsGateway"`
|
||||
IsActive bool `json:"isActive"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
func NewProjectTypeAdminController() *ProjectTypeAdminController {
|
||||
return &ProjectTypeAdminController{DB: config.DB}
|
||||
}
|
||||
|
||||
func (pc *ProjectTypeAdminController) List(c *gin.Context) {
|
||||
var items []models.ProjectType
|
||||
query := pc.DB.Model(&models.ProjectType{}).Order("sort ASC, id ASC")
|
||||
|
||||
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
|
||||
likeValue := "%" + keyword + "%"
|
||||
query = query.Where("code LIKE ? OR name LIKE ? OR description LIKE ?", likeValue, likeValue, likeValue)
|
||||
}
|
||||
if supportsGatewayStr := strings.TrimSpace(c.Query("supportsGateway")); supportsGatewayStr != "" {
|
||||
supportsGateway, err := strconv.ParseBool(supportsGatewayStr)
|
||||
if err != nil {
|
||||
writeError(c, http.StatusBadRequest, "invalid supportsGateway")
|
||||
return
|
||||
}
|
||||
query = query.Where("supports_gateway = ?", supportsGateway)
|
||||
}
|
||||
if isActiveStr := strings.TrimSpace(c.Query("isActive")); isActiveStr != "" {
|
||||
isActive, err := strconv.ParseBool(isActiveStr)
|
||||
if err != nil {
|
||||
writeError(c, http.StatusBadRequest, "invalid isActive")
|
||||
return
|
||||
}
|
||||
query = query.Where("is_active = ?", isActive)
|
||||
}
|
||||
|
||||
if err := query.Find(&items).Error; err != nil {
|
||||
writeError(c, http.StatusInternalServerError, "failed to query project types")
|
||||
return
|
||||
}
|
||||
writeSuccess(c, http.StatusOK, "query success", items)
|
||||
}
|
||||
|
||||
func (pc *ProjectTypeAdminController) Create(c *gin.Context) {
|
||||
var payload projectTypePayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateProjectTypePayload(payload); err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
record := models.ProjectType{
|
||||
Code: normalizeProjectTypeCode(payload.Code),
|
||||
Name: strings.TrimSpace(payload.Name),
|
||||
Description: strings.TrimSpace(payload.Description),
|
||||
SupportsGateway: payload.SupportsGateway,
|
||||
IsActive: payload.IsActive,
|
||||
Sort: payload.Sort,
|
||||
}
|
||||
if err := pc.DB.Create(&record).Error; err != nil {
|
||||
writeProjectTypeDBError(c, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(c, http.StatusCreated, "create success", record)
|
||||
}
|
||||
|
||||
func (pc *ProjectTypeAdminController) Update(c *gin.Context) {
|
||||
record, err := pc.findByID(c.Param("id"))
|
||||
if err != nil {
|
||||
respondProjectTypeLookupError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var payload projectTypePayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateProjectTypePayload(payload); err != nil {
|
||||
writeError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
record.Code = normalizeProjectTypeCode(payload.Code)
|
||||
record.Name = strings.TrimSpace(payload.Name)
|
||||
record.Description = strings.TrimSpace(payload.Description)
|
||||
record.SupportsGateway = payload.SupportsGateway
|
||||
record.IsActive = payload.IsActive
|
||||
record.Sort = payload.Sort
|
||||
|
||||
if err := pc.DB.Save(&record).Error; err != nil {
|
||||
writeProjectTypeDBError(c, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(c, http.StatusOK, "update success", record)
|
||||
}
|
||||
|
||||
func (pc *ProjectTypeAdminController) Delete(c *gin.Context) {
|
||||
record, err := pc.findByID(c.Param("id"))
|
||||
if err != nil {
|
||||
respondProjectTypeLookupError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var gatewayCount int64
|
||||
if err := pc.DB.Model(&models.Gateway{}).Where("project_type = ?", record.Code).Count(&gatewayCount).Error; err != nil {
|
||||
writeError(c, http.StatusInternalServerError, "failed to check gateway references")
|
||||
return
|
||||
}
|
||||
if gatewayCount > 0 {
|
||||
writeError(c, http.StatusConflict, "current project type is referenced by gateways")
|
||||
return
|
||||
}
|
||||
|
||||
if err := pc.DB.Delete(&record).Error; err != nil {
|
||||
writeError(c, http.StatusInternalServerError, "failed to delete project type")
|
||||
return
|
||||
}
|
||||
writeSuccess(c, http.StatusOK, "delete success", nil)
|
||||
}
|
||||
|
||||
func (pc *ProjectTypeAdminController) findByID(id string) (models.ProjectType, error) {
|
||||
var record models.ProjectType
|
||||
numericID, err := strconv.ParseUint(strings.TrimSpace(id), 10, 64)
|
||||
if err != nil {
|
||||
return record, gorm.ErrRecordNotFound
|
||||
}
|
||||
if err := pc.DB.First(&record, numericID).Error; err != nil {
|
||||
return record, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func validateProjectTypePayload(payload projectTypePayload) error {
|
||||
if normalizeProjectTypeCode(payload.Code) == "" {
|
||||
return errors.New("code is required")
|
||||
}
|
||||
if strings.TrimSpace(payload.Name) == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeProjectTypeCode(code string) string {
|
||||
return strings.TrimSpace(strings.ToLower(code))
|
||||
}
|
||||
|
||||
func respondProjectTypeLookupError(c *gin.Context, err error) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
writeError(c, http.StatusNotFound, "project type not found")
|
||||
return
|
||||
}
|
||||
writeError(c, http.StatusInternalServerError, "failed to query project type")
|
||||
}
|
||||
|
||||
func writeProjectTypeDBError(c *gin.Context, err error) {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "unique") {
|
||||
writeError(c, http.StatusConflict, "project type code already exists")
|
||||
return
|
||||
}
|
||||
writeError(c, http.StatusInternalServerError, "failed to persist project type")
|
||||
}
|
||||
@@ -30,6 +30,7 @@ func main() {
|
||||
&models.User{},
|
||||
&models.UserRegionBinding{},
|
||||
&models.Kindergarten{},
|
||||
&models.ProjectType{},
|
||||
&models.AppFile{},
|
||||
&models.AppFileShareCode{},
|
||||
&models.MqttHeartRateRecord{},
|
||||
@@ -49,6 +50,9 @@ func main() {
|
||||
if err := models.EnsureDefaultAIPricing(config.DB); err != nil {
|
||||
log.Printf("default ai pricing init failed: %v", err)
|
||||
}
|
||||
if err := models.EnsureDefaultProjectTypes(config.DB); err != nil {
|
||||
log.Printf("default project types init failed: %v", err)
|
||||
}
|
||||
|
||||
if err := mqtt.Start(config.DB, config.App.MQTT); err != nil {
|
||||
log.Printf("mqtt listener start failed: %v", err)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ProjectType struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Code string `gorm:"size:64;not null;uniqueIndex" json:"code"`
|
||||
Name string `gorm:"size:255;not null" json:"name"`
|
||||
Description string `gorm:"size:1024" json:"description"`
|
||||
SupportsGateway bool `gorm:"not null;default:false;index" json:"supportsGateway"`
|
||||
IsActive bool `gorm:"not null;default:true;index" json:"isActive"`
|
||||
Sort int `gorm:"not null;default:0;index" json:"sort"`
|
||||
CreatedAt int64 `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt int64 `gorm:"not null" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (ProjectType) TableName() string {
|
||||
return "project_types"
|
||||
}
|
||||
|
||||
func (p *ProjectType) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
now := time.Now().UnixMilli()
|
||||
p.Code = strings.TrimSpace(strings.ToLower(p.Code))
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
p.Description = strings.TrimSpace(p.Description)
|
||||
p.CreatedAt = now
|
||||
p.UpdatedAt = now
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProjectType) BeforeUpdate(tx *gorm.DB) (err error) {
|
||||
p.Code = strings.TrimSpace(strings.ToLower(p.Code))
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
p.Description = strings.TrimSpace(p.Description)
|
||||
p.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureDefaultProjectTypes(db *gorm.DB) error {
|
||||
defaults := []ProjectType{
|
||||
{Code: "flink", Name: "步频节拍器", Description: "Flink 步频节拍器项目", SupportsGateway: false, IsActive: true, Sort: 10},
|
||||
{Code: "light", Name: "心肺耐力测试", Description: "Light 心肺耐力测试项目", SupportsGateway: true, IsActive: true, Sort: 20},
|
||||
{Code: "heartrate", Name: "智能心率采集", Description: "智能心率采集项目", SupportsGateway: true, IsActive: true, Sort: 30},
|
||||
{Code: "run50", Name: "50米往返跑", Description: "50米往返跑项目", SupportsGateway: false, IsActive: true, Sort: 40},
|
||||
}
|
||||
|
||||
for _, item := range defaults {
|
||||
var existing ProjectType
|
||||
err := db.Where("code = ?", item.Code).First(&existing).Error
|
||||
switch {
|
||||
case err == nil:
|
||||
existing.Name = item.Name
|
||||
existing.Description = item.Description
|
||||
existing.SupportsGateway = item.SupportsGateway
|
||||
existing.IsActive = item.IsActive
|
||||
existing.Sort = item.Sort
|
||||
if err := db.Save(&existing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
if err := db.Create(&item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -15,6 +15,7 @@ func SetupRouter() *gin.Engine {
|
||||
stepTrainController := controllers.NewStepTrainingController()
|
||||
lessonPlanController := controllers.NewLessonPlanController()
|
||||
kindergartenAdminController := controllers.NewKindergartenAdminController()
|
||||
projectTypeAdminController := controllers.NewProjectTypeAdminController()
|
||||
userAdminController := controllers.NewUserAdminController()
|
||||
gatewayController := controllers.NewGatewayAdminController()
|
||||
systemDebugController := controllers.NewSystemDebugController()
|
||||
@@ -73,6 +74,11 @@ func SetupRouter() *gin.Engine {
|
||||
admin.PUT("/kindergartens/:id", kindergartenAdminController.Update)
|
||||
admin.DELETE("/kindergartens/:id", kindergartenAdminController.Delete)
|
||||
|
||||
admin.GET("/project-types", projectTypeAdminController.List)
|
||||
admin.POST("/project-types", projectTypeAdminController.Create)
|
||||
admin.PUT("/project-types/:id", projectTypeAdminController.Update)
|
||||
admin.DELETE("/project-types/:id", projectTypeAdminController.Delete)
|
||||
|
||||
admin.GET("/users", userAdminController.List)
|
||||
admin.POST("/users", userAdminController.Create)
|
||||
admin.PUT("/users/:id", userAdminController.Update)
|
||||
|
||||
Reference in New Issue
Block a user