From 26ce858f7ad85af29b05a81f642b31501b10e900 Mon Sep 17 00:00:00 2001 From: zhouwentao Date: Sat, 24 Jan 2026 13:56:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E6=8C=81=E5=BF=97=E6=84=BF=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/common/sql_utils.go | 14 +++ .../user/controller/user_major_controller.go | 105 ++++++++++++++++++ .../user/service/user_score_service.go | 11 +- .../yx/mapper/yx_calculation_major_mapper.go | 45 ++++++++ .../modules/yx/mapper/yx_volunteer_mapper.go | 17 ++- .../yx/mapper/yx_volunteer_record_mapper.go | 4 + .../service/yx_calculation_major_service.go | 4 + .../yx/service/yx_volunteer_record_service.go | 4 + .../yx/service/yx_volunteer_service.go | 23 ++-- 9 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 server/common/sql_utils.go diff --git a/server/common/sql_utils.go b/server/common/sql_utils.go new file mode 100644 index 0000000..1fa9924 --- /dev/null +++ b/server/common/sql_utils.go @@ -0,0 +1,14 @@ +package common + +import "regexp" + +// 验证表名格式的辅助函数 +func IsValidTableName(tableName string) bool { + if tableName == "" { + return false + } + + // 表名只能包含字母、数字、下划线和点号,且长度合理 + matched, err := regexp.MatchString(`^[a-zA-Z_][a-zA-Z0-9_.]{0,100}$`, tableName) + return err == nil && matched +} diff --git a/server/modules/user/controller/user_major_controller.go b/server/modules/user/controller/user_major_controller.go index 1beab90..1a07176 100644 --- a/server/modules/user/controller/user_major_controller.go +++ b/server/modules/user/controller/user_major_controller.go @@ -4,8 +4,10 @@ import ( "server/common" user_service "server/modules/user/service" yxDto "server/modules/yx/dto" + "server/modules/yx/entity" yx_service "server/modules/yx/service" "strconv" + "time" "github.com/gin-gonic/gin" ) @@ -14,6 +16,8 @@ type UserMajorController struct { userScoreService *user_service.UserScoreService yxUserScoreService *yx_service.YxUserScoreService yxCalculationMajorService *yx_service.YxCalculationMajorService + yxVolunteerService *yx_service.YxVolunteerService + yxVolunteerRecordService *yx_service.YxVolunteerRecordService } func NewUserMajorController() *UserMajorController { @@ -21,6 +25,8 @@ func NewUserMajorController() *UserMajorController { yxUserScoreService: yx_service.NewYxUserScoreService(), userScoreService: user_service.NewUserScoreService(), yxCalculationMajorService: yx_service.NewYxCalculationMajorService(), + yxVolunteerService: yx_service.NewYxVolunteerService(), + yxVolunteerRecordService: yx_service.NewYxVolunteerRecordService(), } } @@ -32,6 +38,7 @@ func (ctrl *UserMajorController) RegisterRoutes(rg *gin.RouterGroup) { // group.GET("/:id", ctrl.GetByID) group.GET("/list", ctrl.List) group.GET("/list_by_school", ctrl.ListBySchool) + group.POST("/save_volunteer", ctrl.SaveVolunteer) } } @@ -112,3 +119,101 @@ func (ctrl *UserMajorController) List(c *gin.Context) { } common.SuccessPage(c, newMap, total, page, size) } + +// SaveVolunteer 保存志愿明细 +// @Summary 保存志愿明细 +// @Tags 用户专业 +// @Param keys body []string true "Keys: schoolCode_majorCode_enrollmentCode" +// @Success 200 {object} common.Response +// @Router /user/major/save_volunteer [post] +func (ctrl *UserMajorController) SaveVolunteer(c *gin.Context) { + var keys []string + if err := c.ShouldBindJSON(&keys); err != nil { + common.Error(c, 500, err.Error()) + return + } + + // data deduplication + seen := make(map[string]bool) + var uniqueKeys []string + for _, key := range keys { + if !seen[key] { + seen[key] = true + uniqueKeys = append(uniqueKeys, key) + } + } + keys = uniqueKeys + + loginUserId := common.GetLoginUser(c).ID + userScoreVO, err := ctrl.userScoreService.GetActiveByID(loginUserId) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + + if userScoreVO.CalculationTableName == "" { + common.Error(c, 500, "未找到计算表名") + return + } + + // 查找当前激活的志愿表 + volunteer, err := ctrl.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID) + if err != nil { + common.Error(c, 500, "查找志愿表失败: "+err.Error()) + return + } + if volunteer == nil || volunteer.ID == "" { + common.Error(c, 500, "请先创建志愿表") + return + } + + // 查找专业信息 + majors, err := ctrl.yxCalculationMajorService.FindListByCompositeKeys(userScoreVO.CalculationTableName, keys, userScoreVO.ID) + if err != nil { + common.Error(c, 500, "查找专业信息失败: "+err.Error()) + return + } + + // 构建 Map 用于保持顺序 + majorMap := make(map[string]entity.YxCalculationMajor) + for _, major := range majors { + k := major.SchoolCode + "_" + major.MajorCode + "_" + major.EnrollmentCode + majorMap[k] = major + } + + var records []entity.YxVolunteerRecord + for i, key := range keys { + if major, ok := majorMap[key]; ok { + record := entity.YxVolunteerRecord{ + VolunteerID: volunteer.ID, + SchoolCode: major.SchoolCode, + MajorCode: major.MajorCode, + EnrollmentCode: major.EnrollmentCode, + Indexs: i + 1, + CreateBy: loginUserId, + CreateTime: time.Now(), + Batch: major.Batch, + EnrollProbability: major.EnrollProbability, + StudentConvertedScore: major.StudentConvertedScore, + CalculationMajorID: major.ID, + } + records = append(records, record) + } + } + + // 先删除旧数据 + if err := ctrl.yxVolunteerRecordService.DeleteByVolunteerID(volunteer.ID); err != nil { + common.Error(c, 500, "删除旧数据失败: "+err.Error()) + return + } + + // 批量插入新数据 + if len(records) > 0 { + if err := ctrl.yxVolunteerRecordService.BatchCreate(records); err != nil { + common.Error(c, 500, "保存失败: "+err.Error()) + return + } + } + + common.Success(c, "保存成功") +} diff --git a/server/modules/user/service/user_score_service.go b/server/modules/user/service/user_score_service.go index 61d3379..2e2a15f 100644 --- a/server/modules/user/service/user_score_service.go +++ b/server/modules/user/service/user_score_service.go @@ -109,6 +109,7 @@ func NewUserScoreService() *UserScoreService { return &UserScoreService{ yxUserScoreService: service.NewYxUserScoreService(), yxCalculationMajorService: service.NewYxCalculationMajorService(), + yxVolunteerService: service.NewYxVolunteerService(), mapper: mapper.NewYxUserScoreMapper(), } } @@ -178,6 +179,13 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (vo.UserScor return vo.UserScoreVO{}, fmt.Errorf("保存专业信息失败: %w", err) } + // 创建志愿表 + err = s.yxVolunteerService.CreateByScoreId(entityItem.ID, req.CreateBy) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("创建志愿表失败: %w", err) + } + // 提交事务 if err := tx.Commit().Error; err != nil { tx.Rollback() @@ -193,9 +201,6 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (vo.UserScor if err != nil { return vo.UserScoreVO{}, fmt.Errorf("缓存成绩记录失败: %w", err) } - - // 创建志愿表 - s.yxVolunteerService.CreateByScoreId(entityItem.ID, req.CreateBy) return userScoreVO, nil } diff --git a/server/modules/yx/mapper/yx_calculation_major_mapper.go b/server/modules/yx/mapper/yx_calculation_major_mapper.go index 9129403..e900a1f 100644 --- a/server/modules/yx/mapper/yx_calculation_major_mapper.go +++ b/server/modules/yx/mapper/yx_calculation_major_mapper.go @@ -3,6 +3,7 @@ package mapper import ( "fmt" + "server/common" "server/config" "server/modules/yx/dto" "server/modules/yx/entity" @@ -401,6 +402,50 @@ func (m *YxCalculationMajorMapper) FindByScoreID(scoreID string) ([]entity.YxCal return items, err } +func (m *YxCalculationMajorMapper) FindListByCompositeKeys(tableName string, keys []string, scoreId string) ([]entity.YxCalculationMajor, error) { + if len(keys) == 0 { + return nil, nil + } + + // 验证表名格式(防止表名注入) + if !common.IsValidTableName(tableName) { + return nil, fmt.Errorf("无效的表名: %s", tableName) + } + + // 验证和转义 score_id + if scoreId == "" { + return nil, fmt.Errorf("score_id 不能为空") + } + + var items []entity.YxCalculationMajor + db := config.DB + if tableName != "" { + db = db.Table(tableName) + } + + sql := "SELECT * FROM " + tableName + " WHERE score_id = ? AND (school_code, major_code, enrollment_code) IN (" + var params []interface{} + + // 将 score_id 作为第一个参数 + params = append(params, scoreId) + + for i, key := range keys { + parts := strings.Split(key, "_") + if len(parts) != 3 { + continue + } + if i > 0 { + sql += "," + } + sql += "(?, ?, ?)" + params = append(params, parts[0], parts[1], parts[2]) + } + sql += ")" + + err := db.Raw(sql, params...).Scan(&items).Error + return items, err +} + func (m *YxCalculationMajorMapper) BatchCreate(tableName string, items []entity.YxCalculationMajor, batchSize int) error { if tableName != "" { return config.DB.Table(tableName).CreateInBatches(items, batchSize).Error diff --git a/server/modules/yx/mapper/yx_volunteer_mapper.go b/server/modules/yx/mapper/yx_volunteer_mapper.go index bf23489..6d3b388 100644 --- a/server/modules/yx/mapper/yx_volunteer_mapper.go +++ b/server/modules/yx/mapper/yx_volunteer_mapper.go @@ -10,8 +10,15 @@ import ( type YxVolunteerMapper struct{} -func (m *YxVolunteerMapper) CloseOtherVolunteer(userId string) { - config.DB.Model(&entity.YxVolunteer{}).Where("create_by = ? ", userId).Updates(map[string]interface{}{"state": "0"}) +func (m *YxVolunteerMapper) CloseOtherVolunteer(userId string) error { + result := config.DB.Model(&entity.YxVolunteer{}). + Where("create_by = ?", userId). + Updates(map[string]interface{}{"state": "0"}) + + if result.Error != nil { + return result.Error + } + return nil } func NewYxVolunteerMapper() *YxVolunteerMapper { @@ -32,6 +39,12 @@ func (m *YxVolunteerMapper) FindByID(id string) (*entity.YxVolunteer, error) { return &item, err } +func (m *YxVolunteerMapper) FindActiveByScoreId(scoreId string) (*entity.YxVolunteer, error) { + var item entity.YxVolunteer + err := config.DB.Where("score_id = ? AND state = ?", scoreId, "1").First(&item).Error + return &item, err +} + func (m *YxVolunteerMapper) Create(item *entity.YxVolunteer) error { return config.DB.Create(item).Error } diff --git a/server/modules/yx/mapper/yx_volunteer_record_mapper.go b/server/modules/yx/mapper/yx_volunteer_record_mapper.go index 8ba8c32..e660133 100644 --- a/server/modules/yx/mapper/yx_volunteer_record_mapper.go +++ b/server/modules/yx/mapper/yx_volunteer_record_mapper.go @@ -44,6 +44,10 @@ func (m *YxVolunteerRecordMapper) Delete(id string) error { return config.DB.Delete(&entity.YxVolunteerRecord{}, "id = ?", id).Error } +func (m *YxVolunteerRecordMapper) DeleteByVolunteerID(volunteerID string) error { + return config.DB.Delete(&entity.YxVolunteerRecord{}, "volunteer_id = ?", volunteerID).Error +} + func (m *YxVolunteerRecordMapper) BatchCreate(items []entity.YxVolunteerRecord, batchSize int) error { return config.DB.CreateInBatches(items, batchSize).Error } diff --git a/server/modules/yx/service/yx_calculation_major_service.go b/server/modules/yx/service/yx_calculation_major_service.go index 8532095..43a936b 100644 --- a/server/modules/yx/service/yx_calculation_major_service.go +++ b/server/modules/yx/service/yx_calculation_major_service.go @@ -145,6 +145,10 @@ func (s *YxCalculationMajorService) GetByScoreID(scoreID string) ([]entity.YxCal return s.mapper.FindByScoreID(scoreID) } +func (s *YxCalculationMajorService) FindListByCompositeKeys(tableName string, keys []string, scoreId string) ([]entity.YxCalculationMajor, error) { + return s.mapper.FindListByCompositeKeys(tableName, keys, scoreId) +} + func (s *YxCalculationMajorService) BatchCreate(tableName string, items []entity.YxCalculationMajor) error { for i := range items { items[i].ID = uuid.New().String() diff --git a/server/modules/yx/service/yx_volunteer_record_service.go b/server/modules/yx/service/yx_volunteer_record_service.go index b95c358..9ab1ef0 100644 --- a/server/modules/yx/service/yx_volunteer_record_service.go +++ b/server/modules/yx/service/yx_volunteer_record_service.go @@ -41,6 +41,10 @@ func (s *YxVolunteerRecordService) Delete(id string) error { return s.mapper.Delete(id) } +func (s *YxVolunteerRecordService) DeleteByVolunteerID(volunteerID string) error { + return s.mapper.DeleteByVolunteerID(volunteerID) +} + func (s *YxVolunteerRecordService) BatchCreate(items []entity.YxVolunteerRecord) error { for i := range items { items[i].ID = uuid.New().String() diff --git a/server/modules/yx/service/yx_volunteer_service.go b/server/modules/yx/service/yx_volunteer_service.go index dd92138..e7f68b2 100644 --- a/server/modules/yx/service/yx_volunteer_service.go +++ b/server/modules/yx/service/yx_volunteer_service.go @@ -2,11 +2,11 @@ package service import ( + "fmt" + "server/common" "server/modules/yx/entity" "server/modules/yx/mapper" "time" - - "github.com/google/uuid" ) type YxVolunteerService struct { @@ -26,10 +26,14 @@ func (s *YxVolunteerService) GetByID(id string) (*entity.YxVolunteer, error) { } func (s *YxVolunteerService) Create(item *entity.YxVolunteer) error { - item.ID = uuid.New().String() + item.ID = common.GenerateStringID() return s.mapper.Create(item) } +func (s *YxVolunteerService) FindActiveByScoreId(scoreId string) (*entity.YxVolunteer, error) { + return s.mapper.FindActiveByScoreId(scoreId) +} + func (s *YxVolunteerService) Update(item *entity.YxVolunteer) error { return s.mapper.Update(item) } @@ -44,7 +48,7 @@ func (s *YxVolunteerService) Delete(id string) error { func (s *YxVolunteerService) BatchCreate(items []entity.YxVolunteer) error { for i := range items { - items[i].ID = uuid.New().String() + items[i].ID = common.GenerateStringID() } return s.mapper.BatchCreate(items, 100) } @@ -56,7 +60,7 @@ func (s *YxVolunteerService) BatchUpdate(items []entity.YxVolunteer) error { func (s *YxVolunteerService) BatchUpsert(items []entity.YxVolunteer, updateColumns []string) error { for i := range items { if items[i].ID == "" { - items[i].ID = uuid.New().String() + items[i].ID = common.GenerateStringID() } } return s.mapper.BatchUpsert(items, updateColumns) @@ -69,8 +73,7 @@ func (s *YxVolunteerService) BatchDelete(ids []string) error { // 根据ScoreId创建新志愿表 func (s *YxVolunteerService) CreateByScoreId(scoreId string, userId string) error { volunteer := entity.YxVolunteer{} - volunteer.ID = uuid.New().String() - + volunteer.ID = common.GenerateStringID() // 志愿表名称格式 时间戳 20260101134501志愿表 volunteer.VolunteerName = time.Now().Format("20060102150405") + "志愿表" @@ -81,8 +84,10 @@ func (s *YxVolunteerService) CreateByScoreId(scoreId string, userId string) erro volunteer.CreateTime = time.Now() volunteer.UpdateTime = time.Now() - // 先关闭当前用户其他志愿单 - s.mapper.CloseOtherVolunteer(userId) + // 先关闭当前用户其他志愿单 - ✅ 检查错误 + if err := s.mapper.CloseOtherVolunteer(userId); err != nil { + return fmt.Errorf("关闭其他志愿表失败: %w", err) + } // 创建志愿表 return s.mapper.Create(&volunteer) }