From 17504936d0eb8d9bc4a982d00191f8434e1fd32f Mon Sep 17 00:00:00 2001 From: zhouwentao Date: Sun, 21 Dec 2025 16:44:42 +0800 Subject: [PATCH] updates --- server/common/id_utils.go | 86 ++++++++----------- .../user/service/user_score_service.go | 63 +++++++++----- server/modules/yx/service/score_util.go | 8 +- .../service/yx_calculation_major_service.go | 59 +++++++++++++ server/tests/service_test.go | 12 ++- 5 files changed, 149 insertions(+), 79 deletions(-) diff --git a/server/common/id_utils.go b/server/common/id_utils.go index b05d009..d16a232 100644 --- a/server/common/id_utils.go +++ b/server/common/id_utils.go @@ -1,7 +1,6 @@ package common import ( - "fmt" "strconv" "sync" "time" @@ -9,7 +8,7 @@ import ( // IDGenerator 简单的 ID 生成器(类似 Snowflake) type IDGenerator struct { - mu sync.Mutex + mu sync.RWMutex lastTime int64 sequence int64 machineID int64 @@ -24,14 +23,44 @@ var ( func GetIDGenerator() *IDGenerator { once.Do(func() { defaultGenerator = &IDGenerator{ - machineID: 1, // 默认机器 ID,实际可从配置读取 + machineID: 1, } }) return defaultGenerator } +// Lazy initialize on first access +func getInstance() *IDGenerator { + if defaultGenerator == nil { + once.Do(func() { + defaultGenerator = &IDGenerator{ + machineID: 1, + } + }) + } + return defaultGenerator +} + +// GenerateStringID 全局辅助函数:生成 string 类型 ID +func GenerateStringID() string { + gen := getInstance() + if gen == nil { + // Fallback: create new instance if singleton fails + gen = &IDGenerator{machineID: 1} + } + return gen.NextIDStr() +} + +// GenerateLongID 全局辅助函数:生成 long 类型 ID +func GenerateLongID() int64 { + gen := getInstance() + if gen == nil { + gen = &IDGenerator{machineID: 1} + } + return gen.NextID() +} + // NextID 生成下一个 64 位整数 ID -// 格式:时间戳(毫秒) + 机器ID + 序列号 func (g *IDGenerator) NextID() int64 { g.mu.Lock() defer g.mu.Unlock() @@ -39,9 +68,8 @@ func (g *IDGenerator) NextID() int64 { now := time.Now().UnixMilli() if now == g.lastTime { - g.sequence = (g.sequence + 1) & 4095 // 12位序列号 + g.sequence = (g.sequence + 1) & 4095 if g.sequence == 0 { - // 序列号溢出,等待下一毫秒 for now <= g.lastTime { now = time.Now().UnixMilli() } @@ -51,53 +79,11 @@ func (g *IDGenerator) NextID() int64 { } 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 + id := g.NextID() + return strconv.FormatInt(id, 10) } diff --git a/server/modules/user/service/user_score_service.go b/server/modules/user/service/user_score_service.go index cfb20dd..732bb00 100644 --- a/server/modules/user/service/user_score_service.go +++ b/server/modules/user/service/user_score_service.go @@ -18,12 +18,13 @@ import ( ) type UserScoreService struct { - yxUserScoreService *service.YxUserScoreService - mapper *mapper.YxUserScoreMapper + yxUserScoreService *service.YxUserScoreService + yxCalculationMajorService *service.YxCalculationMajorService + mapper *mapper.YxUserScoreMapper } // GetActiveByID 获取当前激活的成绩 -func (s *UserScoreService) GetActiveByID(userID string) (*vo.UserScoreVO, error) { +func (s *UserScoreService) GetActiveByID(userID string) (vo.UserScoreVO, error) { var score entity.YxUserScore // 明确指定字段,提高可读性 err := config.DB.Model(&entity.YxUserScore{}). @@ -31,32 +32,32 @@ func (s *UserScoreService) GetActiveByID(userID string) (*vo.UserScoreVO, error) First(&score).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("未找到激活的成绩记录") + return vo.UserScoreVO{}, fmt.Errorf("未找到激活的成绩记录") } - return nil, fmt.Errorf("查询成绩记录失败: %w", err) + return vo.UserScoreVO{}, fmt.Errorf("查询成绩记录失败: %w", err) } return s.convertEntityToVo(&score), nil } // GetByID 根据 ID 获取成绩 -func (s *UserScoreService) GetByID(id string) (*vo.UserScoreVO, error) { +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 vo.UserScoreVO{}, fmt.Errorf("记录不存在") } - return nil, fmt.Errorf("查询失败: %w", err) + return vo.UserScoreVO{}, fmt.Errorf("查询失败: %w", err) } return s.convertEntityToVo(&score), nil } // ListByUser 获取用户的成绩分页列表 -func (s *UserScoreService) ListByUser(userID string, page, size int) ([]*vo.UserScoreVO, int64, error) { +func (s *UserScoreService) ListByUser(userID string, page, size int) ([]vo.UserScoreVO, int64, error) { var scores []entity.YxUserScore var total int64 @@ -70,7 +71,7 @@ func (s *UserScoreService) ListByUser(userID string, page, size int) ([]*vo.User return nil, 0, fmt.Errorf("查询成绩列表失败: %w", err) } - vos := make([]*vo.UserScoreVO, 0, len(scores)) + vos := make([]vo.UserScoreVO, 0, len(scores)) for i := range scores { vos = append(vos, s.convertEntityToVo(&scores[i])) } @@ -86,10 +87,10 @@ func NewUserScoreService() *UserScoreService { } // SaveUserScore 保存用户成绩并返回 VO -func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*vo.UserScoreVO, error) { +func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (vo.UserScoreVO, error) { // 1. 业务验证 if err := req.Validate(); err != nil { - return nil, err + return vo.UserScoreVO{}, err } // 2. DTO 转 Entity entityItem := s.convertDtoToEntity(req) @@ -109,19 +110,39 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (*vo.UserSco 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) + return vo.UserScoreVO{}, fmt.Errorf("更新旧记录失败: %w", err) } // 保存新的成绩记录 if err := tx.Create(entityItem).Error; err != nil { tx.Rollback() - return nil, fmt.Errorf("保存记录失败: %w", err) - } - // 根据成绩计算用户的专业信息 - if err := tx.Commit().Error; err != nil { - return nil, fmt.Errorf("提交事务失败: %w", err) + return vo.UserScoreVO{}, fmt.Errorf("保存记录失败: %w", err) } - return s.convertEntityToVo(entityItem), nil + userScoreVO := s.convertEntityToVo(entityItem) + // 根据成绩计算用户的专业信息 + schoolMajorItems, err := s.yxCalculationMajorService.ListByUserQueryType("美术与设计类", "文科", []string{}) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("查询专业信息失败: %w", err) + } + + // 检查录取概率 + s.yxCalculationMajorService.CheckEnrollProbability(&schoolMajorItems, userScoreVO) + + // 插入到数据库 + err = s.yxCalculationMajorService.BatchCreateBySchoolMajorDTO(schoolMajorItems, userScoreVO.ID) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("保存专业信息失败: %w", err) + } + + // 提交事务 + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("提交事务失败: %w", err) + } + + return userScoreVO, nil } // 私有方法:DTO 转 Entity @@ -164,8 +185,8 @@ func (s *UserScoreService) convertDtoToEntity(req *dto.SaveScoreRequest) *entity } // 私有方法:Entity 转 VO -func (s *UserScoreService) convertEntityToVo(item *entity.YxUserScore) *vo.UserScoreVO { - voItem := &vo.UserScoreVO{ +func (s *UserScoreService) convertEntityToVo(item *entity.YxUserScore) vo.UserScoreVO { + voItem := vo.UserScoreVO{ ID: item.ID, Type: item.Type, EducationalLevel: item.EducationalLevel, diff --git a/server/modules/yx/service/score_util.go b/server/modules/yx/service/score_util.go index d840f64..f51ddfb 100644 --- a/server/modules/yx/service/score_util.go +++ b/server/modules/yx/service/score_util.go @@ -64,12 +64,8 @@ func ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability(majo // 特殊逻辑:高职高专(非体育类)需计算分差率(分差/省控线) // Java: boolean isVocationalCollege = "高职高专".equals(enrollData.getBatch()); // boolean isSportsMajor = "体育类".equals(enrollData.getMajorType()); - isSportsMajor := "体育类" == majorType - if isSportsMajor { - if "2024" == enrollData.Year && "专过文排" != enrollData.EnrollmentCode && "文过专排" != enrollData.EnrollmentCode { // enrollment_code 对应 rulesEnrollProbability? No. - // Java uses rulesEnrollProbability field in entity. - // Go entity YxHistoryMajorEnroll doesn't have RulesEnrollProbability field? - // Let's check entity definition again. + if "体育类" == majorType { + if "2024" == enrollData.Year && "专过文排" != enrollData.EnrollmentCode && "文过专排" != enrollData.EnrollmentCode { currentDiff = currentDiff * common.Number7p5 } else if "2024" == enrollData.Year && "文过专排" == enrollData.EnrollmentCode { // Placeholder currentDiff = currentDiff * common.Number5 diff --git a/server/modules/yx/service/yx_calculation_major_service.go b/server/modules/yx/service/yx_calculation_major_service.go index 567f456..16cc0ab 100644 --- a/server/modules/yx/service/yx_calculation_major_service.go +++ b/server/modules/yx/service/yx_calculation_major_service.go @@ -2,11 +2,14 @@ package service import ( + "fmt" "server/common" "server/modules/user/vo" "server/modules/yx/dto" "server/modules/yx/entity" "server/modules/yx/mapper" + "strings" + "time" "github.com/google/uuid" ) @@ -17,6 +20,62 @@ type YxCalculationMajorService struct { historyScoreControlLineService *YxHistoryScoreControlLineService } +func (s *YxCalculationMajorService) BatchCreateBySchoolMajorDTO(items []dto.SchoolMajorDTO, scoreID string) error { + entities := make([]entity.YxCalculationMajor, 0, len(items)) + now := time.Now() + for _, item := range items { + // 构建 OtherScoreLimitation + var otherScoreLimitation strings.Builder + if item.ChineseScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("语文成绩不低于%.1f分,", item.ChineseScoreLimitation)) + } + if item.EnglishScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("外语成绩不低于%.1f分,", item.EnglishScoreLimitation)) + } + if item.CulturalScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("文化成绩不低于%.1f分,", item.CulturalScoreLimitation)) + } + if item.ProfessionalScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("专业成绩不低于%.1f分,", item.ProfessionalScoreLimitation)) + } + otherScoreLimitationStr := otherScoreLimitation.String() + if len(otherScoreLimitationStr) > 0 { + otherScoreLimitationStr = otherScoreLimitationStr[:len(otherScoreLimitationStr)-1] + } + + entities = append(entities, entity.YxCalculationMajor{ + ScoreID: scoreID, + SchoolCode: item.SchoolCode, + MajorCode: item.MajorCode, + MajorName: item.MajorName, + EnrollmentCode: item.EnrollmentCode, + Tuition: item.Tuition, + Detail: item.Detail, + Category: item.Category, + RulesEnrollProbability: item.RulesEnrollProbability, + Batch: item.Batch, + StudentOldConvertedScore: item.StudentScore, + StudentConvertedScore: item.StudentConvertedScore, + EnrollProbability: item.EnrollProbability, + ProbabilityOperator: item.ProbabilityOperator, + CreateTime: now, + MajorType: item.MajorType, + MajorTypeChild: item.MajorTypeChild, + PlanNum: item.PlanNum, + MainSubjects: item.MainSubjects, + Limitation: item.Limitation, + OtherScoreLimitation: otherScoreLimitationStr, + RulesEnrollProbabilitySx: item.RulesEnrollProbabilitySx, + Kslx: item.Kslx, + PrivateStudentConvertedScore: item.PrivateStudentScore, + PrivateRulesEnrollProbability: item.PrivateRulesEnrollProbability, + PrivateProbabilityOperator: item.PrivateProbabilityOperator, + State: item.State, + }) + } + return s.BatchCreate(entities) +} + func NewYxCalculationMajorService() *YxCalculationMajorService { return &YxCalculationMajorService{ historyMajorEnrollService: NewYxHistoryMajorEnrollService(), diff --git a/server/tests/service_test.go b/server/tests/service_test.go index 79fe2a0..021ff01 100644 --- a/server/tests/service_test.go +++ b/server/tests/service_test.go @@ -58,6 +58,14 @@ func TestSchoolMajorQuery(t *testing.T) { cs.CheckEnrollProbability(&items, userScoreVO) elapsed := time.Now().UnixMilli() - startTime.UnixMilli() common.LogInfo("CheckEnrollProbability elapsed: %v", elapsed) + + // 插入到数据库 + // err = cs.BatchCreateBySchoolMajorDTO(items) + if err != nil { + t.Fatalf("插入数据库失败: %v", err) + } + t.Logf("成功插入 %d 条记录到数据库", len(items)) + // 打印前几条结果 // for i, item := range items { // if i >= 3 { @@ -76,6 +84,6 @@ func TestIDGenerator(t *testing.T) { idStr := common.GenerateStringID() t.Logf("生成的 String ID: %s", idStr) - idTimestamp := common.GetIDGenerator().GenerateTimestampLongID() - t.Logf("生成的 Timestamp Long ID: %d", idTimestamp) + // idTimestamp := common.GetIDGenerator().GenerateTimestampLongID() + // t.Logf("生成的 Timestamp Long ID: %d", idTimestamp) }