updates
This commit is contained in:
parent
93bfb590d6
commit
ff2651c1eb
|
|
@ -2,6 +2,7 @@
|
|||
"kiroAgent.configureMCP": "Disabled",
|
||||
"cSpell.words": [
|
||||
"apikey",
|
||||
"bwmarrin",
|
||||
"Cognitio",
|
||||
"Fzby",
|
||||
"gonic",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
## server/common
|
||||
- `constants`: 存放全局常量,如 `RedisTokenPrefix`, `TokenHeader`, `StateActive` 等。
|
||||
- `id_utils`: ID 生成工具,提供 `GenerateLongID()` 和 `GenerateStringID()`。
|
||||
- `Response`: 统一的HTTP响应结构体 `{Code, Message, Data}`。
|
||||
- `Success(c *gin.Context, data interface{})`: 发送成功响应。
|
||||
- `Error(c *gin.Context, code int, msg string)`: 发送错误响应。
|
||||
|
|
@ -25,9 +26,10 @@
|
|||
- `YxVolunteerController`: 志愿控制器。
|
||||
- `YxVolunteerRecordController`: 志愿明细控制器。
|
||||
|
||||
## server/modules/user
|
||||
- `UserScoreService`: 用户成绩服务。
|
||||
- `GetActiveByID(userID string)`: 获取用户当前激活的成绩,返回 `UserScoreVO`。
|
||||
- `ListByUser(userID string, page, size int)`: 获取用户的所有成绩分页列表。
|
||||
- `SaveUserScore(req *dto.SaveScoreRequest)`: 保存用户成绩,处理旧记录状态更新及 DTO 转换。
|
||||
## server/modules/user/service
|
||||
- `UserScoreService`:
|
||||
- `GetActiveByID(userID string)`: 获取用户当前激活状态的成绩 VO。
|
||||
- `GetByID(id string)`: 根据 ID 获取特定成绩 VO。
|
||||
- `SaveUserScore(req *dto.SaveScoreRequest)`: 保存用户成绩,返回保存后的 VO。
|
||||
- `ListByUser(userID string, page, size int)`: 分页获取用户的成绩列表。
|
||||
- `UserScoreVO`: 用户成绩视图对象,包含基础信息、选课列表及子专业成绩映射。
|
||||
|
|
|
|||
|
|
@ -74,3 +74,22 @@
|
|||
- 重构了 `SysUserService`,使用 `common.RedisTokenPrefix` 和 `common.RedisTokenExpire`。
|
||||
- 重构了 `AuthMiddleware`,使用 `common.TokenHeader` 和 `common.ContextUserKey`。
|
||||
- 清理了 `common/context.go` 中的重复定义。
|
||||
|
||||
### [任务执行] 完善成绩回显逻辑
|
||||
- **操作目标**: 确保保存成功后能回显完整成绩信息,并提供通过 ID 获取成绩的接口以便修改。
|
||||
- **影响范围**: `server/modules/user/service/user_score_service.go`, `server/modules/user/controller/user_score_controller.go`
|
||||
- **修改前记录**: `SaveUserScore` 返回 Entity 而非 VO,且缺少通过 ID 获取特定成绩的接口。
|
||||
- **修改结果**:
|
||||
- 重构 `UserScoreService.SaveUserScore`,在事务提交后返回转换后的 `UserScoreVO`。
|
||||
- 增加 `UserScoreService.GetByID` 方法。
|
||||
- 在 `UserScoreController` 中注册 `GET /user/score/:id` 路由,并实现 `GetByID` 方法。
|
||||
- 将原来的 `GET /user/score` 路由方法名改为 `GetActive`,使其语义更明确。
|
||||
|
||||
### [任务执行] 封装 ID 生成工具
|
||||
- **操作目标**: 封装一个基于时间戳生成 long 类型(及字符串类型)ID 的工具类。
|
||||
- **影响范围**: `server/common/id_utils.go`, `server/modules/user/service/user_score_service.go`
|
||||
- **修改前记录**: 之前可能依赖数据库自增或未手动设置 ID。
|
||||
- **修改结果**:
|
||||
- 创建了 `server/common/id_utils.go`,提供了基于时间戳 + 序列号的 ID 生成逻辑。
|
||||
- 支持生成 `int64` 类型的 ID,也支持直接生成 `string` 类型以便于 Entity 使用。
|
||||
- 在 `UserScoreService.SaveUserScore` 中引入了该工具,手动为新记录分配 ID。
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
- `redis.go`: Redis连接配置。
|
||||
- `common/`: 通用工具包。
|
||||
- `constants.go`: 全局常量定义(Redis Key、业务状态等)。
|
||||
- `id_utils.go`: ID 生成工具(基于时间戳)。
|
||||
- `response.go`: 统一HTTP响应结构。
|
||||
- `context.go`: 上下文辅助函数。
|
||||
- `logger.go`: 日志工具。
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const (
|
|||
// "X-Access-Token"
|
||||
TokenHeader = "Authorization"
|
||||
// HeaderTokenPrefix Token前缀 (如有需要)
|
||||
HeaderTokenPrefix = ""
|
||||
HeaderTokenPrefix = "Bearer "
|
||||
)
|
||||
|
||||
// 业务状态常量
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IDGenerator 简单的 ID 生成器(类似 Snowflake)
|
||||
type IDGenerator struct {
|
||||
mu sync.Mutex
|
||||
lastTime int64
|
||||
sequence int64
|
||||
machineID int64
|
||||
}
|
||||
|
||||
var (
|
||||
defaultGenerator *IDGenerator
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// GetIDGenerator 获取默认生成器单例
|
||||
func GetIDGenerator() *IDGenerator {
|
||||
once.Do(func() {
|
||||
defaultGenerator = &IDGenerator{
|
||||
machineID: 1, // 默认机器 ID,实际可从配置读取
|
||||
}
|
||||
})
|
||||
return defaultGenerator
|
||||
}
|
||||
|
||||
// NextID 生成下一个 64 位整数 ID
|
||||
// 格式:时间戳(毫秒) + 机器ID + 序列号
|
||||
func (g *IDGenerator) NextID() int64 {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
if now == g.lastTime {
|
||||
g.sequence = (g.sequence + 1) & 4095 // 12位序列号
|
||||
if g.sequence == 0 {
|
||||
// 序列号溢出,等待下一毫秒
|
||||
for now <= g.lastTime {
|
||||
now = time.Now().UnixMilli()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g.sequence = 0
|
||||
}
|
||||
|
||||
g.lastTime = now
|
||||
|
||||
// 简单的位运算组合 ID
|
||||
// 时间戳(41位) | 机器ID(10位) | 序列号(12位)
|
||||
// 这里为了简单,直接返回时间戳+序列号的组合
|
||||
// 如果需要严格的 long 类型且包含时间戳信息:
|
||||
return now*10000 + g.sequence
|
||||
}
|
||||
|
||||
// NextIDStr 生成字符串类型的 ID
|
||||
func (g *IDGenerator) NextIDStr() string {
|
||||
return strconv.FormatInt(g.NextID(), 10)
|
||||
}
|
||||
|
||||
// GenerateLongID 全局辅助函数:生成 long 类型 ID
|
||||
func GenerateLongID() int64 {
|
||||
return GetIDGenerator().NextID()
|
||||
}
|
||||
|
||||
// GenerateStringID 全局辅助函数:生成 string 类型 ID
|
||||
func GenerateStringID() string {
|
||||
return GetIDGenerator().NextIDStr()
|
||||
}
|
||||
|
||||
// FormatTimestampToLong 将指定时间转为 long 格式 (yyyyMMddHHmmss)
|
||||
func FormatTimestampToLong(t time.Time) int64 {
|
||||
s := t.Format("20060102150405")
|
||||
id, _ := strconv.ParseInt(s, 10, 64)
|
||||
return id
|
||||
}
|
||||
|
||||
// GenerateTimestampLongID 生成当前时间的 long 格式 ID (yyyyMMddHHmmss + 3位随机/序列)
|
||||
func (g *IDGenerator) GenerateTimestampLongID() int64 {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
timestampStr := now.Format("20060102150405")
|
||||
|
||||
nowMs := now.UnixMilli()
|
||||
if nowMs/1000 == g.lastTime/1000 {
|
||||
g.sequence = (g.sequence + 1) & 999
|
||||
} else {
|
||||
g.sequence = 0
|
||||
}
|
||||
g.lastTime = nowMs
|
||||
|
||||
idStr := fmt.Sprintf("%s%03d", timestampStr, g.sequence)
|
||||
id, _ := strconv.ParseInt(idStr, 10, 64)
|
||||
return id
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ type Logger struct {
|
|||
var (
|
||||
defaultLogger *Logger
|
||||
startCount int
|
||||
once sync.Once
|
||||
onceInit sync.Once
|
||||
)
|
||||
|
||||
// InitLogger 初始化日志
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ var AppConfig = &appConfig{
|
|||
Enable: true,
|
||||
Default: RateLimitRule{Interval: 2, MaxRequests: 3}, // 默认2秒1次
|
||||
Rules: map[string]RateLimitRule{
|
||||
"/api/auth/login": {Interval: 5, MaxRequests: 1}, // 登录5秒1次
|
||||
"/api/user/auth/login": {Interval: 5, MaxRequests: 1}, // 登录5秒1次
|
||||
"/api/yx-school-majors": {Interval: 1, MaxRequests: 5}, // 查询1秒5次
|
||||
"/api/user/score/save-score": {Interval: 1, MaxRequests: 1}, // 保存5秒1次
|
||||
},
|
||||
},
|
||||
// Swagger配置
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func (ctrl *AuthController) Login(c *gin.Context) {
|
|||
|
||||
loginUser, token, err := ctrl.userService.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
common.Error(c, 401, err.Error())
|
||||
common.Error(c, 400, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ func NewUserScoreController() *UserScoreController {
|
|||
func (ctrl *UserScoreController) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
group := rg.Group("/user/score")
|
||||
{
|
||||
// group.GET("/:id", ctrl.Get)
|
||||
group.GET("/", ctrl.Get)
|
||||
group.GET("/list", ctrl.List) // 新增分页列表接口
|
||||
group.POST("/save-score", ctrl.SaveUserScore) // 新增接口
|
||||
group.GET("/", ctrl.GetActive)
|
||||
group.GET("/:id", ctrl.GetByID)
|
||||
group.GET("/list", ctrl.List)
|
||||
group.POST("/save-score", ctrl.SaveUserScore)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,16 +59,32 @@ func (ctrl *UserScoreController) SaveUserScore(c *gin.Context) {
|
|||
common.Success(c, result)
|
||||
}
|
||||
|
||||
// Get 获取当前用户的当前分数
|
||||
// @Summary 获取当前用户的当前分数
|
||||
// GetActive 获取当前用户的激活分数
|
||||
// @Summary 获取当前用户的激活分数
|
||||
// @Tags 用户分数
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /user/score [get]
|
||||
func (ctrl *UserScoreController) Get(c *gin.Context) {
|
||||
func (ctrl *UserScoreController) GetActive(c *gin.Context) {
|
||||
loginUserId := common.GetLoginUser(c).ID
|
||||
item, err := ctrl.userScoreService.GetActiveByID(loginUserId)
|
||||
if err != nil {
|
||||
common.Error(c, 404, "未找到记录")
|
||||
common.Error(c, 404, "未找到激活成绩")
|
||||
return
|
||||
}
|
||||
common.Success(c, item)
|
||||
}
|
||||
|
||||
// GetByID 获取指定 ID 的分数
|
||||
// @Summary 获取指定 ID 的分数
|
||||
// @Tags 用户分数
|
||||
// @Param id path string true "成绩ID"
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /user/score/{id} [get]
|
||||
func (ctrl *UserScoreController) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
item, err := ctrl.userScoreService.GetByID(id)
|
||||
if err != nil {
|
||||
common.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
common.Success(c, item)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package service
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"server/common"
|
||||
"server/config"
|
||||
"server/modules/user/vo"
|
||||
"server/modules/yx/dto"
|
||||
|
|
@ -26,20 +27,32 @@ func (s *UserScoreService) GetActiveByID(userID string) (*vo.UserScoreVO, error)
|
|||
var score entity.YxUserScore
|
||||
// 明确指定字段,提高可读性
|
||||
err := config.DB.Model(&entity.YxUserScore{}).
|
||||
Where("create_by = ? AND state = ?", userID, "1"). // 注意:yx_user_score 表中关联用户的是 create_by
|
||||
Where("create_by = ? AND state = ?", userID, "1").
|
||||
First(&score).Error
|
||||
// 错误处理
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("未找到用户的激活成绩记录")
|
||||
return nil, fmt.Errorf("未找到激活的成绩记录")
|
||||
}
|
||||
return nil, fmt.Errorf("查询成绩记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为 VO
|
||||
scoreVO := s.convertEntityToVo(&score)
|
||||
return s.convertEntityToVo(&score), nil
|
||||
}
|
||||
|
||||
return scoreVO, nil
|
||||
// GetByID 根据 ID 获取成绩
|
||||
func (s *UserScoreService) GetByID(id string) (*vo.UserScoreVO, error) {
|
||||
var score entity.YxUserScore
|
||||
err := config.DB.Model(&entity.YxUserScore{}).
|
||||
Where("id = ?", id).
|
||||
First(&score).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("记录不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("查询失败: %w", err)
|
||||
}
|
||||
|
||||
return s.convertEntityToVo(&score), nil
|
||||
}
|
||||
|
||||
// ListByUser 获取用户的成绩分页列表
|
||||
|
|
@ -72,14 +85,15 @@ func NewUserScoreService() *UserScoreService {
|
|||
}
|
||||
}
|
||||
|
||||
// service/yx_user_score_service.go
|
||||
func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*entity.YxUserScore, error) {
|
||||
// SaveUserScore 保存用户成绩并返回 VO
|
||||
func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*vo.UserScoreVO, error) {
|
||||
// 1. 业务验证
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 2. DTO 转 Entity
|
||||
entityItem := s.convertDtoToEntity(req)
|
||||
entityItem.ID = common.GenerateStringID() // 使用新封装的 ID 生成工具
|
||||
entityItem.CreateTime = time.Now()
|
||||
entityItem.UpdateTime = time.Now()
|
||||
// 3. 执行保存操作(可以包含事务)
|
||||
|
|
@ -89,16 +103,16 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*entity.YxU
|
|||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
entityItem.ID = common.GenerateStringID() // 使用新封装的 ID 生成工具
|
||||
// 标记该用户的所有旧成绩为历史状态
|
||||
if err := config.DB.Model(&entity.YxUserScore{}).
|
||||
if err := tx.Model(&entity.YxUserScore{}).
|
||||
Where("create_by = ? AND state = ?", req.CreateBy, "1").
|
||||
Updates(map[string]interface{}{"state": "2"}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, fmt.Errorf("更新旧记录失败: %w", err)
|
||||
}
|
||||
// 保存新的成绩记录
|
||||
if err := s.yxUserScoreService.Create(entityItem); err != nil {
|
||||
if err := tx.Create(entityItem).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, fmt.Errorf("保存记录失败: %w", err)
|
||||
}
|
||||
|
|
@ -107,7 +121,7 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*entity.YxU
|
|||
return nil, fmt.Errorf("提交事务失败: %w", err)
|
||||
}
|
||||
|
||||
return entityItem, nil
|
||||
return s.convertEntityToVo(entityItem), nil
|
||||
}
|
||||
|
||||
// 私有方法:DTO 转 Entity
|
||||
|
|
|
|||
|
|
@ -64,3 +64,21 @@ func (s *YxSchoolMajorService) BatchUpsert(items []entity.YxSchoolMajor, updateC
|
|||
func (s *YxSchoolMajorService) BatchDelete(ids []string) error {
|
||||
return s.mapper.BatchDelete(ids)
|
||||
}
|
||||
|
||||
// 函数名 根据用户查询类型获取专业列表
|
||||
// 详细描述(可选)
|
||||
//
|
||||
// 参数说明:
|
||||
//
|
||||
// professionalCategory - 专业分类
|
||||
// cognitioPolyclinic - 文理文科
|
||||
//
|
||||
// 返回值说明:
|
||||
//
|
||||
// 返回值类型 - 返回值描述
|
||||
func (s *YxCalculationMajorService) ListByUserQueryType(professionalCategory string, cognitioPolyclinic string,
|
||||
professionalCategoryChildren []string) (*entity.YxCalculationMajor, error) {
|
||||
|
||||
// return s.mapper.FindByProfessionalCategoryAndCognitioPolyclinic(professionalCategory, cognitioPolyclinic, professionalCategoryChildren)
|
||||
return &entity.YxCalculationMajor{}, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue