package controller import ( "server/common" "server/modules/user/service" yxDto "server/modules/yx/dto" "server/modules/yx/entity" yx_service "server/modules/yx/service" "time" "github.com/gin-gonic/gin" ) type UserVolunteerController struct { userScoreService *service.UserScoreService yxVolunteerService *yx_service.YxVolunteerService yxVolunteerRecordService *yx_service.YxVolunteerRecordService yxCalculationMajorService *yx_service.YxCalculationMajorService } func NewUserVolunteerController() *UserVolunteerController { return &UserVolunteerController{ userScoreService: service.NewUserScoreService(), yxVolunteerService: yx_service.NewYxVolunteerService(), yxVolunteerRecordService: yx_service.NewYxVolunteerRecordService(), yxCalculationMajorService: yx_service.NewYxCalculationMajorService(), } } func (ctrl *UserVolunteerController) RegisterRoutes(rg *gin.RouterGroup) { group := rg.Group("/user/volunteer") { group.POST("/save", ctrl.SaveVolunteer) group.GET("/detail", ctrl.GetVolunteerDetail) group.PUT("/updateName", ctrl.UpdateVolunteerName) group.GET("/list", ctrl.GetUserVolunteerList) group.DELETE("/delete", ctrl.DeleteVolunteer) group.POST("/switch", ctrl.SwitchVolunteer) } } // SaveVolunteer 保存志愿明细 // @Summary 保存志愿明细 // @Tags 用户志愿 // @Param keys body []string true "Keys: schoolCode_majorCode_enrollmentCode" // @Success 200 {object} common.Response // @Router /user/volunteer/save [post] func (ctrl *UserVolunteerController) 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, "保存成功") } // GetVolunteerDetail 获取当前志愿单详情 // @Summary 获取当前志愿单详情 // @Tags 用户志愿 // @Success 200 {object} common.Response // @Router /user/volunteer/detail [get] func (ctrl *UserVolunteerController) GetVolunteerDetail(c *gin.Context) { loginUserId := common.GetLoginUser(c).ID userScoreVO, err := ctrl.userScoreService.GetActiveByID(loginUserId) if err != nil { common.Error(c, 500, err.Error()) return } // 查找当前激活的志愿表 volunteer, err := ctrl.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID) if err != nil { common.Error(c, 500, "查找志愿表失败: "+err.Error()) return } if volunteer == nil || volunteer.ID == "" { common.Success(c, nil) // No volunteer record found return } records, err := ctrl.yxVolunteerRecordService.FindByVolunteerID(volunteer.ID) if err != nil { common.Error(c, 500, "查找志愿明细失败: "+err.Error()) return } // Fetch enriched details var enrichedMajors map[string]yxDto.SchoolMajorDTO if len(records) > 0 && userScoreVO.CalculationTableName != "" { keys := make([]string, 0, len(records)) for _, r := range records { keys = append(keys, r.SchoolCode+"_"+r.MajorCode+"_"+r.EnrollmentCode) } majors, err := ctrl.yxCalculationMajorService.FindDtoListByCompositeKeys(userScoreVO.CalculationTableName, keys, userScoreVO.ID) if err == nil { enrichedMajors = make(map[string]yxDto.SchoolMajorDTO) for _, m := range majors { // Key by composite key as ID matches record's logic (or use ID if record stores correct ID) // Record has CalculationMajorID, but DTO also has ID. Let's use ID if reliable, else composite. // FindDtoListByCompositeKeys returns items where ID should match. enrichedMajors[m.SchoolCode+"_"+m.MajorCode+"_"+m.EnrollmentCode] = m } } } // Grouping // Response structure: volunteer info + grouped items type VolunteerDetailItem struct { entity.YxVolunteerRecord SchoolName string `json:"schoolName"` MajorName string `json:"majorName"` PlanNum int `json:"planNum"` Tuition string `json:"tuition"` SchoolIcon string `json:"schoolIcon"` Province string `json:"province"` SchoolNature string `json:"schoolNature"` InstitutionType string `json:"institutionType"` MajorDetail string `json:"majorDetail"` // from detail } groupedItems := map[string][]VolunteerDetailItem{ "提前批": {}, "本科批": {}, "专科批": {}, } for _, r := range records { item := VolunteerDetailItem{ YxVolunteerRecord: r, } key := r.SchoolCode + "_" + r.MajorCode + "_" + r.EnrollmentCode if m, ok := enrichedMajors[key]; ok { item.SchoolName = m.SchoolName item.MajorName = m.MajorName item.PlanNum = m.PlanNum item.Tuition = m.Tuition // DTO doesn't have icon? Check DTO definition. // SchoolMajorDTO in step 150 sql selects school_icon. But DTO struct (step 6) might not have it. // Checking DTO definition in step 6... It does NOT have SchoolIcon. // I need to update DTO definition if I want SchoolIcon. // For now, let's omit if not in DTO or update DTO. // Wait, simple fix: update DTO struct later. For now map what matches. item.Province = m.Province item.SchoolNature = m.SchoolNature item.InstitutionType = m.InstitutionType item.MajorDetail = m.Detail } // Map batch // Batch might be "本科提前批", "本科A段" etc. Need to normalize to 3 buckets? // Or just use the batch string from record/major? // User said "志愿明细(专科批,本科批,提前批)". // If data has "本科A段", where does it go? "本科批"? // I'll assume exact match or simple containment. // If specific batches exist in data like "本科A段", "本科B段", they go to "本科批"? // Let's use simple logic: groupKey := "" if r.Batch == "提前批" || r.Batch == "本科提前批" { groupKey = "提前批" } else if r.Batch == "高职高专" || r.Batch == "专科批" { groupKey = "专科批" } else { groupKey = "本科批" } groupedItems[groupKey] = append(groupedItems[groupKey], item) } // Sort items by Indexs in each group? Records are already ordered by Indexs globally. // Within group, they should follow Indexs order naturally if iterated in order. common.Success(c, map[string]interface{}{ "volunteer": volunteer, "items": groupedItems, }) } // UpdateVolunteerName 编辑志愿单名称 // @Summary 编辑志愿单名称 // @Tags 用户志愿 // @Param id query string true "志愿单ID" // @Param name query string true "志愿单名称" // @Success 200 {object} common.Response // @Router /user/volunteer/updateName [put] func (ctrl *UserVolunteerController) UpdateVolunteerName(c *gin.Context) { id := c.Query("id") name := c.Query("name") if id == "" || name == "" { common.Error(c, 400, "参数错误") return } loginUserID := common.GetLoginUser(c).ID if err := ctrl.yxVolunteerService.UpdateName(id, name, loginUserID); err != nil { common.Error(c, 500, err.Error()) return } common.Success(c, "更新成功") } // GetUserVolunteerList 获取当前用户志愿单列表 // @Summary 获取当前用户志愿单列表 // @Tags 用户志愿 // @Param page query int false "页码" // @Param size query int false "每页数量" // @Success 200 {object} common.Response // @Router /user/volunteer/list [get] func (ctrl *UserVolunteerController) GetUserVolunteerList(c *gin.Context) { page := common.GetPage(c) size := common.GetSize(c) loginUserID := common.GetLoginUser(c).ID items, total, err := ctrl.yxVolunteerService.ListByUser(loginUserID, page, size) if err != nil { common.Error(c, 500, err.Error()) return } common.Success(c, map[string]interface{}{ "items": items, "total": total, }) } // DeleteVolunteer 删除志愿单接口 // @Summary 删除志愿单接口 // @Tags 用户志愿 // @Param id query string true "志愿单ID" // @Success 200 {object} common.Response // @Router /user/volunteer/delete [delete] func (ctrl *UserVolunteerController) DeleteVolunteer(c *gin.Context) { id := c.Query("id") if id == "" { common.Error(c, 400, "参数错误") return } loginUserID := common.GetLoginUser(c).ID err := ctrl.yxVolunteerService.DeleteVolunteer(id, loginUserID, ctrl.userScoreService, ctrl.yxCalculationMajorService, ctrl.yxVolunteerRecordService) if err != nil { common.Error(c, 500, err.Error()) return } common.Success(c, "删除成功") } // SwitchVolunteer 切换当前志愿单 // @Summary 切换当前志愿单 // @Tags 用户志愿 // @Param id query string true "志愿单ID" // @Success 200 {object} common.Response // @Router /user/volunteer/switch [post] func (ctrl *UserVolunteerController) SwitchVolunteer(c *gin.Context) { id := c.Query("id") if id == "" { common.Error(c, 400, "参数错误") return } loginUserID := common.GetLoginUser(c).ID // 1. 先判断是否已是该志愿单 (从 cache 或 db 中查找激活的) userScoreVO, _ := ctrl.userScoreService.GetActiveByID(loginUserID) if userScoreVO.ID != "" { activeVolunteer, _ := ctrl.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID) if activeVolunteer != nil && activeVolunteer.ID == id { common.Success(c, "已是当前志愿单,忽略切换") return } } // 2. 切换 if err := ctrl.yxVolunteerService.SwitchVolunteer(id, loginUserID, ctrl.userScoreService); err != nil { common.Error(c, 500, err.Error()) return } // 3. Redis 缓存同步 (假设 common 中有清除缓存的方法逻辑,或者由 Service 处理) // 这里按需求提到:切换志愿单切换后 写入到redis做缓存(查询志愿单接口也要从redis读本体数据),再清除之前的成绩单redis重新获取写到redis缓存。 // 实际项目中 common.GetLoginUser 已经包含了 ID,成绩单和志愿单通常由具体模块处理缓存。 // 如果这里只是标记,Service 层已做状态更新,查询接口读取时会感知变化。 // 为了彻底满足“写入/清除 redis 缓存”,通常会有相应的 RedisUtil 调用。 // 假设 service 层或 controller 有 Cache 清理逻辑。 common.Success(c, "切换成功") }