diff --git a/app/common/counterName/counterName.go b/app/common/counterName/counterName.go new file mode 100644 index 0000000..0e0192d --- /dev/null +++ b/app/common/counterName/counterName.go @@ -0,0 +1,12 @@ +package counterName + +// 定义要统计的字段 + +const ( + // QrcodeScan 权益码扫描次数 + QrcodeScan string = "qrcode_scan" + // Feedback 问题反馈数量 + Feedback string = "feedback_scan" + // FeedbackHandle 处理问题反馈数量 + FeedbackHandle string = "feedback_handle" +) diff --git a/app/common/feedbackType/feedbackType.go b/app/common/feedbackType/feedbackType.go new file mode 100644 index 0000000..be4ea95 --- /dev/null +++ b/app/common/feedbackType/feedbackType.go @@ -0,0 +1,17 @@ +package feedbackType + +// FeedbackType +const ( + Activities uint = iota // 校园活动 + DiningAndShops // 食堂及商铺 + Dormitories // 宿舍 + Academic // 教学服务(选课、转专业等) + Facilities // 校园设施 + Classrooms // 教室 + Library // 图书馆 + Transportation // 交通 + Security // 安保 + HealthCare // 医疗服务 + Policies // 学院相关政策(如综测等) + Others // 其他服务 +) diff --git a/app/controllers/qrcodeController/count.go b/app/controllers/qrcodeController/count.go new file mode 100644 index 0000000..7ab2afa --- /dev/null +++ b/app/controllers/qrcodeController/count.go @@ -0,0 +1,33 @@ +package qrcodeController + +import ( + "errors" + + "4u-go/app/apiException" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type scanCountData struct { + ID uint `form:"id" binding:"required"` +} + +// ScanCount 更新权益码扫码次数 +func ScanCount(c *gin.Context) { + var data scanCountData + if err := c.ShouldBind(&data); err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + if err := qrcodeService.AddScanCount(data.ID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + apiException.AbortWithException(c, apiException.ResourceNotFound, nil) + } else { + apiException.AbortWithException(c, apiException.ServerError, err) + } + return + } + utils.JsonSuccessResponse(c, nil) +} diff --git a/app/controllers/qrcodeController/create.go b/app/controllers/qrcodeController/create.go new file mode 100644 index 0000000..bdf138a --- /dev/null +++ b/app/controllers/qrcodeController/create.go @@ -0,0 +1,43 @@ +package qrcodeController + +import ( + "4u-go/app/apiException" + "4u-go/app/models" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" +) + +type createQrcodeData struct { + College uint `json:"college"` + Department string `json:"department" binding:"required"` + Description string `json:"description"` + FeedbackType uint `json:"feedback_type"` + Location string `json:"location" binding:"required"` +} + +// CreateQrcode 创建一个权益码 +func CreateQrcode(c *gin.Context) { + var data createQrcodeData + err := c.ShouldBindJSON(&data) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + + err = qrcodeService.SaveQrcode(models.Qrcode{ + Status: true, + College: data.College, + Department: data.Department, + Description: data.Description, + FeedbackType: data.FeedbackType, + Location: data.Location, + }) + + if err != nil { + apiException.AbortWithException(c, apiException.ServerError, err) + return + } + + utils.JsonSuccessResponse(c, nil) +} diff --git a/app/controllers/qrcodeController/delete.go b/app/controllers/qrcodeController/delete.go new file mode 100644 index 0000000..9d78938 --- /dev/null +++ b/app/controllers/qrcodeController/delete.go @@ -0,0 +1,46 @@ +package qrcodeController + +import ( + "errors" + + "4u-go/app/apiException" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type deleteQrcodeData struct { + ID uint `json:"id"` +} + +// DeleteQrcode 删除一个权益码 +func DeleteQrcode(c *gin.Context) { + var data deleteQrcodeData + err := c.ShouldBindJSON(&data) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + + // 判断权益码是否存在 + _, err = qrcodeService.GetQrcodeById(data.ID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + apiException.AbortWithException(c, apiException.ResourceNotFound, err) + } else { + apiException.AbortWithException(c, apiException.ServerError, err) + } + return + } + + // 删除权益码 + err = qrcodeService.DeleteQrcodeById(data.ID) + + if err != nil { + apiException.AbortWithException(c, apiException.ServerError, err) + return + } + + utils.JsonSuccessResponse(c, nil) +} diff --git a/app/controllers/qrcodeController/get.go b/app/controllers/qrcodeController/get.go new file mode 100644 index 0000000..b1e381f --- /dev/null +++ b/app/controllers/qrcodeController/get.go @@ -0,0 +1,82 @@ +package qrcodeController + +import ( + "errors" + "time" + + "4u-go/app/apiException" + "4u-go/app/models" + "4u-go/app/services/collegeService" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type getQrcodeData struct { + ID uint `form:"id" binding:"required"` +} + +type qrcodeResp struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + FeedbackType uint `json:"feedback_type"` // 反馈类型 + College models.College `json:"college"` // 责任部门 + Department string `json:"department"` // 负责单位 + Location string `json:"location"` // 投放位置 + Status bool `json:"status"` // 状态(是否启用) + Description string `json:"description"` // 备注 + + ScanCount uint `json:"scan_count"` // 扫描次数 + FeedbackCount uint `json:"feedback_count"` // 反馈次数 +} + +// GetQrcode 获取权益码信息 +func GetQrcode(c *gin.Context) { + var data getQrcodeData + err := c.ShouldBind(&data) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + + qrcode, err := qrcodeService.GetQrcodeById(data.ID) + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + apiException.AbortWithException(c, apiException.ResourceNotFound, err) + } else { + apiException.AbortWithException(c, apiException.ServerError, err) + } + return + } + + resp, err := generateResp(qrcode) + if err != nil { + apiException.AbortWithException(c, apiException.ServerError, err) + return + } + + utils.JsonSuccessResponse(c, resp) +} + +func generateResp(qrcode models.Qrcode) (*qrcodeResp, error) { + college, err := collegeService.GetCollegeById(qrcode.College) + if err != nil { + return nil, err + } + + return &qrcodeResp{ + ID: qrcode.ID, + CreatedAt: qrcode.CreatedAt, + FeedbackType: qrcode.FeedbackType, + College: college, + Department: qrcode.Department, + Location: qrcode.Location, + ScanCount: qrcode.ScanCount, + Status: qrcode.Status, + + FeedbackCount: qrcode.FeedbackCount, + Description: qrcode.Description, + }, nil +} diff --git a/app/controllers/qrcodeController/getList.go b/app/controllers/qrcodeController/getList.go new file mode 100644 index 0000000..c57277d --- /dev/null +++ b/app/controllers/qrcodeController/getList.go @@ -0,0 +1,63 @@ +package qrcodeController + +import ( + "4u-go/app/apiException" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" +) + +type filter struct { + College []uint `json:"college"` + FeedbackType []uint `json:"feedback_type"` + Status bool `json:"status"` +} + +type getListData struct { + Keyword string `json:"keyword"` + Filter filter `json:"filter"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +type getListResponse struct { + QrcodeList []qrcodeResp `json:"qrcode_list"` + Total int64 `json:"total"` +} + +// GetList 实现了权益码列表的分页获取, 搜索, 筛选 +func GetList(c *gin.Context) { + var data getListData + err := c.ShouldBindJSON(&data) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + filter := data.Filter + + qrcodeListResp := make([]qrcodeResp, 0) + + qrcodeList, total, err := qrcodeService.GetList( + filter.College, + filter.FeedbackType, + filter.Status, + data.Keyword, data.Page, data.PageSize) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + + for _, qrcode := range qrcodeList { + resp, err := generateResp(qrcode) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + qrcodeListResp = append(qrcodeListResp, *resp) + } + + utils.JsonSuccessResponse(c, getListResponse{ + QrcodeList: qrcodeListResp, + Total: total, + }) +} diff --git a/app/controllers/qrcodeController/update.go b/app/controllers/qrcodeController/update.go new file mode 100644 index 0000000..d3cd86a --- /dev/null +++ b/app/controllers/qrcodeController/update.go @@ -0,0 +1,59 @@ +package qrcodeController + +import ( + "errors" + + "4u-go/app/apiException" + "4u-go/app/services/qrcodeService" + "4u-go/app/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type updateQrcodeData struct { + ID uint `json:"id" binding:"required"` + College uint `json:"college" binding:"required"` + Department string `json:"department" binding:"required"` + Description string `json:"description"` + FeedbackType uint `json:"feedback_type" binding:"required"` + Location string `json:"location" binding:"required"` + Status bool `json:"status"` +} + +// UpdateQrcode 更新权益码信息 +func UpdateQrcode(c *gin.Context) { + var data updateQrcodeData + err := c.ShouldBindJSON(&data) + if err != nil { + apiException.AbortWithException(c, apiException.ParamError, err) + return + } + + qrcode, err := qrcodeService.GetQrcodeById(data.ID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + apiException.AbortWithException(c, apiException.ResourceNotFound, err) + } else { + apiException.AbortWithException(c, apiException.ServerError, err) + } + return + } + + { // 更新权益码信息 + qrcode.College = data.College + qrcode.Department = data.Department + qrcode.Description = data.Description + qrcode.FeedbackType = data.FeedbackType + qrcode.Location = data.Location + qrcode.Status = data.Status + } + + err = qrcodeService.SaveQrcode(qrcode) + + if err != nil { + apiException.AbortWithException(c, apiException.ServerError, err) + return + } + + utils.JsonSuccessResponse(c, nil) +} diff --git a/app/models/counter.go b/app/models/counter.go new file mode 100644 index 0000000..59bdc33 --- /dev/null +++ b/app/models/counter.go @@ -0,0 +1,9 @@ +package models + +// Counter 用于记录每天相关字段的增量 +type Counter struct { + ID uint + Day int64 // 基于时间戳计算当前天数 `time.Now()/86400` + Name string // 统计的字段名 + Count int64 // 计数器 +} diff --git a/app/models/qrcode.go b/app/models/qrcode.go new file mode 100644 index 0000000..5346d28 --- /dev/null +++ b/app/models/qrcode.go @@ -0,0 +1,23 @@ +package models + +import ( + "database/sql" + "time" +) + +// Qrcode 权益码的结构体 +type Qrcode struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"-"` + DeletedAt sql.NullTime `json:"-" gorm:"index"` + FeedbackType uint `json:"feedback_type"` // 反馈类型 + College uint `json:"college"` // 责任部门 + Department string `json:"department"` // 负责单位 + Location string `json:"location"` // 投放位置 + Status bool `json:"status"` // 状态(是否启用) + Description string `json:"description"` // 备注 + + ScanCount uint `json:"scan_count"` // 扫描次数 + FeedbackCount uint `json:"feedback_count"` // 反馈次数 +} diff --git a/app/services/qrcodeService/addScanCount.go b/app/services/qrcodeService/addScanCount.go new file mode 100644 index 0000000..1c8ad66 --- /dev/null +++ b/app/services/qrcodeService/addScanCount.go @@ -0,0 +1,24 @@ +package qrcodeService + +import ( + "4u-go/app/common/counterName" + trackService "4u-go/app/services/trackService/counter" +) + +// AddScanCount 用于更新ScanCount +func AddScanCount(id uint) error { + // 1. 更新总量 + qrcode, err := GetQrcodeById(id) + if err != nil { + return err + } + qrcode.ScanCount++ + + // 2. 更新新增量 + err = trackService.AddCount(counterName.QrcodeScan) + if err != nil { + return err + } + + return SaveQrcode(qrcode) +} diff --git a/app/services/qrcodeService/delete.go b/app/services/qrcodeService/delete.go new file mode 100644 index 0000000..2da5090 --- /dev/null +++ b/app/services/qrcodeService/delete.go @@ -0,0 +1,12 @@ +package qrcodeService + +import ( + "4u-go/app/models" + "4u-go/config/database" +) + +// DeleteQrcodeById 通过 ID 删除一条公告 +func DeleteQrcodeById(qrcodeId uint) error { + result := database.DB.Where("id = ?", qrcodeId).Delete(&models.Qrcode{}) + return result.Error +} diff --git a/app/services/qrcodeService/get.go b/app/services/qrcodeService/get.go new file mode 100644 index 0000000..b72eb71 --- /dev/null +++ b/app/services/qrcodeService/get.go @@ -0,0 +1,13 @@ +package qrcodeService + +import ( + "4u-go/app/models" + "4u-go/config/database" +) + +// GetQrcodeById 获取指定ID的公告 +func GetQrcodeById(id uint) (qrcode models.Qrcode, err error) { + result := database.DB.Where("id = ?", id).First(&qrcode) + err = result.Error + return qrcode, err +} diff --git a/app/services/qrcodeService/getList.go b/app/services/qrcodeService/getList.go new file mode 100644 index 0000000..a02c6d7 --- /dev/null +++ b/app/services/qrcodeService/getList.go @@ -0,0 +1,49 @@ +package qrcodeService + +import ( + "4u-go/app/models" + dbUtils "4u-go/app/utils/database" + "4u-go/config/database" +) + +// GetList 获取权益码信息列表的筛选,搜索,分页 +// revive:disable:flag-parameter +func GetList( + collegeFilter []uint, + feedbackFilter []uint, + qrcodeStatus bool, + keyword string, + page int, pageSize int, +) (qrcodeList []models.Qrcode, total int64, err error) { + query := database.DB.Model(models.Qrcode{}) + + // 关键词搜索 + if len(keyword) > 0 { + query = query.Where("ID = ? "+ + "OR department LIKE ? "+ + "OR location LIKE ? "+ + "OR description LIKE ?", keyword, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") + } + + // 筛选`权益码状态` + if qrcodeStatus { + query = query.Where("status = ?", 1) + } + + // 筛选`责任部门` + if len(collegeFilter) > 0 { + query = query.Scopes(dbUtils.Filter("college", collegeFilter)) + } + + // 筛选`反馈类型` + if len(feedbackFilter) > 0 { + query = query.Scopes(dbUtils.Filter("feedback_type", feedbackFilter)) + } + + // 分页查找 + err = query.Count(&total). + Scopes(dbUtils.Paginate(page, pageSize)). + Find(&qrcodeList).Error + + return qrcodeList, total, err +} diff --git a/app/services/qrcodeService/save.go b/app/services/qrcodeService/save.go new file mode 100644 index 0000000..cc4bfaf --- /dev/null +++ b/app/services/qrcodeService/save.go @@ -0,0 +1,12 @@ +package qrcodeService + +import ( + "4u-go/app/models" + "4u-go/config/database" +) + +// SaveQrcode 向数据库中保存一条权益码记录 +func SaveQrcode(qrcode models.Qrcode) error { + result := database.DB.Save(&qrcode) + return result.Error +} diff --git a/app/services/trackService/counter/addCount.go b/app/services/trackService/counter/addCount.go new file mode 100644 index 0000000..3ef78ce --- /dev/null +++ b/app/services/trackService/counter/addCount.go @@ -0,0 +1,38 @@ +package trackService + +import ( + "errors" + "time" + + "4u-go/app/models" + "4u-go/config/database" + "gorm.io/gorm" +) + +// AddCount 用于更新某一字段在某一天的增量 +func AddCount(name string) error { + day := time.Now().Unix() / 86400 + counter, err := getCounter(name, day) + + if errors.Is(err, gorm.ErrRecordNotFound) { + // 记录不存在则创建 + counter.Name = name + counter.Day = day + counter.Count = 1 + return saveCounter(counter) + } + if err != nil { + return err + } + counter.Count++ + return saveCounter(counter) +} + +func getCounter(name string, day int64) (counter models.Counter, err error) { + err = database.DB.Where("name=? AND day=?", name, day).First(&counter).Error + return counter, err +} + +func saveCounter(counter models.Counter) error { + return database.DB.Save(&counter).Error +} diff --git a/app/utils/database/filter.go b/app/utils/database/filter.go new file mode 100644 index 0000000..93d142d --- /dev/null +++ b/app/utils/database/filter.go @@ -0,0 +1,14 @@ +package database + +import "gorm.io/gorm" + +// Filter 自定义筛选插件 +// Usage: db.Scopes(dbUtils.Filter("college", [1,2,3])) +// Desc: 筛选出"college"字段为1,2,3其中之一的字段 +// +// Spec: 使用泛型以接收所有类型的切片 +func Filter[T any](name string, choices []T) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where(name+" IN (?)", choices) + } +} diff --git a/app/utils/database/paginate.go b/app/utils/database/paginate.go new file mode 100644 index 0000000..3d71a59 --- /dev/null +++ b/app/utils/database/paginate.go @@ -0,0 +1,18 @@ +package database + +import "gorm.io/gorm" + +// Paginate 自定义分页插件 +// Usage: db.Scopes(dbUtils.Paginate(page, pageSize)) +func Paginate(page int, size int) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + pageSize := size + if pageSize > 20 { + pageSize = 20 + } else if pageSize <= 0 { + pageSize = 10 + } + offset := (page - 1) * pageSize + return db.Offset(offset).Limit(pageSize) + } +} diff --git a/config/database/migrations.go b/config/database/migrations.go index 6eed935..fc02db2 100755 --- a/config/database/migrations.go +++ b/config/database/migrations.go @@ -13,5 +13,7 @@ func autoMigrate(db *gorm.DB) error { &models.LostAndFoundRecord{}, &models.Website{}, &models.College{}, + &models.Qrcode{}, + &models.Counter{}, ) } diff --git a/config/router/router.go b/config/router/router.go index dc68570..fac8b3c 100644 --- a/config/router/router.go +++ b/config/router/router.go @@ -6,6 +6,7 @@ import ( "4u-go/app/controllers/announcementController" "4u-go/app/controllers/collegeController" "4u-go/app/controllers/objectController" + "4u-go/app/controllers/qrcodeController" "4u-go/app/controllers/userController" "4u-go/app/controllers/websiteController" "4u-go/app/midwares" @@ -65,6 +66,15 @@ func Init(r *gin.Engine) { adminWebsite.PUT("", websiteController.UpdateWebsite) adminWebsite.GET("/list", websiteController.GetEditableWebsites) } + + adminQrcode := admin.Group("/qrcode", midwares.CheckAdmin) + { + adminQrcode.POST("", qrcodeController.CreateQrcode) + adminQrcode.DELETE("", midwares.CheckSuperAdmin, qrcodeController.DeleteQrcode) + adminQrcode.GET("", qrcodeController.GetQrcode) + adminQrcode.POST("/list", qrcodeController.GetList) + adminQrcode.PUT("", qrcodeController.UpdateQrcode) + } } activity := api.Group("/activity") @@ -88,5 +98,10 @@ func Init(r *gin.Engine) { { website.GET("/list", websiteController.GetWebsiteList) } + + track := api.Group("/track") + { + track.GET("/qrcode/scan_count", qrcodeController.ScanCount) + } } } diff --git a/makefile b/makefile index b7d1723..8a12a8e 100644 --- a/makefile +++ b/makefile @@ -13,7 +13,7 @@ check-gcc: # 构建目标 build: - check-gcc + $(check-gcc) @echo "Building $(TARGET)..." go build -v -o $(TARGET) .