This commit is contained in:
zhouwentao 2026-01-02 21:38:30 +08:00
parent 11b467bd56
commit 1f9d5a1431
4 changed files with 238 additions and 9 deletions

View File

@ -60,10 +60,15 @@ func (ctrl *UserMajorController) List(c *gin.Context) {
return
}
schoolMajorQuery.UserScoreVO = userScoreVO
items, total, err := ctrl.yxCalculationMajorService.RecommendMajorList(schoolMajorQuery)
items, total, probCount, err := ctrl.yxCalculationMajorService.RecommendMajorList(schoolMajorQuery)
if err != nil {
common.Error(c, 500, err.Error())
return
}
common.SuccessPage(c, items, total, page, size)
newMap := map[string]interface{}{
"items": items,
"total": total,
"probCount": probCount,
}
common.SuccessPage(c, newMap, total, page, size)
}

View File

@ -0,0 +1,9 @@
package dto
// 定义概率数量统计结果结构体,用于接收四种录取概率对应的各自数量
type ProbabilityCountDTO struct {
HardAdmit int64 `gorm:"column:hard_admit"` // 难录取(<60
Impact int64 `gorm:"column:impact"` // 可冲击60<=x<73
Stable int64 `gorm:"column:stable"` // 较稳妥73<=x<93
Secure int64 `gorm:"column:secure"` // 可保底(>=93
}

View File

