diff --git a/server/docs/docs.go b/server/docs/docs.go index 4da8f36..b88b4c4 100644 --- a/server/docs/docs.go +++ b/server/docs/docs.go @@ -282,6 +282,38 @@ const docTemplate = `{ } } }, + "/user//major/list": { + "get": { + "tags": [ + "用户专业" + ], + "summary": "获取当前用户可检索列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, "/user/auth/info": { "get": { "tags": [ @@ -347,6 +379,54 @@ const docTemplate = `{ } } }, + "/user/score": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的激活分数", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/list": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的成绩列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, "/user/score/save-score": { "post": { "tags": [ @@ -379,7 +459,16 @@ const docTemplate = `{ "tags": [ "用户分数" ], - "summary": "获取当前用户的当前分数", + "summary": "获取指定 ID 的分数", + "parameters": [ + { + "type": "string", + "description": "成绩ID", + "name": "id", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", diff --git a/server/docs/swagger.json b/server/docs/swagger.json index 70b8714..98fe964 100644 --- a/server/docs/swagger.json +++ b/server/docs/swagger.json @@ -276,6 +276,38 @@ } } }, + "/user//major/list": { + "get": { + "tags": [ + "用户专业" + ], + "summary": "获取当前用户可检索列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, "/user/auth/info": { "get": { "tags": [ @@ -341,6 +373,54 @@ } } }, + "/user/score": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的激活分数", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/list": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的成绩列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, "/user/score/save-score": { "post": { "tags": [ @@ -373,7 +453,16 @@ "tags": [ "用户分数" ], - "summary": "获取当前用户的当前分数", + "summary": "获取指定 ID 的分数", + "parameters": [ + { + "type": "string", + "description": "成绩ID", + "name": "id", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", diff --git a/server/docs/swagger.yaml b/server/docs/swagger.yaml index 400af6d..dceae95 100644 --- a/server/docs/swagger.yaml +++ b/server/docs/swagger.yaml @@ -710,6 +710,27 @@ paths: summary: Sys用户登出 tags: - 认证 + /user//major/list: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前用户可检索列表 + tags: + - 用户专业 /user/auth/info: get: responses: @@ -751,14 +772,51 @@ paths: summary: 用户登出 tags: - 认证 - /user/score/{id}: + /user/score: get: responses: "200": description: OK schema: $ref: '#/definitions/common.Response' - summary: 获取当前用户的当前分数 + summary: 获取当前用户的激活分数 + tags: + - 用户分数 + /user/score/{id}: + get: + parameters: + - description: 成绩ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取指定 ID 的分数 + tags: + - 用户分数 + /user/score/list: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前用户的成绩列表 tags: - 用户分数 /user/score/save-score: diff --git a/server/main.go b/server/main.go index 5d49029..202f96c 100644 --- a/server/main.go +++ b/server/main.go @@ -85,6 +85,7 @@ func main() { // 注册 User 模块路由 userController.NewUserScoreController().RegisterRoutes(api) userController.NewAuthController().RegisterRoutes(api) + userController.NewUserMajorController().RegisterRoutes(api) // 创建 HTTP 服务器 srv := &http.Server{ diff --git a/server/modules/user/controller/user_major_controller.go b/server/modules/user/controller/user_major_controller.go new file mode 100644 index 0000000..ddcfcb9 --- /dev/null +++ b/server/modules/user/controller/user_major_controller.go @@ -0,0 +1,64 @@ +package controller + +import ( + "server/common" + user_service "server/modules/user/service" + yxDto "server/modules/yx/dto" + yx_service "server/modules/yx/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type UserMajorController struct { + userScoreService *user_service.UserScoreService + yxUserScoreService *yx_service.YxUserScoreService + yxCalculationMajorService *yx_service.YxCalculationMajorService +} + +func NewUserMajorController() *UserMajorController { + return &UserMajorController{ + yxUserScoreService: yx_service.NewYxUserScoreService(), + userScoreService: user_service.NewUserScoreService(), + yxCalculationMajorService: yx_service.NewYxCalculationMajorService(), + } +} + +// RegisterRoutes 注册路由 +func (ctrl *UserMajorController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/user/major") + { + // group.GET("/", ctrl.GetActive) + // group.GET("/:id", ctrl.GetByID) + group.GET("/list", ctrl.List) + } +} + +// List 获取当前用户可检索列表 +// @Summary 获取当前用户可检索列表 +// @Tags 用户专业 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Success 200 {object} common.Response +// @Router /user/major/list [get] +func (ctrl *UserMajorController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + schoolMajorQuery := yxDto.SchoolMajorQuery{ + Page: page, + Size: size, + LoginUserId: common.GetLoginUser(c).ID, + } + userScoreVO, err := ctrl.userScoreService.GetActiveByID(schoolMajorQuery.LoginUserId) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + schoolMajorQuery.UserScoreVO = userScoreVO + items, total, err := ctrl.yxCalculationMajorService.RecommendMajorList(schoolMajorQuery) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.SuccessPage(c, items, total, page, size) +} diff --git a/server/modules/user/dto/user_major_dto.go b/server/modules/user/dto/user_major_dto.go new file mode 100644 index 0000000..4b27bc8 --- /dev/null +++ b/server/modules/user/dto/user_major_dto.go @@ -0,0 +1,11 @@ +package dto + +import "server/modules/user/vo" + +// RecommendMajorListRequest 推荐专业列表请求 +type RecommendMajorListRequest struct { + Page int `json:"page"` + Size int `json:"size"` + LoginUserId string `json:"loginUserId"` + UserScoreVO vo.UserScoreVO `json:"userScoreVO"` +} diff --git a/server/modules/user/service/user_score_service.go b/server/modules/user/service/user_score_service.go index 732bb00..4cbf417 100644 --- a/server/modules/user/service/user_score_service.go +++ b/server/modules/user/service/user_score_service.go @@ -2,6 +2,8 @@ package service import ( + "context" + "encoding/json" "errors" "fmt" "server/common" @@ -23,21 +25,44 @@ type UserScoreService struct { mapper *mapper.YxUserScoreMapper } +func (s *UserScoreService) GetUserScoreByUserId(id string) { + panic("unimplemented") +} + // GetActiveByID 获取当前激活的成绩 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"). - First(&score).Error + + scoreRedisData, err := config.RDB.Get(context.Background(), common.RedisUserScorePrefix+userID).Result() if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return vo.UserScoreVO{}, fmt.Errorf("未找到激活的成绩记录") + // 明确指定字段,提高可读性 + err := config.DB.Model(&entity.YxUserScore{}). + Where("create_by = ? AND state = ?", userID, "1"). + First(&score).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return vo.UserScoreVO{}, fmt.Errorf("未找到激活的成绩记录") + } + return vo.UserScoreVO{}, fmt.Errorf("查询成绩记录失败: %w", err) } - return vo.UserScoreVO{}, fmt.Errorf("查询成绩记录失败: %w", err) + // 缓存到 Redis + scoreRedisSetData, err := json.Marshal(score) + if err != nil { + return vo.UserScoreVO{}, fmt.Errorf("序列化成绩记录失败: %w", err) + } + err = config.RDB.Set(context.Background(), common.RedisUserScorePrefix+userID, scoreRedisSetData, common.RedisUserScoreExpire).Err() + if err != nil { + return vo.UserScoreVO{}, fmt.Errorf("缓存成绩记录失败: %w", err) + } + } else { + if err := json.Unmarshal([]byte(scoreRedisData), &score); err != nil { + return vo.UserScoreVO{}, fmt.Errorf("解析 Redis 数据失败: %w", err) + } + // 刷新过期时间 + config.RDB.Expire(context.Background(), common.RedisUserScorePrefix+userID, common.RedisUserScoreExpire) } - return s.convertEntityToVo(&score), nil + return s.convertEntityToVo(score), nil } // GetByID 根据 ID 获取成绩 @@ -53,7 +78,7 @@ func (s *UserScoreService) GetByID(id string) (vo.UserScoreVO, error) { return vo.UserScoreVO{}, fmt.Errorf("查询失败: %w", err) } - return s.convertEntityToVo(&score), nil + return s.convertEntityToVo(score), nil } // ListByUser 获取用户的成绩分页列表 @@ -73,7 +98,7 @@ func (s *UserScoreService) ListByUser(userID string, page, size int) ([]vo.UserS vos := make([]vo.UserScoreVO, 0, len(scores)) for i := range scores { - vos = append(vos, s.convertEntityToVo(&scores[i])) + vos = append(vos, s.convertEntityToVo(scores[i])) } return vos, total, nil @@ -81,8 +106,9 @@ func (s *UserScoreService) ListByUser(userID string, page, size int) ([]vo.UserS func NewUserScoreService() *UserScoreService { return &UserScoreService{ - yxUserScoreService: service.NewYxUserScoreService(), - mapper: mapper.NewYxUserScoreMapper(), + yxUserScoreService: service.NewYxUserScoreService(), + yxCalculationMajorService: service.NewYxCalculationMajorService(), + mapper: mapper.NewYxUserScoreMapper(), } } @@ -146,8 +172,8 @@ func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (vo.UserScor } // 私有方法:DTO 转 Entity -func (s *UserScoreService) convertDtoToEntity(req *dto.SaveScoreRequest) *entity.YxUserScore { - entityItem := &entity.YxUserScore{ +func (s *UserScoreService) convertDtoToEntity(req *dto.SaveScoreRequest) entity.YxUserScore { + entityItem := entity.YxUserScore{ CognitioPolyclinic: req.CognitioPolyclinic, Subjects: strings.Join(req.SubjectList, ","), ProfessionalCategory: req.ProfessionalCategory, @@ -185,7 +211,7 @@ func (s *UserScoreService) convertDtoToEntity(req *dto.SaveScoreRequest) *entity } // 私有方法:Entity 转 VO -func (s *UserScoreService) convertEntityToVo(item *entity.YxUserScore) vo.UserScoreVO { +func (s *UserScoreService) convertEntityToVo(item entity.YxUserScore) vo.UserScoreVO { voItem := vo.UserScoreVO{ ID: item.ID, Type: item.Type, diff --git a/server/modules/yx/dto/yx_school_major_dto.go b/server/modules/yx/dto/yx_school_major_dto.go index d03adaa..98b2fea 100644 --- a/server/modules/yx/dto/yx_school_major_dto.go +++ b/server/modules/yx/dto/yx_school_major_dto.go @@ -1,6 +1,9 @@ package dto -import "server/modules/yx/entity" +import ( + userVO "server/modules/user/vo" + "server/modules/yx/entity" +) // SchoolMajorDTO 院校专业查询结果 DTO type SchoolMajorDTO struct { @@ -52,8 +55,20 @@ type YxHistoryMajorEnrollDTO struct { // SchoolMajorQuery 院校专业查询条件 type SchoolMajorQuery struct { - MajorType string `json:"majorType"` // 对应 major_type - Category string `json:"category"` // 对应 category - MajorTypeChildren []string `json:"majorTypeChildren"` // 对应 major_type_child in (...) - MainSubjects string `json:"mainSubjects"` // 对应 main_subjects + Page int `json:"page"` + Size int `json:"size"` + MajorType string `json:"majorType"` // 对应 major_type + Category string `json:"category"` // 对应 category + Batch string `json:"batch"` // 对应 batch + MajorTypeChildren []string `json:"majorTypeChildren"` // 对应 major_type_child in (...) + MainSubjects string `json:"mainSubjects"` // 对应 main_subjects + ScoreId string `json:"scoreId"` // 对应 score_id + TagList []string `json:"tagList"` // 对应 tags in (...) + SchoolNatureList []string `json:"schoolNatureList"` // 对应 school_nature in (...) + AddressList []string `json:"addressList"` // 对应 address in (...) + KyjxList []string `json:"kyjxList"` // 对应 kyjx in (...) + RulesEnrollProbabilityList []string `json:"rulesEnrollProbabilityList"` // 对应 rules_enroll_probability in (...) + LoginUserId string `json:"loginUserId"` // 登录用户 ID + UserScoreVO userVO.UserScoreVO `json:"userScoreVO"` // 用户成绩 VO + CalculationTableName string `json:"calculationTableName"` // 对应 calculation_table_name } diff --git a/server/modules/yx/mapper/yx_calculation_major_mapper.go b/server/modules/yx/mapper/yx_calculation_major_mapper.go index e1ed3a5..62deb6e 100644 --- a/server/modules/yx/mapper/yx_calculation_major_mapper.go +++ b/server/modules/yx/mapper/yx_calculation_major_mapper.go @@ -2,8 +2,12 @@ package mapper import ( + "fmt" "server/config" + "server/modules/yx/dto" "server/modules/yx/entity" + "strings" + "sync" "gorm.io/gorm/clause" ) @@ -22,6 +26,104 @@ func (m *YxCalculationMajorMapper) FindAll(page, size int) ([]entity.YxCalculati return items, total, err } +func (m *YxCalculationMajorMapper) FindRecommendList(query dto.SchoolMajorQuery) ([]entity.YxCalculationMajor, int64, error) { + var items []entity.YxCalculationMajor + var total int64 + countSQL := ` + SELECT COUNT(cm.id) FROM yx_calculation_major cm + LEFT JOIN (SELECT school_id,school_name,school_code FROM yx_school_child group by school_code) sc ON sc.school_code = cm.school_code + LEFT JOIN yx_school s ON s.id = sc.school_id + WHERE 1=1 AND cm.state > 0 + ` + + sql := ` + SELECT + sc.school_code, + sc.school_name, + cm.major_code, + cm.major_name, + cm.major_type, + cm.major_type_child, + cm.plan_num, + cm.main_subjects, + cm.limitation, + cm.other_score_limitation, + cm.enrollment_code, + cm.tuition, + cm.detail, + cm.category, + cm.batch, + cm.rules_enroll_probability, + cm.probability_operator, + cm.private_rules_enroll_probability, + cm.private_probability_operator, + cm.rules_enroll_probability_sx, + cm.kslx, + cm.state + FROM yx_calculation_major cm + LEFT JOIN (SELECT school_id,school_name,school_code FROM yx_school_child group by school_code) sc ON sc.school_code = cm.school_code + LEFT JOIN yx_school s ON s.id = sc.school_id + WHERE 1=1 AND cm.state > 0 + ` + params := []interface{}{} + + if query.UserScoreVO.ID != "" { + countSQL += " AND cm.score_id = ?" + sql += " AND cm.score_id = ?" + params = append(params, query.UserScoreVO.ID) + } + + if query.MajorType != "" { + countSQL += " AND cm.major_type = ?" + sql += " AND cm.major_type = ?" + params = append(params, query.MajorType) + } + if query.Category != "" { + countSQL += " AND cm.category = ?" + sql += " AND cm.category = ?" + params = append(params, query.Category) + } + if len(query.MajorTypeChildren) > 0 { + placeholders := strings.Repeat("?,", len(query.MajorTypeChildren)-1) + "?" + countSQL += " AND cm.major_type_child IN (" + placeholders + ")" + sql += " AND cm.major_type_child IN (" + placeholders + ")" + for _, v := range query.MajorTypeChildren { + params = append(params, v) + } + } + + if query.MainSubjects != "" { + countSQL += " AND cm.main_subjects = ?" + sql += " AND cm.main_subjects = ?" + params = append(params, query.MainSubjects) + } + + countSQL = strings.Replace(countSQL, "yx_calculation_major", "yx_calculation_major_2025_2", -1) + sql = strings.Replace(sql, "yx_calculation_major", "yx_calculation_major_2025_2", -1) + + var wg sync.WaitGroup + var countErr, queryErr error + + wg.Add(2) + // 协程1:COUNT 查询 + go func() { + defer wg.Done() + countErr = config.DB.Raw(countSQL, params...).Count(&total).Error + }() + + // 协程2:主查询 + go func() { + defer wg.Done() + sql += fmt.Sprintf(" LIMIT %d OFFSET %d", query.Size, (query.Page-1)*query.Size) + queryErr = config.DB.Raw(sql, params...).Scan(&items).Error + }() + wg.Wait() + if countErr != nil || queryErr != nil { + return nil, 0, fmt.Errorf("countErr: %v, queryErr: %v", countErr, queryErr) + } + return items, total, queryErr +} + func (m *YxCalculationMajorMapper) FindByID(id string) (*entity.YxCalculationMajor, error) { var item entity.YxCalculationMajor err := config.DB.First(&item, "id = ?", id).Error diff --git a/server/modules/yx/service/yx_calculation_major_service.go b/server/modules/yx/service/yx_calculation_major_service.go index 16cc0ab..18792de 100644 --- a/server/modules/yx/service/yx_calculation_major_service.go +++ b/server/modules/yx/service/yx_calculation_major_service.go @@ -6,6 +6,7 @@ import ( "server/common" "server/modules/user/vo" "server/modules/yx/dto" + yxDto "server/modules/yx/dto" "server/modules/yx/entity" "server/modules/yx/mapper" "strings" @@ -20,6 +21,25 @@ type YxCalculationMajorService struct { historyScoreControlLineService *YxHistoryScoreControlLineService } +func (s *YxCalculationMajorService) RecommendMajorList(schoolMajorQuery yxDto.SchoolMajorQuery) (any, int64, error) { + if schoolMajorQuery.UserScoreVO.ProfessionalCategory == "" { + return nil, 0, fmt.Errorf("专业类型错误") + } + if schoolMajorQuery.Batch != "" { + schoolMajorQuery.Batch = strings.ReplaceAll(schoolMajorQuery.Batch, "批", "") + } + // if len(schoolMajorQuery.MajorTypeChildren) > 0 && "高职高专" != schoolMajorQuery.Batch { + // if "表演类" == schoolMajorQuery.MajorType { + + // } + // } + calculationMajors, total, err := s.mapper.FindRecommendList(schoolMajorQuery) + if err != nil { + return nil, 0, err + } + return calculationMajors, total, nil +} + func (s *YxCalculationMajorService) BatchCreateBySchoolMajorDTO(items []dto.SchoolMajorDTO, scoreID string) error { entities := make([]entity.YxCalculationMajor, 0, len(items)) now := time.Now()