This commit is contained in:
zhouwentao 2025-12-20 20:53:26 +08:00
parent ff2651c1eb
commit 60dc668f1d
12 changed files with 423 additions and 20 deletions

View File

@ -7,6 +7,7 @@
"Fzby",
"gonic",
"gorm",
"kslx",
"swaggo",
"Xjysby",
"Xjysdy",

35
Task5.md Normal file
View File

@ -0,0 +1,35 @@
```
select
sm.school_code,
sc.school_name,
sm.major_code,
sm.major_name,
sm.major_type,
sm.major_type_child,
sm.plan_num,
sm.main_subjects,
sm.limitation,
sm.chinese_score_limitation,
sm.english_score_limitation,
sm.cultural_score_limitation,
sm.professional_score_limitation,
sm.enrollment_code,
sm.tuition,
sm.detail,
sm.category,
sm.batch,
sm.rules_enroll_probability as rulesEnrollProbability,
sm.probability_operator as probabilityOperator,
sm.private_rules_enroll_probability as privateRulesEnrollProbability,
sm.private_probability_operator as privateProbabilityOperator,
sm.rules_enroll_probability_sx,
sm.kslx,
sm.state
FROM yitisheng.yx_school_major sm
LEFT JOIN (SELECT school_id,school_name,school_code FROM yitisheng.yx_school_child group by school_code) sc ON sc.school_code = sm.school_code
LEFT JOIN yitisheng.yx_school s ON s.id = sc.school_id
where 1=1 and sm.state >0
and sm.major_type = '音乐类' and sm.category = '文科' and major_type_child in ('音乐教育') and sm.main_subjects = '器乐'
```
请你参考这个查询SQL帮我在#yx_school_major_mapper.go中实现这个查询Where后条件是传递过去的。
这个返回结果封装成DTO。

View File

@ -93,3 +93,19 @@
- 创建了 `server/common/id_utils.go`,提供了基于时间戳 + 序列号的 ID 生成逻辑。
- 支持生成 `int64` 类型的 ID也支持直接生成 `string` 类型以便于 Entity 使用。
- 在 `UserScoreService.SaveUserScore` 中引入了该工具,手动为新记录分配 ID。
### [任务执行] 实现院校专业复杂查询
- **操作目标**: 在 `YxSchoolMajorMapper` 中实现基于复杂 SQL 的查询,并将结果封装为 DTO。
- **影响范围**: `server/modules/yx/dto/yx_school_major_dto.go`, `server/modules/yx/mapper/yx_school_major_mapper.go`
- **修改前记录**: 只有基本的 CRUD 操作。
- **修改结果**:
- 创建了 `SchoolMajorDTO``SchoolMajorQuery` 结构体。
- 在 `YxSchoolMajorMapper` 中实现了 `SelectSchoolMajor` 方法,支持 `major_type`, `category`, `major_type_child` (IN查询), `main_subjects` 的动态过滤。
### [任务执行] 搭建本地测试环境
- **操作目标**: 创建独立的测试目录和初始化脚本,以便在不启动 Web 服务的情况下测试 Mapper、Service 和 Redis。
- **影响范围**: `server/tests/init_test.go`, `server/tests/service_test.go`
- **修改前记录**: 只能通过 API 接口进行测试,调试效率低。
- **修改结果**:
- 创建了 `server/tests/init_test.go`,利用 `init()` 函数自动初始化 DB 和 Redis 连接。
- 创建了 `server/tests/service_test.go`,编写了 Redis 读写、复杂 SQL 查询和 ID 生成器的测试用例。

View File

@ -6,6 +6,9 @@
- `config.go`: 应用全局配置。
- `database.go`: 数据库连接配置。
- `redis.go`: Redis连接配置。
- `tests/`: 单元测试与集成测试目录。
- `init_test.go`: 测试环境初始化(自动连接 DB 和 Redis
- `service_test.go`: 业务逻辑与数据访问测试用例。
- `common/`: 通用工具包。
- `constants.go`: 全局常量定义Redis Key、业务状态等
- `id_utils.go`: ID 生成工具(基于时间戳)。

View File

@ -0,0 +1,50 @@
package dto
// SchoolMajorDTO 院校专业查询结果 DTO
type SchoolMajorDTO struct {
SchoolCode string `json:"schoolCode"`
SchoolName string `json:"schoolName"`
MajorCode string `json:"majorCode"`
MajorName string `json:"majorName"`
MajorType string `json:"majorType"`
MajorTypeChild string `json:"majorTypeChild"`
PlanNum int `json:"planNum"`
MainSubjects string `json:"mainSubjects"`
Limitation string `json:"limitation"`
ChineseScoreLimitation float64 `json:"chineseScoreLimitation"`
EnglishScoreLimitation float64 `json:"englishScoreLimitation"`
CulturalScoreLimitation float64 `json:"culturalScoreLimitation"`
ProfessionalScoreLimitation float64 `json:"professionalScoreLimitation"`
EnrollmentCode string `json:"enrollmentCode"`
Tuition string `json:"tuition"`
Detail string `json:"detail"`
Category string `json:"category"`
Batch string `json:"batch"`
RulesEnrollProbability string `json:"rulesEnrollProbability"`
ProbabilityOperator string `json:"probabilityOperator"`
PrivateRulesEnrollProbability string `json:"privateRulesEnrollProbability"`
PrivateProbabilityOperator string `json:"privateProbabilityOperator"`
RulesEnrollProbabilitySx string `json:"rulesEnrollProbabilitySx"`
Kslx string `json:"kslx"`
State string `json:"state"`
HistoryMajorEnrollMap map[string]YxHistoryMajorEnrollDTO `json:"historyMajorEnrollMap"`
}
type YxHistoryMajorEnrollDTO struct {
Year string `json:"year"`
EnrollmentCode string `json:"enrollmentCode"`
EnrollmentCount int `json:"enrollmentCount"`
RulesEnrollProbability string `json:"rulesEnrollProbability"`
ProbabilityOperator string `json:"probabilityOperator"`
AdmissionLine float64 `json:"admissionLine"`
ControlLine float64 `json:"controlLine"`
// 其他字段...
}
// 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
}

View File

@ -3,6 +3,7 @@ package mapper
import (
"server/config"
"server/modules/yx/dto"
"server/modules/yx/entity"
"gorm.io/gorm/clause"
@ -62,3 +63,61 @@ func (m *YxSchoolMajorMapper) BatchUpsert(items []entity.YxSchoolMajor, updateCo
func (m *YxSchoolMajorMapper) BatchDelete(ids []string) error {
return config.DB.Delete(&entity.YxSchoolMajor{}, "id IN ?", ids).Error
}
func (m *YxSchoolMajorMapper) SelectSchoolMajor(query dto.SchoolMajorQuery) ([]dto.SchoolMajorDTO, error) {
var items []dto.SchoolMajorDTO
sql := `
SELECT
sm.school_code,
sc.school_name,
sm.major_code,
sm.major_name,
sm.major_type,
sm.major_type_child,
sm.plan_num,
sm.main_subjects,
sm.limitation,
sm.chinese_score_limitation,
sm.english_score_limitation,
sm.cultural_score_limitation,
sm.professional_score_limitation,
sm.enrollment_code,
sm.tuition,
sm.detail,
sm.category,
sm.batch,
sm.rules_enroll_probability,
sm.probability_operator,
sm.private_rules_enroll_probability,
sm.private_probability_operator,
sm.rules_enroll_probability_sx,
sm.kslx,
sm.state
FROM yx_school_major sm
LEFT JOIN (SELECT school_id,school_name,school_code FROM yx_school_child group by school_code) sc ON sc.school_code = sm.school_code
LEFT JOIN yx_school s ON s.id = sc.school_id
WHERE 1=1 AND sm.state > 0
`
params := []interface{}{}
if query.MajorType != "" {
sql += " AND sm.major_type = ?"
params = append(params, query.MajorType)
}
if query.Category != "" {
sql += " AND sm.category = ?"
params = append(params, query.Category)
}
if len(query.MajorTypeChildren) > 0 {
sql += " AND sm.major_type_child IN ?"
params = append(params, query.MajorTypeChildren)
}
if query.MainSubjects != "" {
sql += " AND sm.main_subjects = ?"
params = append(params, query.MainSubjects)
}
err := config.DB.Raw(sql, params...).Scan(&items).Error
return items, err
}

View File

@ -2,6 +2,8 @@
package service
import (
"server/modules/user/vo"
"server/modules/yx/dto"
"server/modules/yx/entity"
"server/modules/yx/mapper"
@ -9,11 +11,15 @@ import (
)
type YxCalculationMajorService struct {
mapper *mapper.YxCalculationMajorMapper
historyMajorEnrollService *YxHistoryMajorEnrollService
mapper *mapper.YxCalculationMajorMapper
}
func NewYxCalculationMajorService() *YxCalculationMajorService {
return &YxCalculationMajorService{mapper: mapper.NewYxCalculationMajorMapper()}
return &YxCalculationMajorService{
historyMajorEnrollService: NewYxHistoryMajorEnrollService(),
mapper: mapper.NewYxCalculationMajorMapper(),
}
}
func (s *YxCalculationMajorService) List(page, size int) ([]entity.YxCalculationMajor, int64, error) {
@ -72,3 +78,113 @@ func (s *YxCalculationMajorService) BatchDelete(ids []string) error {
func (s *YxCalculationMajorService) DeleteByScoreID(scoreID string) error {
return s.mapper.DeleteByScoreID(scoreID)
}
// 函数名 根据用户查询类型获取专业列表
// 详细描述(可选)
//
// 参数说明:
//
// professionalCategory - 专业分类
// cognitioPolyclinic - 文理文科
// professionalCategoryChildren - 专业分类子项
//
// 返回值说明:
//
// 返回值类型 - 返回值描述
func (s *YxCalculationMajorService) ListByUserQueryType(professionalCategory string, cognitioPolyclinic string,
professionalCategoryChildren []string) ([]dto.SchoolMajorDTO, error) {
// 构造查询条件
query := dto.SchoolMajorQuery{
MajorType: professionalCategory,
Category: cognitioPolyclinic,
// MainSubjects: "器乐",
// MajorTypeChildren: []string{
// "音乐教育",
// },
}
years := []string{"2025", "2024"}
// 执行院校查询
majorItems, err := mapper.NewYxSchoolMajorMapper().SelectSchoolMajor(query)
if err != nil {
return nil, err
}
// 分别收集专业名称和院校代码集合(去重)
majorNameSet := make(map[string]bool)
schoolCodeSet := make(map[string]bool)
for _, dto := range majorItems {
majorNameSet[dto.MajorName] = true
schoolCodeSet[dto.SchoolCode] = true
}
// 转换为切片用于查询
majorNames := make([]string, 0, len(majorNameSet))
schoolCodes := make([]string, 0, len(schoolCodeSet))
for name := range majorNameSet {
majorNames = append(majorNames, name)
}
for code := range schoolCodeSet {
schoolCodes = append(schoolCodes, code)
}
// 执行查询院校专业的历年数据 - 类似Java中的in查询
historyItems, err := s.historyMajorEnrollService.ListBySchoolCodesAndMajorNames(
schoolCodes,
majorNames,
query.Category, // 文科/理科
query.MajorType, // 专业类型
years, // 年份
)
if err != nil {
return nil, err
}
// 构建历史数据映射schoolCode_majorName_batch_year -> historyItem
allHistoryMajorEnrollMap := make(map[string]entity.YxHistoryMajorEnroll)
for _, historyItem := range historyItems {
key := historyItem.SchoolCode + "_" + historyItem.MajorName + "_" + historyItem.Batch + "_" + historyItem.Year
allHistoryMajorEnrollMap[key] = historyItem
}
// 将历史数据填充到每个专业数据中
for i, majorItem := range majorItems {
// 为每个majorItem创建独立的历史数据映射
historyMap := make(map[string]dto.YxHistoryMajorEnrollDTO)
for _, year := range years {
key := majorItem.SchoolCode + "_" + majorItem.MajorName + "_" + majorItem.Batch + "_" + year
if historyItem, ok := allHistoryMajorEnrollMap[key]; ok {
// 类型转换entity -> dto
dtoItem := dto.YxHistoryMajorEnrollDTO{
Year: historyItem.Year,
EnrollmentCode: historyItem.EnrollmentCode,
RulesEnrollProbability: historyItem.RulesEnrollProbability,
ProbabilityOperator: historyItem.ProbabilityOperator,
AdmissionLine: historyItem.AdmissionLine,
ControlLine: historyItem.ControlLine,
// 复制其他需要的字段
}
historyMap[year] = dtoItem
}
}
majorItems[i].HistoryMajorEnrollMap = historyMap
}
return majorItems, nil
}
// 函数名 给专业列表计算录取率
// 详细描述(可选)
//
// 参数说明:
//
// schoolMajorDTOList - 专业列表
// professionalCategory - 专业分类
// cognitioPolyclinic - 文理文科
// professionalCategoryChildren - 专业分类子项
//
// 返回值说明:
//
// 返回值类型 - 返回值描述
func (s *YxCalculationMajorService) CheckEnrollProbability(schoolMajorDTOList *[]dto.SchoolMajorDTO, userScoreVO vo.UserScoreVO) error {
return nil
}

View File

@ -2,6 +2,7 @@
package service
import (
"server/config"
"server/modules/yx/entity"
"server/modules/yx/mapper"
@ -12,6 +13,46 @@ type YxHistoryMajorEnrollService struct {
mapper *mapper.YxHistoryMajorEnrollMapper
}
// HistoryMajorEnrollService 中的方法
func (s *YxHistoryMajorEnrollService) ListBySchoolCodesAndMajorNames(
schoolCodes []string,
majorNames []string,
category string,
majorType string,
years []string,
) ([]entity.YxHistoryMajorEnroll, error) {
var items []entity.YxHistoryMajorEnroll
sql := `
SELECT hme.*
FROM yx_history_major_enroll hme
WHERE 1=1 AND hme.rules_enroll_probability is not null AND hme.admission_line > 0
`
params := []interface{}{}
if majorType != "" {
sql += " AND hme.major_type = ?"
params = append(params, majorType)
}
if category != "" {
sql += " AND hme.category = ?"
params = append(params, category)
}
if len(schoolCodes) > 0 {
sql += " AND hme.school_code IN ?"
params = append(params, schoolCodes)
}
if len(majorNames) > 0 {
sql += " AND hme.major_name IN ?"
params = append(params, majorNames)
}
if len(years) > 0 {
sql += " AND hme.year IN ?"
params = append(params, years)
}
sql += " ORDER BY hme.year DESC"
err := config.DB.Raw(sql, params...).Scan(&items).Error
return items, err
}
func NewYxHistoryMajorEnrollService() *YxHistoryMajorEnrollService {
return &YxHistoryMajorEnrollService{mapper: mapper.NewYxHistoryMajorEnrollMapper()}
}

View File

@ -64,21 +64,3 @@ 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
}

6
server/tests/help.md Normal file
View File

@ -0,0 +1,6 @@
```
cd server/tests
go test -v -run TestRedisOperation # 运行 Redis 测试
go test -v -run TestSchoolMajorQuery # 运行 SQL 查询测试
go test -v # 运行所有测试
```

25
server/tests/init_test.go Normal file
View File

@ -0,0 +1,25 @@
package tests
import (
"log"
"os"
"path/filepath"
"runtime"
"server/config"
)
func init() {
// 切换工作目录到 server 根目录,以便读取配置文件(如果有)
// 这里直接初始化配置,不依赖文件,但保留目录切换逻辑以防万一
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Join(filepath.Dir(filename), "..")
err := os.Chdir(dir)
if err != nil {
log.Fatal("切换工作目录失败:", err)
}
// 初始化数据库
config.InitDB()
// 初始化Redis
config.InitRedis()
}

View File

@ -0,0 +1,69 @@
package tests
import (
"context"
"server/common"
"server/config"
"server/modules/yx/service"
"testing"
"time"
)
// TestRedisOperation 测试 Redis 读写
func TestRedisOperation(t *testing.T) {
ctx := context.Background()
key := "test:hello"
value := "world_" + time.Now().Format("2006-01-02 15:04:05")
// 写 Redis
err := config.RDB.Set(ctx, key, value, time.Minute).Err()
if err != nil {
t.Fatalf("Redis 写入失败: %v", err)
}
t.Logf("Redis 写入成功: %s -> %s", key, value)
// 读 Redis
val, err := config.RDB.Get(ctx, key).Result()
if err != nil {
t.Fatalf("Redis 读取失败: %v", err)
}
t.Logf("Redis 读取成功: %s", val)
if val != value {
t.Errorf("Redis 值不匹配: 期望 %s, 实际 %s", value, val)
}
}
// TestSchoolMajorQuery 测试院校专业复杂查询 Mapper
func TestSchoolMajorQuery(t *testing.T) {
cs := service.NewYxCalculationMajorService()
start := time.Now()
items, err := cs.ListByUserQueryType("美术与设计类", "文科", []string{})
if err != nil {
t.Fatalf("查询失败: %v", err)
}
duration := time.Since(start)
t.Logf("查询耗时: %v", duration)
t.Logf("查询成功,共找到 %d 条记录", len(items))
// 打印前几条结果
// for i, item := range items {
// if i >= 3 {
// break
// }
// jsonBytes, _ := json.Marshal(item)
// t.Logf("记录 #%d: %s", i+1, string(jsonBytes))
// }
}
// TestIDGenerator 测试 ID 生成器
func TestIDGenerator(t *testing.T) {
idLong := common.GenerateLongID()
t.Logf("生成的 Long ID: %d", idLong)
idStr := common.GenerateStringID()
t.Logf("生成的 String ID: %s", idStr)
idTimestamp := common.GetIDGenerator().GenerateTimestampLongID()
t.Logf("生成的 Timestamp Long ID: %d", idTimestamp)
}