@ -8,6 +8,7 @@ import (
"server/modules/yx/entity"
"strings"
"sync"
"time"
"gorm.io/gorm/clause"
)
@ -18,6 +19,14 @@ func NewYxCalculationMajorMapper() *YxCalculationMajorMapper {
return &YxCalculationMajorMapper{}
}
// 先定义存储各协程耗时的结构体(局部使用,也可全局复用)
type QueryCostTime struct {
CountCost time.Duration // 总数量查询耗时
ProbCountCost time.Duration // 四种概率数量查询耗时
QueryCost time.Duration // 主列表查询耗时
TotalCost time.Duration // 整体总耗时
}
func (m *YxCalculationMajorMapper) FindAll(page, size int) ([]entity.YxCalculationMajor, int64, error) {
var items []entity.YxCalculationMajor
var total int64
@ -26,7 +35,208 @@ func (m *YxCalculationMajorMapper) FindAll(page, size int) ([]entity.YxCalculati
return items, total, err
}
func (m *YxCalculationMajorMapper) FindRecommendList(query dto.SchoolMajorQuery) ([]dto.UserMajorDTO, int64, error) {
// 调整返回值:新增 ProbabilityCountDTO返回列表、总数量、四种概率各自数量
func (m *YxCalculationMajorMapper) FindRecommendList(query dto.SchoolMajorQuery) ([]dto.UserMajorDTO, int64, dto.ProbabilityCountDTO, error) {
var items []dto.UserMajorDTO
var total int64
var probCount dto.ProbabilityCountDTO // 四种概率的数量统计结果
// 1. 表名合法性校验:非空 + 白名单
tableName := query.UserScoreVO.CalculationTableName
if tableName == "" {
return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("CalculationTableName is empty")
}
// if !validTableNames[] {
// return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("invalid table name: %s, potential SQL injection risk", tableName)
// }
// 2. 基础条件SQL共用过滤条件排除概率筛选
baseSQL := " WHERE 1=1 AND cm.state > 0 "
params := []interface{}{}
// 拼接共用过滤条件(与原有列表查询条件一致,保证统计结果准确性)
if query.UserScoreVO.ID != "" {
baseSQL += " AND cm.score_id = ?"
params = append(params, query.UserScoreVO.ID)
}
if query.MajorType != "" {
baseSQL += " AND cm.major_type = ?"
params = append(params, query.MajorType)
}
if query.Category != "" {
baseSQL += " AND cm.category = ?"
params = append(params, query.Category)
}
if len(query.MajorTypeChildren) > 0 {
placeholders := strings.Repeat("?,", len(query.MajorTypeChildren)-1) + "?"
baseSQL += " AND cm.major_type_child IN (" + placeholders + ")"
for _, v := range query.MajorTypeChildren {
params = append(params, v)
}
}
if query.MainSubjects != "" {
baseSQL += " AND cm.main_subjects = ?"
params = append(params, query.MainSubjects)
}
// 3. 优化后的总数量COUNT SQL
countSQL := fmt.Sprintf(`
SELECT COUNT(cm.id) FROM %s cm
%s
`, tableName, baseSQL)
// 4. 四种概率批量统计SQL使用CASE WHEN一次查询性能最优
probCountSQL := fmt.Sprintf(`
SELECT
SUM(CASE WHEN cm.enroll_probability < 60 THEN 1 ELSE 0 END) AS hard_admit,
SUM(CASE WHEN cm.enroll_probability >= 60 AND cm.enroll_probability < 73 THEN 1 ELSE 0 END) AS impact,
SUM(CASE WHEN cm.enroll_probability >= 73 AND cm.enroll_probability < 93 THEN 1 ELSE 0 END) AS stable,
SUM(CASE WHEN cm.enroll_probability >= 93 THEN 1 ELSE 0 END) AS secure
FROM %s cm
%s
`, tableName, baseSQL)
// 5. 主查询SQL保留原有字段和JOIN
mainSQL := fmt.Sprintf(`
SELECT
cm.id,
s.school_name,
s.school_icon,
cm.state,
cm.school_code,
cm.major_code,
cm.major_name,
cm.enrollment_code,
cm.tuition,
cm.detail as majorDetail,
cm.category,
cm.batch,
cm.private_student_converted_score as privateStudentScore,
cm.student_old_converted_score as studentScore,
cm.student_converted_score,
cm.enroll_probability,
cm.rules_enroll_probability_sx,
cm.rules_enroll_probability,
cm.probability_operator,
cm.major_type,
cm.major_type_child,
cm.plan_num,
cm.main_subjects,
cm.limitation,
cm.other_score_limitation,
s.province as province,
s.school_nature as schoolNature,
s.institution_type as institutionType
FROM %s cm
LEFT JOIN yx_school_child sc ON sc.school_code = cm.school_code
LEFT JOIN yx_school_research_teaching srt ON srt.school_id = sc.school_id
LEFT JOIN yx_school s ON s.id = sc.school_id
%s
`, tableName, baseSQL)
// 拼接传入概率的筛选条件(兼容原有业务逻辑)
switch query.Probability {
case "难录取":
mainSQL += " AND cm.enroll_probability < 60"
case "可冲击":
mainSQL += " AND (cm.enroll_probability >= 60 and cm.enroll_probability < 73)"
case "较稳妥":
mainSQL += " AND (cm.enroll_probability >= 73 and cm.enroll_probability < 93)"
case "可保底":
mainSQL += " AND (cm.enroll_probability >= 93)"
}
// 6. 分页参数合法性校验
page := query.Page
size := query.Size
if page < 1 {
page = 1
}
if size < 1 {
size = 10
}
if size > 100 {
size = 100
}
offset := (page - 1) * size
// 提前拼接分页条件,避免协程内操作共享变量
mainSQL += fmt.Sprintf(" LIMIT %d OFFSET %d", size, offset)
// 7. 协程并发执行三个查询(总数量、概率数量、主列表),提升性能
// ---------------------- 核心局部代码(替换你原来的协程块) ----------------------
var wg sync.WaitGroup
var countErr, probCountErr, queryErr error
var queryCost QueryCostTime // 存储各协程耗时
var mu sync.Mutex // 互斥锁防止多协程同时修改queryCost引发竞态问题
// 整体开始时间
totalStartTime := time.Now()
wg.Add(3)
// 协程1总数量查询单独记录耗时
go func() {
defer wg.Done()
// 记录该协程单独的开始时间
start := time.Now()
countErr = config.DB.Raw(countSQL, params...).Count(&total).Error
// 计算该协程耗时,通过互斥锁安全写入共享变量
mu.Lock()
queryCost.CountCost = time.Now().Sub(start)
mu.Unlock()
}()
// 协程2四种概率数量批量查询单独记录耗时
go func() {
defer wg.Done()
// 记录该协程单独的开始时间
start := time.Now()
probCountErr = config.DB.Raw(probCountSQL, params...).Scan(&probCount).Error
// 计算该协程耗时,通过互斥锁安全写入共享变量
mu.Lock()
queryCost.ProbCountCost = time.Now().Sub(start)
mu.Unlock()
}()
// 协程3主列表查询单独记录耗时
go func() {
defer wg.Done()
// 记录该协程单独的开始时间
start := time.Now()
queryErr = config.DB.Raw(mainSQL, params...).Scan(&items).Error
// 计算该协程耗时,通过互斥锁安全写入共享变量
mu.Lock()
queryCost.QueryCost = time.Now().Sub(start)
mu.Unlock()
}()
wg.Wait()
// 计算整体总耗时
queryCost.TotalCost = time.Now().Sub(totalStartTime)
// 打印各协程耗时和总耗时(按需输出,可注释或删除)
fmt.Printf("各查询耗时统计:\n")
fmt.Printf(" 总数量查询耗时:%v\n", queryCost.CountCost)
fmt.Printf(" 概率数量查询耗时:%v\n", queryCost.ProbCountCost)
fmt.Printf(" 主列表查询耗时:%v\n", queryCost.QueryCost)
fmt.Printf(" 整体总耗时:%v\n", queryCost.TotalCost)
// 8. 错误处理
if countErr != nil {
return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("failed to query total count: %w", countErr)
}
if probCountErr != nil {
return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("failed to query probability count: %w", probCountErr)
}
if queryErr != nil {
return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("failed to query recommend major list: %w", queryErr)
}
return items, total, probCount, nil
}
func (m *YxCalculationMajorMapper) FindRecommendList1(query dto.SchoolMajorQuery) ([]dto.UserMajorDTO, int64, error) {
var items []dto.UserMajorDTO
var total int64

View File

@ -21,9 +21,9 @@ type YxCalculationMajorService struct {
historyScoreControlLineService *YxHistoryScoreControlLineService
}
func (s *YxCalculationMajorService) RecommendMajorList(schoolMajorQuery yxDto.SchoolMajorQuery) ([]yxDto.UserMajorDTO, int64, error) {
func (s *YxCalculationMajorService) RecommendMajorList(schoolMajorQuery yxDto.SchoolMajorQuery) ([]yxDto.UserMajorDTO, int64, dto.ProbabilityCountDTO, error) {
if schoolMajorQuery.UserScoreVO.ProfessionalCategory == "" {
return nil, 0, fmt.Errorf("专业类型错误")
return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("专业类型错误")
}
if schoolMajorQuery.Batch != "" {
schoolMajorQuery.Batch = strings.ReplaceAll(schoolMajorQuery.Batch, "批", "")
@ -33,18 +33,23 @@ func (s *YxCalculationMajorService) RecommendMajorList(schoolMajorQuery yxDto.Sc
// }
// }
calculationMajors, total, err := s.mapper.FindRecommendList(schoolMajorQuery)
calculationMajors, total, probCount, err := s.mapper.FindRecommendList(schoolMajorQuery)
if err != nil {
return nil, 0, err
return nil, 0, dto.ProbabilityCountDTO{}, err
}
// 为专业列表添加历史数据
startTime := time.Now()
err = s.UserMajorDTOGetHistory(&calculationMajors)
endTime := time.Now()
// 执行时长
queryCostTime := endTime.Sub(startTime)
fmt.Printf(" 历史数据查询耗时:%v\n", queryCostTime)
if err != nil {
return nil, 0, err
return nil, 0, dto.ProbabilityCountDTO{}, err
}
return calculationMajors, total, nil
return calculationMajors, total, probCount, nil
}
func (s *YxCalculationMajorService) BatchCreateBySchoolMajorDTO(tableName string, items []dto.SchoolMajorDTO, scoreID string) error {