diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7868e54 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + + - name: Build + run: | + go install src.techknowlogick.com/xgo@latest + xgo -out 4uonline --targets=*/amd64 . + + - name: Archive Output + run: | + mkdir -p artifacts + mv 4uonline* artifacts/ + cp README.md artifacts/ + cp config.example.yaml artifacts/config.yaml + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-output + path: artifacts/ diff --git a/.golangci.yml b/.golangci.yml index 899a837..3d99cd8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -57,6 +57,9 @@ linters-settings: - name: function-length # 函数长度限制 severity: warning disabled: true + - name: cyclomatic # 圈复杂度 + severity: warning + disabled: true - name: cognitive-complexity # 认知复杂度 severity: warning disabled: false diff --git a/app/apiException/apiException.go b/app/apiException/apiException.go index 1b8b6d4..6776367 100644 --- a/app/apiException/apiException.go +++ b/app/apiException/apiException.go @@ -32,7 +32,7 @@ var ( ResourceNotFound = NewError(200511, log.LevelInfo, "访问的资源不存在") AdminKeyError = NewError(200512, log.LevelInfo, "管理员注册密钥错误") AdminAlreadyExisted = NewError(200513, log.LevelInfo, "管理员账号已存在") - UploadFileError = NewError(200514, log.LevelError, "上传文件失败") + UploadFileError = NewError(200514, log.LevelInfo, "上传文件失败") FileSizeExceedError = NewError(200515, log.LevelInfo, "文件大小超限") FileNotImageError = NewError(200516, log.LevelInfo, "上传的文件不是图片") 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/config/config.go b/app/config/config.go deleted file mode 100644 index 8a4366a..0000000 --- a/app/config/config.go +++ /dev/null @@ -1,69 +0,0 @@ -package config - -import ( - "context" - "errors" - "time" - - "4u-go/app/models" - "4u-go/config/database" - "4u-go/config/redis" - "gorm.io/gorm" -) - -// 上下文用于 Redis 操作 -var ctx = context.Background() - -// getConfig 从 Redis 获取配置,如果不存在则从数据库中获取,并缓存到 Redis -func getConfig(key string) string { - val, err := redis.GlobalClient.Get(ctx, key).Result() - if err == nil { - return val - } - print(err) - var config = &models.Config{} - database.DB.Model(models.Config{}).Where( - &models.Config{ - Key: key, - }).First(&config) - - redis.GlobalClient.Set(ctx, key, config.Value, 0) - return config.Value -} - -// setConfig 设置指定的配置项,如果不存在则创建新的配置。 -func setConfig(key, value string) error { - redis.GlobalClient.Set(ctx, key, value, 0) - var config models.Config - result := database.DB.Where("`key` = ?", key).First(&config) - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return result.Error - } - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - config = models.Config{ - Key: key, - Value: value, - UpdateTime: time.Now(), - } - result = database.DB.Create(&config) - } else { - config.Value = value - config.UpdateTime = time.Now() - result = database.DB.Updates(&config) - } - return result.Error -} - -// checkConfig 检查指定的配置项是否存在于 Redis 中。 -func checkConfig(key string) bool { - intCmd := redis.GlobalClient.Exists(ctx, key) - return intCmd.Val() == 1 -} - -func delConfig(key string) error { - redis.GlobalClient.Del(ctx, key) - res := database.DB.Where(&models.Config{ - Key: key, - }).Delete(models.Config{}) - return res.Error -} diff --git a/app/config/encrypt.go b/app/config/encrypt.go deleted file mode 100644 index ee8813f..0000000 --- a/app/config/encrypt.go +++ /dev/null @@ -1,24 +0,0 @@ -package config - -// encryptKey 是用于加密的配置键。 -const encryptKey = "encryptKey" - -// SetEncryptKey 设置加密密钥的值 -func SetEncryptKey(value string) error { - return setConfig(encryptKey, value) -} - -// GetEncryptKey 获取当前配置的加密密钥值 -func GetEncryptKey() string { - return getConfig(encryptKey) -} - -// IsSetEncryptKey 检查是否设置了加密密钥 -func IsSetEncryptKey() bool { - return checkConfig(encryptKey) -} - -// DelEncryptKey 删除加密密钥 -func DelEncryptKey() error { - return delConfig(encryptKey) -} diff --git a/app/config/init.go b/app/config/init.go deleted file mode 100644 index 196ffc6..0000000 --- a/app/config/init.go +++ /dev/null @@ -1,19 +0,0 @@ -package config - -// initKey 是用于初始化状态的配置键。 -const initKey = "initKey" - -// SetInit 设置初始化状态为 "True"。 -func SetInit() error { - return setConfig(initKey, "True") -} - -// ResetInit 设置初始化状态为 "False"。 -func ResetInit() error { - return setConfig(initKey, "False") -} - -// GetInit 获取当前的初始化状态。 -func GetInit() bool { - return getConfig(initKey) == "True" -} diff --git a/app/controllers/objectController/upload.go b/app/controllers/objectController/upload.go index 900ec9f..f1f424f 100644 --- a/app/controllers/objectController/upload.go +++ b/app/controllers/objectController/upload.go @@ -1,3 +1,4 @@ +//nolint:all package objectController import ( @@ -26,22 +27,21 @@ func UploadFile(c *gin.Context) { } uploadType := data.UploadType - fileHeader := data.File - // 获取文件流 + fileSize := data.File.Size file, err := data.File.Open() if err != nil { - apiException.AbortWithException(c, apiException.ServerError, err) + apiException.AbortWithException(c, apiException.UploadFileError, err) return } defer func(file multipart.File) { err := file.Close() if err != nil { - zap.L().Error("文件流关闭错误", zap.Error(err)) + zap.L().Warn("文件关闭错误", zap.Error(err)) } }(file) // 获取文件信息 - contentType, fileExt, err := objectService.GetFileInfo(file, fileHeader, uploadType) + contentType, fileExt, err := objectService.GetFileInfo(file, fileSize, uploadType) if errors.Is(err, objectService.ErrSizeExceeded) { apiException.AbortWithException(c, apiException.FileSizeExceedError, err) return @@ -50,25 +50,33 @@ func UploadFile(c *gin.Context) { apiException.AbortWithException(c, apiException.ParamError, err) return } - if errors.Is(err, objectService.ErrNotImage) { - apiException.AbortWithException(c, apiException.FileNotImageError, err) - return - } if err != nil { apiException.AbortWithException(c, apiException.ServerError, err) return } - _, err = file.Seek(0, io.SeekStart) - if err != nil { - apiException.AbortWithException(c, apiException.ServerError, err) - return + + var fileReader io.Reader = file + if uploadType == objectService.TypeImage { + reader, size, err := objectService.ConvertToWebP(file) + if err != nil { + if errors.Is(err, objectService.ErrNotImage) { + apiException.AbortWithException(c, apiException.FileNotImageError, err) + return + } + zap.L().Error("转换图片到 WebP 失败", zap.Error(err)) + } else { // 若转换成功则替代原文件 + fileReader = reader + fileSize = size + fileExt = ".webp" + contentType = "image/webp" + } } // 上传文件 objectKey := objectService.GenerateObjectKey(uploadType, fileExt) - objectUrl, err := objectService.PutObject(objectKey, file, fileHeader.Size, contentType) + objectUrl, err := objectService.PutObject(objectKey, fileReader, fileSize, contentType) if err != nil { - apiException.AbortWithException(c, apiException.UploadFileError, err) + apiException.AbortWithException(c, apiException.ServerError, err) return } 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/midwares/checkInit.go b/app/midwares/checkInit.go deleted file mode 100644 index 2004184..0000000 --- a/app/midwares/checkInit.go +++ /dev/null @@ -1,17 +0,0 @@ -package midwares - -import ( - "4u-go/app/apiException" - "4u-go/app/config" - "github.com/gin-gonic/gin" -) - -// CheckInit 中间件用于检查系统是否已初始化。 -func CheckInit(c *gin.Context) { - inited := config.GetInit() - if !inited { - apiException.AbortWithException(c, apiException.NotInit, nil) - return - } - c.Next() -} diff --git a/app/models/config.go b/app/models/config.go deleted file mode 100644 index 8cefd97..0000000 --- a/app/models/config.go +++ /dev/null @@ -1,11 +0,0 @@ -package models - -import "time" - -// Config 系统配置项的结构体 -type Config struct { - ID uint `gorm:"primaryKey"` // ID 是配置项的唯一标识 - Key string // Key 是配置项的键,必须唯一且不能为空 - Value string // Value 是配置项的值,不能为空 - UpdateTime time.Time `gorm:"comment:'设置时间';type:timestamp"` // UpdateTime 是配置项的最后更新时间 -} 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/objectService/objectService.go b/app/services/objectService/objectService.go index d0507d5..3eeabc0 100644 --- a/app/services/objectService/objectService.go +++ b/app/services/objectService/objectService.go @@ -1,12 +1,15 @@ package objectService import ( + "bytes" "errors" "fmt" + "io" "mime/multipart" - "strings" "time" + "github.com/chai2010/webp" + "github.com/disintegration/imaging" "github.com/dustin/go-humanize" "github.com/gabriel-vasile/mimetype" uuid "github.com/satori/go.uuid" @@ -23,15 +26,23 @@ var ( ErrNotImage = errors.New("file isn't a image") ) +const ( + // TypeImage 图片 + TypeImage = "image" + + // TypeAttachment 附件 + TypeAttachment = "attachment" +) + var uploadTypeLimits = map[string]int64{ - "public/image": humanize.MByte * 10, - "public/attachment": humanize.MByte * 100, + TypeImage: humanize.MByte * 10, + TypeAttachment: humanize.MByte * 100, } // GetFileInfo 获取文件基本信息 func GetFileInfo( file multipart.File, - fileHeader *multipart.FileHeader, + fileSize int64, uploadType string, ) ( contentType string, @@ -39,7 +50,7 @@ func GetFileInfo( err error, ) { // 检查文件大小 - if err = checkFileSize(uploadType, fileHeader.Size); err != nil { + if err = checkFileSize(uploadType, fileSize); err != nil { return "", "", err } @@ -48,12 +59,6 @@ func GetFileInfo( if err != nil { return "", "", err } - - // 检查是否为图像类型 - if uploadType == "public/image" && !strings.HasPrefix(mimeType, "image") { - return "", "", ErrNotImage - } - return mimeType, mimeExt, nil } @@ -80,5 +85,24 @@ func getFileTypeAndExt(file multipart.File) (mimeType string, mimeExt string, er if err != nil { return "", "", err } + _, err = file.Seek(0, io.SeekStart) + if err != nil { + return "", "", err + } return mime.String(), mime.Extension(), nil } + +// ConvertToWebP 将图片转换为 WebP 格式 +func ConvertToWebP(file multipart.File) (io.Reader, int64, error) { + img, err := imaging.Decode(file) + if err != nil { + return nil, 0, fmt.Errorf("%w: %w", ErrNotImage, err) + } + + var buf bytes.Buffer + err = webp.Encode(&buf, img, &webp.Options{Quality: 100}) + if err != nil { + return nil, 0, err + } + return bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil +} 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/services/userService/create.go b/app/services/userService/create.go index 0fd0b37..68702b5 100644 --- a/app/services/userService/create.go +++ b/app/services/userService/create.go @@ -33,7 +33,10 @@ func CreateStudentUser( StudentID: studentID, } - EncryptUserKeyInfo(user) + err = EncryptUserKeyInfo(user) + if err != nil { + return nil, err + } res := database.DB.Create(&user) return user, res.Error diff --git a/app/services/userService/getUser.go b/app/services/userService/getUser.go index a521801..3a75fc7 100644 --- a/app/services/userService/getUser.go +++ b/app/services/userService/getUser.go @@ -17,7 +17,10 @@ func GetUserByWechatOpenID(openid string) (*models.User, error) { return nil, result.Error } - DecryptUserKeyInfo(&user) + err := DecryptUserKeyInfo(&user) + if err != nil { + return nil, err + } return &user, nil } @@ -29,11 +32,14 @@ func GetUserByStudentID(sid string) (*models.User, error) { StudentID: sid, }, ).First(&user) - if result.Error != nil { return nil, result.Error } - DecryptUserKeyInfo(&user) + + err := DecryptUserKeyInfo(&user) + if err != nil { + return nil, err + } return &user, nil } @@ -49,6 +55,9 @@ func GetUserByID(id uint) (*models.User, error) { return nil, result.Error } - DecryptUserKeyInfo(&user) + err := DecryptUserKeyInfo(&user) + if err != nil { + return nil, err + } return &user, nil } diff --git a/app/services/userService/utils.go b/app/services/userService/utils.go index 63e8a87..7922419 100644 --- a/app/services/userService/utils.go +++ b/app/services/userService/utils.go @@ -1,24 +1,30 @@ package userService import ( - "4u-go/app/config" "4u-go/app/models" - "4u-go/app/utils" + "4u-go/app/utils/aes" ) // DecryptUserKeyInfo 解密用户信息 -func DecryptUserKeyInfo(user *models.User) { - key := config.GetEncryptKey() +func DecryptUserKeyInfo(user *models.User) error { if user.PhoneNum != "" { - slt := utils.AesDecrypt(user.PhoneNum, key) + slt, err := aes.Decrypt(user.PhoneNum) + if err != nil { + return err + } user.PhoneNum = slt[0 : len(slt)-len(user.StudentID)] } + return nil } // EncryptUserKeyInfo 加密用户信息 -func EncryptUserKeyInfo(user *models.User) { - key := config.GetEncryptKey() +func EncryptUserKeyInfo(user *models.User) error { if user.PhoneNum != "" { - user.PhoneNum = utils.AesEncrypt(user.PhoneNum+user.StudentID, key) + num, err := aes.Encrypt(user.PhoneNum + user.StudentID) + if err != nil { + return err + } + user.PhoneNum = num } + return nil } diff --git a/app/utils/aes/aes.go b/app/utils/aes/aes.go new file mode 100644 index 0000000..c7954ea --- /dev/null +++ b/app/utils/aes/aes.go @@ -0,0 +1,86 @@ +package aes + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "errors" + + "4u-go/config/config" +) + +var encryptKey []byte + +// Init 读入 AES 密钥配置 +func Init() error { + key := config.Config.GetString("aes.encryptKey") + if len(key) != 16 && len(key) != 24 && len(key) != 32 { + return errors.New("AES 密钥长度必须为 16、24 或 32 字节") + } + encryptKey = []byte(key) + return nil +} + +// Encrypt AES 加密 +func Encrypt(orig string) (string, error) { + origData := []byte(orig) + + // 分组秘钥 + block, err := aes.NewCipher(encryptKey) + if err != nil { + return "", err + } + + // 进行 PKCS7 填充 + blockSize := block.BlockSize() + origData = PKCS7Padding(origData, blockSize) + + // 使用 CBC 加密模式 + blockMode := cipher.NewCBCEncrypter(block, encryptKey[:blockSize]) + cryted := make([]byte, len(origData)) + blockMode.CryptBlocks(cryted, origData) + + // 使用 RawURLEncoding 编码为 Base64,适合放入 URL + return base64.RawURLEncoding.EncodeToString(cryted), nil +} + +// Decrypt AES 解密 +func Decrypt(cryted string) (string, error) { + // 解码 Base64 字符串 + crytedByte, err := base64.RawURLEncoding.DecodeString(cryted) + if err != nil { + return "", err + } + + // 分组秘钥 + block, err := aes.NewCipher(encryptKey) + if err != nil { + return "", err + } + + // CBC 模式解密 + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, encryptKey[:blockSize]) + orig := make([]byte, len(crytedByte)) + blockMode.CryptBlocks(orig, crytedByte) + + // 去除 PKCS7 填充 + orig = PKCS7UnPadding(orig) + + return string(orig), nil +} + +// PKCS7Padding 填充数据,使长度为 blockSize 的倍数 +func PKCS7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +// PKCS7UnPadding 去除填充 +func PKCS7UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} diff --git a/app/utils/aestools.go b/app/utils/aestools.go deleted file mode 100644 index b79307b..0000000 --- a/app/utils/aestools.go +++ /dev/null @@ -1,74 +0,0 @@ -package utils - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "encoding/base64" - "fmt" -) - -// AesEncrypt aes加密 -func AesEncrypt(orig string, key string) string { - // 转成字节数组 - origData := []byte(orig) - k := []byte(key) - - // 分组秘钥 - block, err := aes.NewCipher(k) - if err != nil { - panic(fmt.Sprintf("key 长度必须 16/24/32长度: %s", err.Error())) - } - // 获取秘钥块的长度 - blockSize := block.BlockSize() - // 补全码 - origData = PKCS7Padding(origData, blockSize) - // 加密模式 - blockMode := cipher.NewCBCEncrypter(block, k[:blockSize]) - // 创建数组 - cryted := make([]byte, len(origData)) - // 加密 - blockMode.CryptBlocks(cryted, origData) - // 使用RawURLEncoding 不要使用StdEncoding - // 不要使用StdEncoding 放在url参数中回导致错误 - return base64.RawURLEncoding.EncodeToString(cryted) -} - -// AesDecrypt aes解密 -func AesDecrypt(cryted string, key string) string { - // 使用RawURLEncoding 不要使用StdEncoding - // 不要使用StdEncoding 放在url参数中回导致错误 - crytedByte, _ := base64.RawURLEncoding.DecodeString(cryted) //nolint:errcheck - k := []byte(key) - - // 分组秘钥 - block, err := aes.NewCipher(k) - if err != nil { - panic(fmt.Sprintf("key 长度必须 16/24/32长度: %s", err.Error())) - } - // 获取秘钥块的长度 - blockSize := block.BlockSize() - // 加密模式 - blockMode := cipher.NewCBCDecrypter(block, k[:blockSize]) - // 创建数组 - orig := make([]byte, len(crytedByte)) - // 解密 - blockMode.CryptBlocks(orig, crytedByte) - // 去补全码 - orig = PKCS7UnPadding(orig) - return string(orig) -} - -// PKCS7Padding 补码 -func PKCS7Padding(ciphertext []byte, blocksize int) []byte { - padding := blocksize - len(ciphertext)%blocksize - padtext := bytes.Repeat([]byte{byte(padding)}, padding) - return append(ciphertext, padtext...) -} - -// PKCS7UnPadding 去码 -func PKCS7UnPadding(origData []byte) []byte { - length := len(origData) - unpadding := int(origData[length-1]) - return origData[:(length - unpadding)] -} 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/app/utils/server/server.go b/app/utils/server/server.go new file mode 100644 index 0000000..7545c24 --- /dev/null +++ b/app/utils/server/server.go @@ -0,0 +1,50 @@ +package server + +import ( + "context" + "errors" + "net/http" + "os" + "os/signal" + "time" + + "4u-go/config/redis" + "go.uber.org/zap" +) + +// Run 运行 http 服务器 +func Run(handler http.Handler, addr string) { + srv := &http.Server{ + Addr: addr, + Handler: handler, + ReadHeaderTimeout: 2 * time.Second, + } + + // 启动服务器协程 + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + zap.L().Fatal("Server Error Occurred", zap.Error(err)) + } + }() + + // 阻塞并监听结束信号 + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + zap.L().Info("Shutdown Server...") + + // 关闭服务器(5秒超时时间) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + zap.L().Error("Server Shutdown Failed", zap.Error(err)) + } + + // 关闭 Redis 客户端 + if err := redis.GlobalClient.Close(); err != nil { + zap.L().Error("Redis Client Shutdown Failed", zap.Error(err)) + } + + zap.L().Info("Server Closed") +} diff --git a/config.example.yaml b/config.example.yaml index 7c7c26e..3f7d8b4 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -29,6 +29,9 @@ user: admin: key: # 管理员密钥 +aes: # AES 密钥,长度必须为 16、24 或 32 字节 + encryptKey: + minio: # minio 存储配置 accessKey: # 用于身份验证的访问密钥 secretKey: # 用于身份验证的秘密密钥 diff --git a/config/database/migrations.go b/config/database/migrations.go index b7d9b46..e221050 100755 --- a/config/database/migrations.go +++ b/config/database/migrations.go @@ -7,7 +7,6 @@ import ( func autoMigrate(db *gorm.DB) error { return db.AutoMigrate( - &models.Config{}, &models.User{}, &models.Announcement{}, &models.Activity{}, @@ -15,5 +14,7 @@ func autoMigrate(db *gorm.DB) error { &models.Website{}, &models.College{}, &models.ContactViewRecord{}, + &models.Qrcode{}, + &models.Counter{}, ) } diff --git a/config/router/router.go b/config/router/router.go index 60d2521..b393465 100644 --- a/config/router/router.go +++ b/config/router/router.go @@ -7,6 +7,7 @@ import ( "4u-go/app/controllers/collegeController" "4u-go/app/controllers/lostAndFoundController" "4u-go/app/controllers/objectController" + "4u-go/app/controllers/qrcodeController" "4u-go/app/controllers/userController" "4u-go/app/controllers/websiteController" "4u-go/app/midwares" @@ -17,7 +18,7 @@ import ( func Init(r *gin.Engine) { const pre = "/api" - api := r.Group(pre, midwares.CheckInit) + api := r.Group(pre) { user := api.Group("/user") { @@ -72,6 +73,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") @@ -96,6 +106,7 @@ func Init(r *gin.Engine) { website.GET("/list", websiteController.GetWebsiteList) } + lostAndFound := api.Group("/lost-and-found") { lostAndFound.POST("", midwares.CheckLogin, lostAndFoundController.CreateLostAndFound) @@ -105,6 +116,11 @@ func Init(r *gin.Engine) { lostAndFound.GET("/latest", lostAndFoundController.GetLatestLostAndFound) lostAndFound.GET("/user", midwares.CheckLogin, lostAndFoundController.GetUserLostAndFoundStatus) lostAndFound.PUT("/user", midwares.CheckLogin, lostAndFoundController.UpdateLostAndFoundStatus) + } + + track := api.Group("/track") + { + track.GET("/qrcode/scan_count", qrcodeController.ScanCount) } } } diff --git a/docs/README.md b/docs/README.md index 5630ad5..b6b353f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,7 +33,9 @@ ### 如何参与开发 -1. 克隆该项目并切换到dev分支,然后切出自己的分支进行开发 +1. 安装`GCC`或`MinGW`并配置好环境([下载](http://tdm-gcc.tdragon.net/download)) + +2. 克隆该项目并切换到`dev`分支,然后切出自己的分支进行开发 ```shell git clone https://github.com/zjutjh/4UOnline-Go.git @@ -58,7 +60,7 @@ git reset --hard origin/dev git push --force ``` -2. 复制示例配置,并按注释要求填写配置文件(`user`配置询问部长团,并要提供个人学号) +3. 复制示例配置,并按注释要求填写配置文件(`user`配置询问部长团,并要提供个人学号) ```shell /* Linux */ @@ -68,32 +70,16 @@ cp config.example.yaml config.yaml copy config.example.yaml config.yaml ``` -在配置数据库后,向 config 表插入如下两条记录来完成初始化 - -| key | value | -|---|---| -| encryptKey | *16位的整数倍的字符串 | -| initKey | True | - -3. 启动程序 +4. 启动程序 ```shell go run main.go ``` -4. 每次提交 commit 前,先运行以下命令来格式化代码并检查规范(需要安装 [gci](https://github.com/daixiang0/gci) 和 [golangci-lint](https://golangci-lint.run/)) +5. 每次提交 commit 前,先运行以下命令来格式化代码并检查规范(需要安装 [gci](https://github.com/daixiang0/gci) 和 [golangci-lint](https://golangci-lint.run/)) ``` gofmt -w . gci write . -s standard -s default golangci-lint run --config .golangci.yml ``` - -5. 打包后端到服务器运行 - -``` -SET CGO_ENABLE=0 -SET GOOS=linux -SET GOARCH=amd64 -go build -o 4u main.go -``` diff --git a/go.mod b/go.mod index 1711940..42970bb 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module 4u-go go 1.22.9 require ( + github.com/chai2010/webp v1.1.1 + github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.1 github.com/gabriel-vasile/mimetype v1.4.6 github.com/gin-contrib/cors v1.7.2 @@ -12,7 +14,6 @@ require ( github.com/go-resty/resty/v2 v2.16.0 github.com/json-iterator/go v1.1.12 github.com/minio/minio-go/v7 v7.0.80 - github.com/pkg/errors v0.9.1 github.com/satori/go.uuid v1.2.0 github.com/silenceper/wechat/v2 v2.1.7 github.com/spf13/viper v1.19.0 @@ -77,6 +78,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/image v0.22.0 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect diff --git a/go.sum b/go.sum index 476e02c..ebbcc16 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= +github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -30,6 +32,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -159,8 +163,6 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -235,6 +237,9 @@ golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= +golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/main.go b/main.go index 2375e53..d8e8dd6 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,9 @@ package main import ( "4u-go/app/midwares" + "4u-go/app/utils/aes" "4u-go/app/utils/log" + "4u-go/app/utils/server" "4u-go/config/config" "4u-go/config/database" "4u-go/config/objectStorage" @@ -27,6 +29,9 @@ func main() { r.NoRoute(midwares.HandleNotFound) log.ZapInit() redis.Init() + if err := aes.Init(); err != nil { + zap.L().Fatal(err.Error()) + } if err := database.Init(); err != nil { zap.L().Fatal(err.Error()) } @@ -39,8 +44,5 @@ func main() { wechat.Init() router.Init(r) - err := r.Run(":" + config.Config.GetString("server.port")) - if err != nil { - zap.L().Fatal(err.Error()) - } + server.Run(r, ":"+config.Config.GetString("server.port")) } diff --git a/makefile b/makefile index 24878be..8a12a8e 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,5 @@ -# 设置环境变量 -CGO_ENABLED=0 -GOOS=linux -GOARCH=amd64 +# 开启CGO +CGO_ENABLED=1 # Go 文件 TARGET=main @@ -9,15 +7,15 @@ TARGET=main # 默认目标 all: build +# 检测 GCC +check-gcc: + @command -v gcc >/dev/null 2>&1 || { echo "Error: gcc is not installed. Please install gcc and try again."; exit 1; } + # 构建目标 build: + $(check-gcc) @echo "Building $(TARGET)..." - go build -o $(TARGET) $(TARGET).go - -# 编译为 Linux 目标 -build-linux: - @echo "Building $(TARGET) for $(GOOS)/$(GOARCH)..." - GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=$(CGO_ENABLED) go build -o $(TARGET) $(TARGET).go + go build -v -o $(TARGET) . # 清理生成的文件 clean: @@ -28,3 +26,11 @@ clean: run: build @echo "Running $(TARGET)..." ./$(TARGET) + +# 格式化代码并检查风格 +fmt: + @echo "Formatting Go files..." + gofmt -w . + gci write . -s standard -s default + @echo "Running Lints..." + golangci-lint run \ No newline at end of file