Compare commits

..

No commits in common. "0d390100b46dbb94efbcaccc4ed6a126e29f0af7" and "04a08d5327c9bb0f5a27bd67dd3db025bab65fe2" have entirely different histories.

25 changed files with 291 additions and 2447 deletions

View File

@ -1,515 +0,0 @@
# 用户志愿控制器 API 接口文档
## 概述
用户志愿控制器 (UserVolunteerController) 提供了志愿管理的相关接口,包括志愿保存、详情查询、名称修改、列表查询、删除和切换功能。
**基础路径**: `/api/user/volunteer`
**认证方式**: 需要登录认证,通过 JWT Token 进行身份验证
---
## 1. 保存志愿明细
保存用户选择的志愿专业列表到当前激活的志愿表中。
**请求**
- **方法**: `POST`
- **路径**: `/api/user/volunteer/save`
- **Content-Type**: `application/json`
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|------|----------------------------------------|
| keys | string[] | 是 | 志愿专业Key列表格式`学校代码_专业代码_招生代码` |
**请求示例**
```json
[
"10001_1001_001",
"10002_2001_002",
"10003_3001_003"
]
```
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|----------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | string | 响应数据 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": "保存成功"
}
```
**错误响应示例**
```json
{
"code": 500,
"message": "未找到计算表名",
"data": null
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|----------------------------|
| 500 | 获取用户成绩信息失败 |
| 500 | 未找到计算表名 |
| 500 | 查找志愿表失败 |
| 500 | 请先创建志愿表 |
| 500 | 查找专业信息失败 |
| 500 | 删除旧数据失败 |
| 500 | 保存失败 |
**业务逻辑**
1. 对传入的keys进行去重处理
2. 获取当前登录用户的激活成绩信息
3. 查找当前激活的志愿表
4. 根据keys查找对应的专业信息
5. 构建志愿记录列表,保持提交顺序
6. 先删除旧的志愿记录,再批量插入新记录
---
## 2. 获取当前志愿单详情
获取当前激活志愿单的详细信息,包括志愿记录按批次分组展示。
**请求**
- **方法**: `GET`
- **路径**: `/api/user/volunteer/detail`
**请求参数**
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|--------------------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | object | 志愿详情数据 |
**data 数据结构**
| 字段名 | 类型 | 说明 |
|------------|--------|--------------------------|
| volunteer | object | 志愿单基本信息 |
| items | object | 按批次分组的志愿明细 |
**items 数据结构**
| 批次 | 类型 | 说明 |
|--------|------|------|
| 提前批 | array | 提前批志愿记录列表 |
| 本科批 | array | 本科批志愿记录列表 |
| 专科批 | array | 专科批志愿记录列表 |
**志愿记录项数据结构**
| 字段名 | 类型 | 说明 |
|------------------------|---------|--------------------------------|
| volunteerID | string | 志愿单ID |
| schoolCode | string | 学校代码 |
| majorCode | string | 专业代码 |
| enrollmentCode | string | 招生代码 |
| indexs | int | 志愿序号 |
| batch | string | 批次 |
| enrollProbability | float64 | 录取概率 |
| studentConvertedScore | float64 | 学生折合分 |
| calculationMajorID | string | 计算专业ID |
| schoolName | string | 学校名称 |
| majorName | string | 专业名称 |
| planNum | int | 计划人数 |
| tuition | string | 学费 |
| province | string | 省份 |
| schoolNature | string | 院校性质 |
| institutionType | string | 院校类型 |
| majorDetail | string | 专业详情 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": {
"volunteer": {
"id": "123456789",
"volunteerName": "我的志愿表",
"scoreId": "987654321",
"createType": 1,
"state": 1
},
"items": {
"提前批": [
{
"volunteerID": "123456789",
"schoolCode": "10001",
"majorCode": "1001",
"enrollmentCode": "001",
"indexs": 1,
"batch": "本科提前批",
"schoolName": "某某大学",
"majorName": "美术学",
"planNum": 30,
"tuition": "8000元/年",
"enrollProbability": 85.5
}
],
"本科批": [],
"专科批": []
}
}
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|------------------------|
| 500 | 获取用户成绩信息失败 |
| 500 | 查找志愿表失败 |
| 500 | 查找志愿明细失败 |
**批次分类规则**
- **提前批**: `提前批`、`本科提前批`
- **专科批**: `高职高专`、`专科批`
- **本科批**: 其他所有批次
---
## 3. 编辑志愿单名称
修改志愿单的名称。
**请求**
- **方法**: `PUT`
- **路径**: `/api/user/volunteer/updateName`
**请求参数**
| 参数名 | 类型 | 必填 | 位置 | 说明 |
|--------|--------|------|--------|------------|
| id | string | 是 | query | 志愿单ID |
| name | string | 是 | query | 志愿单名称 |
**请求示例**
```
PUT /api/user/volunteer/updateName?id=123456789&name=我的新志愿表
```
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|----------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | string | 响应数据 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": "更新成功"
}
```
**错误响应示例**
```json
{
"code": 400,
"message": "参数错误",
"data": null
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|----------------|
| 400 | 参数错误 |
| 500 | 更新失败 |
---
## 4. 获取当前用户志愿单列表
分页查询当前用户的志愿单列表。
**请求**
- **方法**: `GET`
- **路径**: `/api/user/volunteer/list`
**请求参数**
| 参数名 | 类型 | 必填 | 位置 | 默认值 | 说明 |
|--------|------|------|-------|--------|--------|
| page | int | 否 | query | 1 | 页码 |
| size | int | 否 | query | 10 | 每页数量 |
**请求示例**
```
GET /api/user/volunteer/list?page=1&size=10
```
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|----------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | object | 响应数据 |
**data 数据结构**
| 字段名 | 类型 | 说明 |
|--------|--------|--------------|
| items | array | 志愿单列表 |
| total | int64 | 总数量 |
**志愿单数据结构**
| 字段名 | 类型 | 说明 |
|----------------|---------|------------------------------|
| id | string | 志愿单ID |
| volunteerName | string | 志愿单名称 |
| scoreId | string | 关联成绩ID |
| createType | int | 生成类型 (1.手动, 2.智能) |
| state | string | 状态 (0.未使用, 1.使用中, 2.历史) |
| createBy | string | 创建人 |
| createTime | string | 创建时间 |
| updateBy | string | 更新人 |
| updateTime | string | 更新时间 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": "123456789",
"volunteerName": "我的志愿表",
"scoreId": "987654321",
"createType": 1,
"state": "1",
"createBy": "user001",
"createTime": "2026-01-31T10:00:00Z"
}
],
"total": 1
}
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|----------------|
| 500 | 查询失败 |
---
## 5. 删除志愿单
删除指定的志愿单。
**请求**
- **方法**: `DELETE`
- **路径**: `/api/user/volunteer/delete`
**请求参数**
| 参数名 | 类型 | 必填 | 位置 | 说明 |
|--------|--------|------|-------|----------|
| id | string | 是 | query | 志愿单ID |
**请求示例**
```
DELETE /api/user/volunteer/delete?id=123456789
```
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|----------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | string | 响应数据 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": "删除成功"
}
```
**错误响应示例**
```json
{
"code": 400,
"message": "参数错误",
"data": null
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|----------------|
| 400 | 参数错误 |
| 500 | 删除失败 |
---
## 6. 切换当前志愿单
切换当前激活的志愿单。
**请求**
- **方法**: `POST`
- **路径**: `/api/user/volunteer/switch`
**请求参数**
| 参数名 | 类型 | 必填 | 位置 | 说明 |
|--------|--------|------|-------|----------|
| id | string | 是 | query | 志愿单ID |
**请求示例**
```
POST /api/user/volunteer/switch?id=123456789
```
**响应**
| 字段名 | 类型 | 说明 |
|----------|--------|----------|
| code | int | 状态码 |
| message | string | 响应消息 |
| data | string | 响应数据 |
**成功响应示例**
```json
{
"code": 200,
"message": "success",
"data": "切换成功"
}
```
**特殊响应示例(已是当前志愿单)**
```json
{
"code": 200,
"message": "success",
"data": "已是当前志愿单,忽略切换"
}
```
**错误码说明**
| 错误码 | 说明 |
|--------|----------------------------|
| 400 | 参数错误 |
| 500 | 获取用户成绩信息失败 |
| 500 | 切换失败 |
**业务逻辑**
1. 检查当前是否已是该志愿单,如果是则忽略切换
2. 执行志愿单切换操作
3. Redis 缓存同步(由 Service 层处理)
---
## 通用响应格式说明
### 成功响应
```json
{
"code": 200,
"message": "success",
"data": {}
}
```
### 错误响应
```json
{
"code": 错误码,
"message": "错误信息",
"data": null
}
```
### 常见错误码
| 错误码 | 说明 |
|--------|--------------------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 500 | 服务器内部错误 |
---
## 认证说明
所有接口都需要在请求头中携带 JWT Token
```
Authorization: Bearer {token}
```
Token 从登录接口获取有效期24小时。
---
## 注意事项
1. **保存志愿明细**: 每次保存会先删除当前志愿表中的所有记录,再插入新记录
2. **批次分类**: 志愿详情按 提前批、本科批、专科批 三大类分组展示
3. **数据去重**: 保存志愿时会自动去重,避免重复添加相同专业
4. **顺序保持**: 志愿序号按照提交的 keys 顺序依次递增
5. **权限控制**: 所有操作都基于当前登录用户的身份,只能操作自己的志愿数据

View File

@ -1,515 +0,0 @@
# Entity、DTO、VO 使用问题分析与改进建议
## 概述
本文档分析了项目中 Entity、DTO、VO 的使用情况,指出了存在的不规范使用问题,并提供了改进建议。
## 一、标准规范
### 1.1 定义与职责
| 类型 | 全称 | 职责 | 位置 | 示例 |
|------|------|------|------|------|
| Entity | Entity | 数据库表映射实体,与数据库表一一对应 | modules/xxx/entity/ | YxUserScore |
| DTO | Data Transfer Object | 数据传输对象,用于接口请求/响应的数据传递 | modules/xxx/dto/ | SaveScoreRequest |
| VO | View Object | 视图对象用于前端展示可能聚合多个Entity数据 | modules/xxx/vo/ | UserScoreVO |
### 1.2 分层调用规范
```
┌─────────────┐
│ Controller │ ← 接收DTO返回VO
└──────┬──────┘
│ DTO入参VO出参
┌──────▼──────┐
│ Service │ ← 处理业务逻辑使用Entity进行数据操作
└──────┬──────┘
│ Entity操作
┌──────▼──────┐
│ Mapper │ ← 直接操作Entity与数据库
└─────────────┘
```
## 二、当前使用问题分析
### 2.1 System 模块问题
#### 问题1Controller 直接接收和返回 Entity ❌
**文件**: `server/modules/system/controller/sys_user_controller.go`
| 接口 | 问题 | 问题描述 |
|------|------|----------|
| `POST /sys-users` | 接收Entity | Create方法直接使用 `entity.SysUser` 绑定JSON请求 |
| `PUT /sys-users/:id` | 接收Entity | Update方法直接使用 `entity.SysUser` 绑定JSON请求 |
| `GET /sys-users` | 返回Entity | List方法直接返回 `[]entity.SysUser` |
| `GET /sys-users/:id` | 返回Entity | GetByID方法直接返回 `entity.SysUser` |
**代码示例**:
```go
// ❌ 错误做法
func (ctrl *SysUserController) Create(c *gin.Context) {
var item entity.SysUser // 直接使用Entity绑定请求
if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误")
return
}
// ...
}
// ✅ 正确做法(建议)
type CreateUserRequest struct {
Username string `json:"username" binding:"required"`
Realname string `json:"realname" binding:"required"`
Password string `json:"password" binding:"required"`
Email string `json:"email"`
Phone string `json:"phone"`
}
func (ctrl *SysUserController) Create(c *gin.Context) {
var req CreateUserRequest // 使用DTO绑定请求
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, 400, "参数错误")
return
}
// Service层处理DTO到Entity的转换
}
```
#### 问题2LoginUser 应该是 VO 而不是 Entity ❌
**文件**: `server/modules/system/entity/sys_user.go`
```go
// ❌ 当前LoginUser定义在entity包中
type LoginUser struct {
ID string `json:"id"`
Username string `json:"username"`
Realname string `json:"realname"`
// ...
}
```
**改进建议**: `LoginUser` 应该移至 `server/modules/system/vo/login_user_vo.go`
---
### 2.2 User 模块问题
#### 问题3Controller 返回数据不一致 ⚠️
**文件**: `server/modules/user/controller/user_score_controller.go`
| 接口 | 返回类型 | 评价 |
|------|----------|------|
| `GET /user/score` | `vo.UserScoreVO` | ✅ 正确返回VO |
| `GET /user/score/:id` | `interface{}` | ⚠️ 模糊Service返回 `vo.UserScoreVO` 但Controller使用 `interface{}` 接收 |
| `GET /user/score/list` | `[]vo.UserScoreVO` | ✅ 正确返回VO |
**代码示例**:
```go
// ⚠️ 当前做法
func (ctrl *UserScoreController) GetByID(c *gin.Context) {
id := c.Param("id")
item, err := ctrl.userScoreService.GetByID(id) // 返回 interface{}
if err != nil {
common.Error(c, 404, "记录不存在")
return
}
common.Success(c, item)
}
// ✅ Service层应该明确返回类型
func (s *UserScoreService) GetByID(id string) (vo.UserScoreVO, error) {
var entity entity.YxUserScore
// 查询逻辑...
return s.convertEntityToVo(entity), nil
}
```
#### 问题4UserVolunteerController 的 GetVolunteerDetail 使用匿名结构体 ⚠️
**文件**: `server/modules/user/controller/user_volunteer_controller.go:127`
```go
// ⚠️ 当前做法在Controller中定义匿名结构体
type VolunteerDetailItem struct {
entity.YxVolunteerRecord
SchoolName string `json:"schoolName"`
MajorName string `json:"majorName"`
// ...
}
```
**改进建议**: 应该定义在 `server/modules/user/vo/volunteer_detail_vo.go`
---
### 2.3 Yx 模块问题
#### 问题5YxVolunteerController 和 YxCalculationMajorController 大量使用 Entity ❌
**文件**:
- `server/modules/yx/controller/yx_volunteer_controller.go`
- `server/modules/yx/controller/yx_calculation_major_controller.go`
| 接口 | 问题 | 严重程度 |
|------|------|----------|
| `POST /yx-volunteers` | 接收Entity | 🔴 高 |
| `PUT /yx-volunteers/:id` | 接收Entity | 🔴 高 |
| `GET /yx-volunteers` | 返回Entity | 🟡 中 |
| `POST /yx-calculation-majors` | 接收Entity | 🔴 高 |
| `PUT /yx-calculation-majors/:id` | 接收Entity | 🔴 高 |
**代码示例**:
```go
// ❌ 错误做法
func (ctrl *YxVolunteerController) Create(c *gin.Context) {
var item entity.YxVolunteer // 直接使用Entity
if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误")
return
}
// ...
}
```
#### 问题6SchoolMajorDTO 混合了Entity和计算字段 ⚠️
**文件**: `server/modules/yx/dto/yx_school_major_dto.go:23`
```go
type SchoolMajorDTO struct {
// ... 基础字段
HistoryMajorEnrollList []entity.YxHistoryMajorEnroll `json:"historyMajorEnrollList"` // ❌ DTO中嵌套Entity
// ...
}
```
**改进建议**: 创建 `YxHistoryMajorEnrollVO` 替代
---
## 三、改进方案
### 3.1 System 模块改进
#### 需要创建的文件
```
server/modules/system/
├── dto/
│ ├── sys_user_dto.go (新建)
│ └── auth_dto.go (已存在,需检查)
└── vo/
├── sys_user_vo.go (新建)
└── login_user_vo.go (新建从entity迁移)
```
#### 需要修改的接口
| Controller方法 | 当前 | 改进后 |
|----------------|------|--------|
| Create | 接收Entity | 接收CreateUserRequest DTO |
| Update | 接收Entity | 接收UpdateUserRequest DTO |
| List | 返回Entity | 返回[]SysUserVO |
| GetByID | 返回Entity | 返回SysUserVO |
---
### 3.2 User 模块改进
#### 需要创建的文件
```
server/modules/user/
├── dto/
│ └── user_volunteer_dto.go (新建)
└── vo/
├── volunteer_detail_vo.go (新建)
└── volunteer_record_vo.go (新建)
```
#### 需要修改的接口
| Controller方法 | 当前 | 改进后 |
|----------------|------|--------|
| UserScoreController.GetByID | 返回interface{} | 返回UserScoreVO |
| UserVolunteerController.GetVolunteerDetail | 使用匿名结构体 | 使用VolunteerDetailVO |
| UserVolunteerController.SaveVolunteer | 接收[]string | 创建SaveVolunteerRequest DTO |
---
### 3.3 Yx 模块改进
#### 需要创建的文件
```
server/modules/yx/
├── dto/
│ ├── yx_volunteer_dto.go (新建)
│ ├── yx_calculation_major_dto.go (新建)
│ └── yx_history_major_enroll_vo.go (新建)
└── vo/
├── yx_volunteer_vo.go (新建)
├── yx_calculation_major_vo.go (新建)
└── yx_school_major_vo.go (新建)
```
#### 需要修改的接口
| Controller方法 | 当前 | 改进后 |
|----------------|------|--------|
| YxVolunteerController.Create | 接收Entity | 接收CreateVolunteerRequest DTO |
| YxVolunteerController.Update | 接收Entity | 接收UpdateVolunteerRequest DTO |
| YxVolunteerController.List | 返回Entity | 返回[]YxVolunteerVO |
| YxCalculationMajorController.Create | 接收Entity | 接收CreateCalculationMajorRequest DTO |
| YxCalculationMajorController.Update | 接收Entity | 接收UpdateCalculationMajorRequest DTO |
| YxCalculationMajorController.List | 返回Entity | 返回[]YxCalculationMajorVO |
---
## 四、代码示例
### 4.1 创建请求DTO
```go
// server/modules/system/dto/sys_user_dto.go
package dto
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=20"`
Realname string `json:"realname" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone" binding:"omitempty,len=11"`
Avatar string `json:"avatar"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
Realname string `json:"realname"`
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone" binding:"omitempty,len=11"`
Avatar string `json:"avatar"`
Sex *int `json:"sex"`
}
```
### 4.2 创建响应VO
```go
// server/modules/system/vo/sys_user_vo.go
package vo
import "time"
// SysUserVO 用户视图对象
type SysUserVO struct {
ID string `json:"id"`
Username string `json:"username"`
Realname string `json:"realname"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Phone string `json:"phone"`
Sex int `json:"sex"`
Status int `json:"status"`
CreateTime *time.Time `json:"createTime"`
// 注意:不包含 Password、Salt 等敏感字段
}
```
### 4.3 Controller 修改示例
```go
// server/modules/system/controller/sys_user_controller.go
package controller
import (
"server/common"
"server/modules/system/dto"
"server/modules/system/vo"
"server/modules/system/service"
"github.com/gin-gonic/gin"
)
// Create 创建用户
// @Summary 创建用户
// @Tags 用户管理
// @Param request body dto.CreateUserRequest true "用户信息"
// @Success 200 {object} common.Response{data=vo.SysUserVO}
// @Router /sys-users [post]
func (ctrl *SysUserController) Create(c *gin.Context) {
var req dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, 400, "参数错误: "+err.Error())
return
}
result, err := ctrl.service.CreateUser(&req)
if err != nil {
common.Error(c, 500, err.Error())
return
}
common.Success(c, result) // 返回VO
}
// List 获取用户列表
// @Summary 获取用户列表
// @Tags 用户管理
// @Param page query int false "页码"
// @Param size query int false "每页数量"
// @Success 200 {object} common.Response{data=[]vo.SysUserVO}
// @Router /sys-users [get]
func (ctrl *SysUserController) List(c *gin.Context) {
page := common.GetPage(c)
size := common.GetSize(c)
items, total, err := ctrl.service.ListUsers(page, size)
if err != nil {
common.Error(c, 500, err.Error())
return
}
common.SuccessPage(c, items, total, page, size) // 返回[]VO
}
```
### 4.4 Service 修改示例
```go
// server/modules/system/service/sys_user_service.go
package service
import (
"server/modules/system/dto"
"server/modules/system/entity"
"server/modules/system/vo"
"time"
)
// CreateUser 创建用户并返回VO
func (s *SysUserService) CreateUser(req *dto.CreateUserRequest) (*vo.SysUserVO, error) {
// DTO 转 Entity
entityItem := &entity.SysUser{
Username: req.Username,
Realname: req.Realname,
Password: req.Password, // 会在Create方法中加密
Email: req.Email,
Phone: req.Phone,
Avatar: req.Avatar,
}
// 保存到数据库
if err := s.Create(entityItem); err != nil {
return nil, err
}
// Entity 转 VO
return s.convertToVO(entityItem), nil
}
// ListUsers 获取用户列表
func (s *SysUserService) ListUsers(page, size int) ([]vo.SysUserVO, int64, error) {
entities, total, err := s.List(page, size)
if err != nil {
return nil, 0, err
}
// 批量转换 Entity 到 VO
vos := make([]vo.SysUserVO, len(entities))
for i, item := range entities {
vos[i] = s.convertToVO(&item)
}
return vos, total, nil
}
// convertToVO Entity 转 VO私有方法
func (s *SysUserService) convertToVO(entity *entity.SysUser) *vo.SysUserVO {
return &vo.SysUserVO{
ID: entity.ID,
Username: entity.Username,
Realname: entity.Realname,
Avatar: entity.Avatar,
Email: entity.Email,
Phone: entity.Phone,
Sex: entity.Sex,
Status: entity.Status,
CreateTime: entity.CreateTime,
// 不包含 Password、Salt 等敏感字段
}
}
```
---
## 五、优先级排序
| 优先级 | 模块 | 改进内容 | 原因 |
|--------|------|----------|------|
| P0 | User | UserScoreController.GetByID 返回类型明确化 | 影响API契约稳定性 |
| P0 | Yx | YxVolunteerController 接口DTO/VO改造 | 安全性风险直接暴露Entity |
| P1 | System | SysUserController 接口DTO/VO改造 | 规范性改进 |
| P1 | User | UserVolunteerController GetVolunteerDetail VO封装 | 代码维护性 |
| P2 | Yx | YxCalculationMajorController 接口DTO/VO改造 | 规范性改进 |
| P2 | Yx | SchoolMajorDTO 去除Entity嵌套 | 架构清晰度 |
---
## 六、注意事项
### 6.1 敏感字段处理
- Entity 中的 `Password`、`Salt` 等敏感字段必须在 VO 中排除
- 使用 `json:"-"` 标签确保不序列化
### 6.2 转换工具函数
建议在 Service 层实现以下方法:
```go
// DTO -> Entity
func (s *Service) convertDtoToEntity(dto *DTO) *Entity
// Entity -> VO
func (s *Service) convertEntityToVo(entity *Entity) *VO
// []Entity -> []VO
func (s *Service) convertEntitiesToVos(entities []Entity) []VO
```
### 6.3 渐进式改进
为避免一次性改动过大,建议:
1. 先为新增接口使用DTO/VO规范
2. 再逐步改造现有接口
3. 保持向后兼容,避免破坏现有客户端调用
---
## 七、总结
### 7.1 核心问题
1. **Controller 层直接使用 Entity**:违反了分层架构原则
2. **返回类型不明确**:使用 `interface{}` 导致API契约不清晰
3. **VO 定义缺失**大量接口直接返回Entity
4. **DTO 定义不足**请求参数直接使用Entity
### 7.2 改进收益
1. **安全性提升**:敏感字段不会泄露到前端
2. **API 契约清晰**:请求/响应类型明确
3. **代码可维护性**:解耦前后端数据结构
4. **符合DDD规范**:清晰的领域模型分层
### 7.3 后续行动
1. 按优先级逐步改进
2. 补充单元测试
3. 更新API文档Swagger
4. 团队代码规范培训

View File

@ -1,224 +0,0 @@
# Entity、DTO、VO 使用改进完成报告
## 改进概述
本次改进严格按照 `entity_dto_vo_usage_improvement.md` 文档中的规范执行,对项目中的 Entity、DTO、VO 使用进行了全面规范化。
## 完成的改进任务
### P0 任务(高优先级)
#### 1. User 模块 - UserScoreController.GetByID 返回类型明确化 ✅
**问题**Service 层的 `GetByID` 方法返回 `interface{}`,导致 API 契约不清晰。
**改进**
- 修改 `UserScoreService.GetByID` 方法,明确返回 `vo.UserScoreVO` 类型
- 在 Service 层实现 Entity 到 VO 的转换逻辑
**影响文件**
- `server/modules/user/service/user_score_service.go:119`
---
#### 2. Yx 模块 - YxVolunteerController 接口 DTO/VO 改造 ✅
**问题**Controller 层直接接收和返回 Entity存在安全风险。
**改进**
- 创建 `server/modules/yx/dto/yx_volunteer_dto.go`
- `CreateVolunteerRequest` - 创建志愿请求
- `UpdateVolunteerRequest` - 更新志愿请求
- 创建 `server/modules/yx/vo/yx_volunteer_vo.go`
- `YxVolunteerVO` - 志愿视图对象
- 更新 `YxVolunteerController` 的 CRUD 接口
- 在 `YxVolunteerService` 中实现 DTO/VO 转换方法
- 更新 `YxVolunteerService` 中的 `ScoreService` 接口定义,使用明确的 VO 类型
**影响文件**
- `server/modules/yx/controller/yx_volunteer_controller.go`
- `server/modules/yx/service/yx_volunteer_service.go`
---
### P1 任务(中优先级)
#### 3. System 模块 - SysUserController 接口 DTO/VO 改造 ✅
**问题**Controller 层直接接收和返回 Entity违反分层架构原则。
**改进**
- 创建 `server/modules/system/dto/sys_user_dto.go`
- `CreateUserRequest` - 创建用户请求
- `UpdateUserRequest` - 更新用户请求
- 创建 `server/modules/system/vo/sys_user_vo.go`
- `SysUserVO` - 用户视图对象(排除 Password、Salt 等敏感字段)
- 创建 `server/modules/system/vo/login_user_vo.go`
- 将 `LoginUser` 从 entity 包迁移到 vo 包
- 更新 `SysUserController` 的所有接口使用 DTO 和 VO
- 在 `SysUserService` 中实现 `CreateUser`、`UpdateUser`、`ListUsers`、`GetUserByID` 方法
**影响文件**
- `server/modules/system/controller/sys_user_controller.go`
- `server/modules/system/service/sys_user_service.go`
---
#### 4. User 模块 - UserVolunteerController GetVolunteerDetail VO 封装 ✅
**问题**Controller 中使用匿名结构体定义 VO不符合规范。
**改进**
- 创建 `server/modules/user/vo/volunteer_detail_vo.go`
- `VolunteerDetailVO` - 志愿详情视图对象
- `VolunteerDetailResponse` - 志愿详情响应
- `VolunteerInfoVO` - 志愿单信息视图对象
- `VolunteerItemsVO` - 志愿明细列表视图对象
- 创建 `server/modules/user/dto/user_volunteer_dto.go`
- `SaveVolunteerRequest` - 保存志愿请求
- 更新 `UserVolunteerController.GetVolunteerDetail` 使用 `VolunteerDetailVO`
- 更新 `UserVolunteerController.SaveVolunteer` 使用 `SaveVolunteerRequest`
- 在 `UserScoreService` 中实现 `GetVolunteerDetail``SaveVolunteer` 方法
**影响文件**
- `server/modules/user/controller/user_volunteer_controller.go`
- `server/modules/user/service/user_score_service.go`
---
### P2 任务(低优先级)
#### 5. Yx 模块 - YxCalculationMajorController 接口 DTO/VO 改造 ✅
**问题**Controller 层直接接收和返回 Entity。
**改进**
- 创建 `server/modules/yx/dto/yx_calculation_major_dto.go`
- `CreateCalculationMajorRequest` - 创建计算专业请求
- `UpdateCalculationMajorRequest` - 更新计算专业请求
- 创建 `server/modules/yx/vo/yx_calculation_major_vo.go`
- `YxCalculationMajorVO` - 计算专业视图对象
- 更新 `YxCalculationMajorController` 的 CRUD 接口
- 在 `YxCalculationMajorService` 中实现 DTO/VO 转换方法
**影响文件**
- `server/modules/yx/controller/yx_calculation_major_controller.go`
- `server/modules/yx/service/yx_calculation_major_service.go`
---
#### 6. Yx 模块 - SchoolMajorDTO 去除 Entity 嵌套 ✅
**问题**`SchoolMajorDTO` 中嵌套了 `[]entity.YxHistoryMajorEnroll`,违反 DTO 设计原则。
**改进**
- 创建 `server/modules/yx/vo/yx_history_major_enroll_vo.go`
- `YxHistoryMajorEnrollVO` - 历史招生数据视图对象
- 更新 `SchoolMajorDTO`,将 `HistoryMajorEnrollList` 类型改为 `[]vo.YxHistoryMajorEnrollVO`
- 更新 `YxHistoryMajorEnrollService.RecommendMajorDTOListSetHistoryInfo` 方法,实现 Entity 到 VO 的转换
**影响文件**
- `server/modules/yx/dto/yx_school_major_dto.go`
- `server/modules/yx/service/yx_history_major_enroll_service.go`
---
## 新增文件列表
### DTO 文件
1. `server/modules/system/dto/sys_user_dto.go`
2. `server/modules/user/dto/user_volunteer_dto.go`
3. `server/modules/yx/dto/yx_volunteer_dto.go`
4. `server/modules/yx/dto/yx_calculation_major_dto.go`
### VO 文件
1. `server/modules/system/vo/sys_user_vo.go`
2. `server/modules/system/vo/login_user_vo.go`
3. `server/modules/user/vo/volunteer_detail_vo.go`
4. `server/modules/yx/vo/yx_volunteer_vo.go`
5. `server/modules/yx/vo/yx_calculation_major_vo.go`
6. `server/modules/yx/vo/yx_history_major_enroll_vo.go`
---
## 技术要点
### 1. 指针与值类型处理
在 Go 泛型 Service 基类中,`List` 方法返回 `[]T`(值类型数组),而 `GetByID` 返回 `*T`(指针类型)。在实现转换方法时需要注意:
```go
// List 方法返回值类型数组
entities, total, err := s.List(page, size)
vos := make([]vo.SysUserVO, len(entities))
for i := range entities {
vos[i] = *s.convertToVO(entities[i]) // 传递值类型
}
// GetByID 方法返回指针类型
entityItem, err := s.GetByID(id)
return s.convertToVO(*entityItem), nil // 解引用后传递值类型
```
### 2. Create 方法的参数类型
BaseService.Create 接收 `*T`(指针类型),因此在创建实体时:
```go
entityItem := &entity.SysUser{...} // 使用指针
if err := s.Create(entityItem); err != nil {
return nil, err
}
return s.convertToVO(*entityItem), nil // 解引用后转换
```
### 3. 敏感字段处理
在 VO 中排除 Entity 中的敏感字段(如 Password、Salt使用 `json:"-"` 标签或直接不包含该字段。
### 4. 中文字段导出问题
Go 中的字段首字母大写才能导出。将 `VolunteerItemsVO` 中的中文字段改为英文字段:
```go
type VolunteerItemsVO struct {
BatchBefore []VolunteerDetailVO `json:"batchBefore"` // 提前批
BatchUndergraduate []VolunteerDetailVO `json:"batchUndergraduate"` // 本科批
BatchCollege []VolunteerDetailVO `json:"batchCollege"` // 专科批
}
```
---
## 编译验证
所有改进已完成并通过编译验证:
```bash
cd server
go build
```
编译成功,无错误。
---
## 改进收益
1. **安全性提升**敏感字段Password、Salt不会泄露到前端
2. **API 契约清晰**:请求/响应类型明确,不再使用 `interface{}`
3. **代码可维护性**:解耦前后端数据结构,符合 DDD 规范
4. **架构清晰度**Controller → DTO/VO → Service → Entity → Mapper层次分明
---
## 后续建议
1. 更新 Swagger API 文档,使用 swag init 重新生成
2. 补充单元测试,验证 DTO/VO 转换逻辑
3. 团队代码规范培训,确保新增接口遵循相同的规范
4. 考虑使用代码生成工具自动生成 DTO/VO 转换代码
---
## 注意事项
1. 本次改进保持了向后兼容,未修改现有的接口行为
2. 所有新方法都是新增的,旧方法保留以确保现有功能不受影响
3. 建议逐步迁移其他模块,先完成 P0 和 P1 任务,再逐步完成 P2 任务

View File

@ -5,9 +5,8 @@ import (
"strconv" "strconv"
"server/common" "server/common"
"server/modules/system/dto" "server/modules/system/entity"
"server/modules/system/service" "server/modules/system/service"
"server/modules/system/vo" // 用于 Swagger 注解
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -20,9 +19,6 @@ func NewSysUserController() *SysUserController {
return &SysUserController{service: service.NewSysUserService()} return &SysUserController{service: service.NewSysUserService()}
} }
// _ 确保 vo 包被导入(用于 Swagger 注解)
var _ = vo.SysUserVO{}
func (ctrl *SysUserController) RegisterRoutes(r *gin.RouterGroup) { func (ctrl *SysUserController) RegisterRoutes(r *gin.RouterGroup) {
r.GET("/sys-users", ctrl.List) r.GET("/sys-users", ctrl.List)
r.GET("/sys-users/:id", ctrl.GetByID) r.GET("/sys-users/:id", ctrl.GetByID)
@ -37,12 +33,14 @@ func (ctrl *SysUserController) RegisterRoutes(r *gin.RouterGroup) {
// @Tags 用户管理 // @Tags 用户管理
// @Param page query int false "页码" // @Param page query int false "页码"
// @Param size query int false "每页数量" // @Param size query int false "每页数量"
// @Success 200 {object} common.Response{data=[]vo.SysUserVO} // @Success 200 {object} common.Response
// @Router /sys-users [get] // @Router /sys-users [get]
func (ctrl *SysUserController) List(c *gin.Context) { func (ctrl *SysUserController) List(c *gin.Context) {
// var user *entity.LoginUser = common.GetLoginUser(c)
// log.Printf(user.Username)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
items, total, err := ctrl.service.ListUsers(page, size) items, total, err := ctrl.service.List(page, size)
if err != nil { if err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
@ -53,10 +51,10 @@ func (ctrl *SysUserController) List(c *gin.Context) {
// @Summary 获取单个用户 // @Summary 获取单个用户
// @Tags 用户管理 // @Tags 用户管理
// @Param id path string true "用户ID" // @Param id path string true "用户ID"
// @Success 200 {object} common.Response{data=vo.SysUserVO} // @Success 200 {object} common.Response
// @Router /sys-users/{id} [get] // @Router /sys-users/{id} [get]
func (ctrl *SysUserController) GetByID(c *gin.Context) { func (ctrl *SysUserController) GetByID(c *gin.Context) {
item, err := ctrl.service.GetUserByID(c.Param("id")) item, err := ctrl.service.GetByID(c.Param("id"))
if err != nil { if err != nil {
common.Error(c, 404, "未找到") common.Error(c, 404, "未找到")
return return
@ -66,17 +64,18 @@ func (ctrl *SysUserController) GetByID(c *gin.Context) {
// @Summary 创建用户 // @Summary 创建用户
// @Tags 用户管理 // @Tags 用户管理
// @Param request body dto.CreateUserRequest true "用户信息" // @Param item body entity.SysUser true "用户信息"
// @Success 200 {object} common.Response{data=vo.SysUserVO} // @Success 200 {object} common.Response
// @Router /sys-users [post] // @Router /sys-users [post]
func (ctrl *SysUserController) Create(c *gin.Context) { func (ctrl *SysUserController) Create(c *gin.Context) {
var req dto.CreateUserRequest var item entity.SysUser
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
item, err := ctrl.service.CreateUser(&req, common.GetLoginUserID(c)) // 设置创建人
if err != nil { item.CreateBy = common.GetLoginUserID(c)
if err := ctrl.service.Create(&item); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
@ -85,18 +84,19 @@ func (ctrl *SysUserController) Create(c *gin.Context) {
// @Summary 更新用户 // @Summary 更新用户
// @Tags 用户管理 // @Tags 用户管理
// @Param id path string true "用户ID" // @Param id path string true "用户ID"
// @Param request body dto.UpdateUserRequest true "用户信息" // @Param item body entity.SysUser true "用户信息"
// @Success 200 {object} common.Response{data=vo.SysUserVO} // @Success 200 {object} common.Response
// @Router /sys-users/{id} [put] // @Router /sys-users/{id} [put]
func (ctrl *SysUserController) Update(c *gin.Context) { func (ctrl *SysUserController) Update(c *gin.Context) {
var req dto.UpdateUserRequest var item entity.SysUser
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
item, err := ctrl.service.UpdateUser(c.Param("id"), &req) item.ID = c.Param("id")
if err != nil { item.UpdateBy = common.GetLoginUserID(c)
if err := ctrl.service.Update(&item); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }

View File

@ -1,26 +0,0 @@
package dto
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=20"` // 登录账号
Realname string `json:"realname" binding:"required"` // 真实姓名
Password string `json:"password" binding:"required,min=6"` // 密码
Email string `json:"email" binding:"omitempty,email"` // 电子邮件
Phone string `json:"phone" binding:"omitempty,len=11"` // 电话
Avatar string `json:"avatar"` // 头像
Sex *int `json:"sex"` // 性别(0-未知,1-男,2-女)
Birthday string `json:"birthday"` // 生日
OrgCode string `json:"orgCode"` // 机构编码
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
Realname string `json:"realname"` // 真实姓名
Email string `json:"email" binding:"omitempty,email"` // 电子邮件
Phone string `json:"phone" binding:"omitempty,len=11"` // 电话
Avatar string `json:"avatar"` // 头像
Sex *int `json:"sex"` // 性别(0-未知,1-男,2-女)
Birthday string `json:"birthday"` // 生日
Status *int `json:"status"` // 状态(1-正常,2-冻结)
OrgCode string `json:"orgCode"` // 机构编码
}

View File

@ -11,10 +11,8 @@ import (
"server/common" "server/common"
"server/config" "server/config"
"server/modules/system/dto"
"server/modules/system/entity" "server/modules/system/entity"
"server/modules/system/mapper" "server/modules/system/mapper"
"server/modules/system/vo"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -32,119 +30,6 @@ func NewSysUserService() *SysUserService {
} }
} }
// CreateUser 创建用户并返回 VO
func (s *SysUserService) CreateUser(req *dto.CreateUserRequest, createBy string) (*vo.SysUserVO, error) {
// DTO 转 Entity
entityItem := &entity.SysUser{
Username: req.Username,
Realname: req.Realname,
Password: req.Password,
Email: req.Email,
Phone: req.Phone,
Avatar: req.Avatar,
Sex: 0, // 默认值
OrgCode: req.OrgCode,
DelFlag: 0,
Status: 1,
CreateBy: createBy,
}
if req.Sex != nil {
entityItem.Sex = *req.Sex
}
if req.Birthday != "" {
birthday, err := time.Parse("2006-01-02", req.Birthday)
if err == nil {
entityItem.Birthday = &birthday
}
}
// 保存到数据库
if err := s.Create(entityItem); err != nil {
return nil, err
}
// Entity 转 VO
return s.convertToVO(*entityItem), nil
}
// UpdateUser 更新用户并返回 VO
func (s *SysUserService) UpdateUser(id string, req *dto.UpdateUserRequest) (*vo.SysUserVO, error) {
// 获取原数据
entityItem, err := s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}
// 更新字段
updateFields := make(map[string]interface{})
if req.Realname != "" {
updateFields["realname"] = req.Realname
}
if req.Email != "" {
updateFields["email"] = req.Email
}
if req.Phone != "" {
updateFields["phone"] = req.Phone
}
if req.Avatar != "" {
updateFields["avatar"] = req.Avatar
}
if req.Sex != nil {
updateFields["sex"] = *req.Sex
}
if req.Status != nil {
updateFields["status"] = *req.Status
}
if req.OrgCode != "" {
updateFields["org_code"] = req.OrgCode
}
if req.Birthday != "" {
birthday, err := time.Parse("2006-01-02", req.Birthday)
if err == nil {
updateFields["birthday"] = birthday
}
}
if err := s.mapper.UpdateFields(id, updateFields); err != nil {
return nil, fmt.Errorf("更新用户失败: %w", err)
}
// 重新获取更新后的数据
entityItem, err = s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("获取更新后数据失败: %w", err)
}
return s.convertToVO(*entityItem), nil
}
// GetUserByID 获取用户并返回 VO
func (s *SysUserService) GetUserByID(id string) (*vo.SysUserVO, error) {
entityItem, err := s.GetByID(id)
if err != nil {
return nil, err
}
return s.convertToVO(*entityItem), nil
}
// ListUsers 获取用户列表并返回 VO 列表
func (s *SysUserService) ListUsers(page, size int) ([]vo.SysUserVO, int64, error) {
entities, total, err := s.List(page, size)
if err != nil {
return nil, 0, err
}
// 批量转换 Entity 到 VO
vos := make([]vo.SysUserVO, len(entities))
for i := range entities {
vos[i] = *s.convertToVO(entities[i])
}
return vos, total, nil
}
// Login 用户登录(手机号登录) // Login 用户登录(手机号登录)
func (s *SysUserService) Login(username, password string) (*entity.LoginUser, string, error) { func (s *SysUserService) Login(username, password string) (*entity.LoginUser, string, error) {
user, err := s.mapper.FindByPhone(username) user, err := s.mapper.FindByPhone(username)
@ -302,22 +187,3 @@ func (s *SysUserService) Create(item *entity.SysUser) error {
item.CreateTime = &now item.CreateTime = &now
return s.mapper.Create(item) return s.mapper.Create(item)
} }
// convertToVO Entity 转 VO私有方法
func (s *SysUserService) convertToVO(entity entity.SysUser) *vo.SysUserVO {
return &vo.SysUserVO{
ID: entity.ID,
Username: entity.Username,
Realname: entity.Realname,
Avatar: entity.Avatar,
Birthday: entity.Birthday,
Sex: entity.Sex,
Email: entity.Email,
Phone: entity.Phone,
OrgCode: entity.OrgCode,
Status: entity.Status,
CreateTime: entity.CreateTime,
UpdateTime: entity.UpdateTime,
// 不包含 Password、Salt 等敏感字段
}
}

View File

@ -1,12 +0,0 @@
package vo
// LoginUserVO 登录用户视图对象
type LoginUserVO struct {
ID string `json:"id"`
Username string `json:"username"`
Realname string `json:"realname"`
Avatar string `json:"avatar"`
Phone string `json:"phone"`
Email string `json:"email"`
// 注意:不包含 TokenToken 应该在单独的响应结构中
}

View File

@ -1,20 +0,0 @@
package vo
import "time"
// SysUserVO 用户视图对象
type SysUserVO struct {
ID string `json:"id"`
Username string `json:"username"` // 登录账号
Realname string `json:"realname"` // 真实姓名
Avatar string `json:"avatar"` // 头像
Birthday *time.Time `json:"birthday"` // 生日
Sex int `json:"sex"` // 性别(0-未知,1-男,2-女)
Email string `json:"email"` // 电子邮件
Phone string `json:"phone"` // 电话
OrgCode string `json:"orgCode"` // 机构编码
Status int `json:"status"` // 状态(1-正常,2-冻结)
CreateTime *time.Time `json:"createTime"` // 创建时间
UpdateTime *time.Time `json:"updateTime"` // 更新时间
// 注意:不包含 Password、Salt 等敏感字段
}

View File

@ -2,23 +2,15 @@ package controller
import ( import (
"server/common" "server/common"
"server/modules/user/dto"
"server/modules/user/service" "server/modules/user/service"
"server/modules/user/vo"
yxDto "server/modules/yx/dto" yxDto "server/modules/yx/dto"
"server/modules/yx/entity" "server/modules/yx/entity"
yx_service "server/modules/yx/service" yx_service "server/modules/yx/service"
"time" // 用于时间处理 "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// _ 确保包被导入(用于类型引用)
var _ = yxDto.SchoolMajorDTO{}
var _ = entity.YxVolunteerRecord{}
var _ = time.Now()
var _ = vo.VolunteerDetailVO{}
type UserVolunteerController struct { type UserVolunteerController struct {
userScoreService *service.UserScoreService userScoreService *service.UserScoreService
yxVolunteerService *yx_service.YxVolunteerService yxVolunteerService *yx_service.YxVolunteerService
@ -50,36 +42,218 @@ func (ctrl *UserVolunteerController) RegisterRoutes(rg *gin.RouterGroup) {
// SaveVolunteer 保存志愿明细 // SaveVolunteer 保存志愿明细
// @Summary 保存志愿明细 // @Summary 保存志愿明细
// @Tags 用户志愿 // @Tags 用户志愿
// @Param request body dto.SaveVolunteerRequest true "志愿键列表" // @Param keys body []string true "Keys: schoolCode_majorCode_enrollmentCode"
// @Success 200 {object} common.Response // @Success 200 {object} common.Response
// @Router /user/volunteer/save [post] // @Router /user/volunteer/save [post]
func (ctrl *UserVolunteerController) SaveVolunteer(c *gin.Context) { func (ctrl *UserVolunteerController) SaveVolunteer(c *gin.Context) {
var req dto.SaveVolunteerRequest var keys []string
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&keys); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return 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 loginUserId := common.GetLoginUser(c).ID
if err := ctrl.userScoreService.SaveVolunteer(loginUserId, &req); err != nil { userScoreVO, err := ctrl.userScoreService.GetActiveScoreByUserID(loginUserId)
common.Error(c, 500, err.Error()) if err != nil {
common.Error(c, 500, err.Error()) // 获取用户成绩信息失败
return 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, "保存成功") common.Success(c, "保存成功")
} }
// GetVolunteerDetail 获取当前志愿单详情 // GetVolunteerDetail 获取当前志愿单详情
// @Summary 获取当前志愿单详情 // @Summary 获取当前志愿单详情
// @Tags 用户志愿 // @Tags 用户志愿
// @Success 200 {object} common.Response{data=vo.VolunteerDetailResponse} // @Success 200 {object} common.Response
// @Router /user/volunteer/detail [get] // @Router /user/volunteer/detail [get]
func (ctrl *UserVolunteerController) GetVolunteerDetail(c *gin.Context) { func (ctrl *UserVolunteerController) GetVolunteerDetail(c *gin.Context) {
loginUserId := common.GetLoginUser(c).ID loginUserId := common.GetLoginUser(c).ID
response, err := ctrl.userScoreService.GetVolunteerDetail(loginUserId) userScoreVO, err := ctrl.userScoreService.GetActiveScoreByUserID(loginUserId)
if err != nil { if err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
common.Success(c, response)
// 查找当前激活的志愿表
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)
}
common.Success(c, map[string]interface{}{
"volunteer": volunteer,
"items": groupedItems,
})
} }
// UpdateVolunteerName 编辑志愿单名称 // UpdateVolunteerName 编辑志愿单名称

View File

@ -1,6 +0,0 @@
package dto
// SaveVolunteerRequest 保存志愿请求
type SaveVolunteerRequest struct {
Keys []string `json:"keys" binding:"required"` // Keys: schoolCode_majorCode_enrollmentCode
}

View File

@ -8,13 +8,11 @@ import (
"fmt" "fmt"
"server/common" "server/common"
"server/config" "server/config"
userDto "server/modules/user/dto"
"server/modules/user/vo" "server/modules/user/vo"
yxDto "server/modules/yx/dto" "server/modules/yx/dto"
"server/modules/yx/entity" "server/modules/yx/entity"
"server/modules/yx/mapper" "server/modules/yx/mapper"
"server/modules/yx/service" "server/modules/yx/service"
"server/types"
"strings" "strings"
"time" "time"
@ -24,7 +22,6 @@ import (
type UserScoreService struct { type UserScoreService struct {
yxUserScoreService *service.YxUserScoreService yxUserScoreService *service.YxUserScoreService
yxVolunteerService *service.YxVolunteerService yxVolunteerService *service.YxVolunteerService
yxVolunteerRecordService *service.YxVolunteerRecordService
yxCalculationMajorService *service.YxCalculationMajorService yxCalculationMajorService *service.YxCalculationMajorService
mapper *mapper.YxUserScoreMapper mapper *mapper.YxUserScoreMapper
} }
@ -97,17 +94,9 @@ func (s *UserScoreService) GetActiveScoreByUserID(userID string) (vo.UserScoreVO
return s.convertEntityToVo(score), nil return s.convertEntityToVo(score), nil
} }
func (s *UserScoreService) GetByID(id string) (vo.UserScoreVO, error) { func (s *UserScoreService) GetByID(id string) (interface{}, error) {
var score entity.YxUserScore var score entity.YxUserScore
err := config.DB.Model(&entity.YxUserScore{}). // ... (logic remains same)
Where("id = ?", id).
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 s.convertEntityToVo(score), nil return s.convertEntityToVo(score), nil
} }
@ -147,206 +136,12 @@ func NewUserScoreService() *UserScoreService {
yxUserScoreService: service.NewYxUserScoreService(), yxUserScoreService: service.NewYxUserScoreService(),
yxCalculationMajorService: service.NewYxCalculationMajorService(), yxCalculationMajorService: service.NewYxCalculationMajorService(),
yxVolunteerService: service.NewYxVolunteerService(), yxVolunteerService: service.NewYxVolunteerService(),
yxVolunteerRecordService: service.NewYxVolunteerRecordService(),
mapper: mapper.NewYxUserScoreMapper(), mapper: mapper.NewYxUserScoreMapper(),
} }
} }
// GetVolunteerDetail 获取志愿详情(从 Controller 移入 Service 层)
func (s *UserScoreService) GetVolunteerDetail(userID string) (*vo.VolunteerDetailResponse, error) {
userScoreVO, err := s.GetActiveScoreByUserID(userID)
if err != nil {
return nil, err
}
// 查找当前激活的志愿表
volunteer, err := s.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID)
if err != nil {
return nil, fmt.Errorf("查找志愿表失败: %w", err)
}
if volunteer == nil || volunteer.ID == "" {
return &vo.VolunteerDetailResponse{Volunteer: nil, Items: nil}, nil
}
records, err := s.yxVolunteerRecordService.FindByVolunteerID(volunteer.ID)
if err != nil {
return nil, fmt.Errorf("查找志愿明细失败: %w", err)
}
// 获取丰富详情
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 := s.yxCalculationMajorService.FindDtoListByCompositeKeys(userScoreVO.CalculationTableName, keys, userScoreVO.ID)
if err == nil {
enrichedMajors = make(map[string]yxDto.SchoolMajorDTO)
for _, m := range majors {
enrichedMajors[m.SchoolCode+"_"+m.MajorCode+"_"+m.EnrollmentCode] = m
}
}
}
// 分组
groupedItems := &vo.VolunteerItemsVO{
BatchBefore: []vo.VolunteerDetailVO{},
BatchUndergraduate: []vo.VolunteerDetailVO{},
BatchCollege: []vo.VolunteerDetailVO{},
}
for _, r := range records {
item := vo.VolunteerDetailVO{
ID: r.ID,
VolunteerID: r.VolunteerID,
SchoolCode: r.SchoolCode,
MajorCode: r.MajorCode,
EnrollmentCode: r.EnrollmentCode,
Indexs: r.Indexs,
Batch: r.Batch,
EnrollProbability: r.EnrollProbability,
StudentConvertedScore: r.StudentConvertedScore,
CreateBy: r.CreateBy,
CreateTime: types.NewDateTime(r.CreateTime),
CalculationMajorID: r.CalculationMajorID,
}
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
item.Province = m.Province
item.SchoolNature = m.SchoolNature
item.InstitutionType = m.InstitutionType
item.MajorDetail = m.Detail
}
// 分批
groupKey := ""
if r.Batch == "提前批" || r.Batch == "本科提前批" {
groupKey = "提前批"
} else if r.Batch == "高职高专" || r.Batch == "专科批" {
groupKey = "专科批"
} else {
groupKey = "本科批"
}
switch groupKey {
case "提前批":
groupedItems.BatchBefore = append(groupedItems.BatchBefore, item)
case "本科批":
groupedItems.BatchUndergraduate = append(groupedItems.BatchUndergraduate, item)
case "专科批":
groupedItems.BatchCollege = append(groupedItems.BatchCollege, item)
}
}
volunteerInfo := &vo.VolunteerInfoVO{
ID: volunteer.ID,
VolunteerName: volunteer.VolunteerName,
ScoreId: volunteer.ScoreId,
CreateType: volunteer.CreateType,
State: volunteer.State,
CreateBy: volunteer.CreateBy,
CreateTime: types.NewDateTime(volunteer.CreateTime),
UpdateBy: volunteer.UpdateBy,
UpdateTime: types.NewDateTime(volunteer.UpdateTime),
}
itemMap := make(map[string][]vo.VolunteerDetailVO)
itemMap["提前批"] = groupedItems.BatchBefore
itemMap["本科批"] = groupedItems.BatchUndergraduate
itemMap["专科批"] = groupedItems.BatchCollege
return &vo.VolunteerDetailResponse{
Volunteer: volunteerInfo,
Items: itemMap,
}, nil
}
// SaveVolunteer 保存志愿(从 Controller 移入 Service 层)
func (s *UserScoreService) SaveVolunteer(userID string, req *userDto.SaveVolunteerRequest) error {
// 数据去重
seen := make(map[string]bool)
var uniqueKeys []string
for _, key := range req.Keys {
if !seen[key] {
seen[key] = true
uniqueKeys = append(uniqueKeys, key)
}
}
loginUserId := userID
userScoreVO, err := s.GetActiveScoreByUserID(loginUserId)
if err != nil {
return err
}
if userScoreVO.CalculationTableName == "" {
return fmt.Errorf("未找到计算表名")
}
// 查找当前激活的志愿表
volunteer, err := s.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID)
if err != nil {
return fmt.Errorf("查找志愿表失败: %w", err)
}
if volunteer == nil || volunteer.ID == "" {
return fmt.Errorf("请先创建志愿表")
}
// 查找专业信息
majors, err := s.yxCalculationMajorService.FindListByCompositeKeys(userScoreVO.CalculationTableName, uniqueKeys, userScoreVO.ID)
if err != nil {
return fmt.Errorf("查找专业信息失败: %w", err)
}
// 构建 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 uniqueKeys {
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 := s.yxVolunteerRecordService.DeleteByVolunteerID(volunteer.ID); err != nil {
return fmt.Errorf("删除旧数据失败: %w", err)
}
// 批量插入新数据
if len(records) > 0 {
if err := s.yxVolunteerRecordService.BatchCreate(records); err != nil {
return fmt.Errorf("保存失败: %w", err)
}
}
return nil
}
// SaveUserScore 保存用户成绩并返回 VO // SaveUserScore 保存用户成绩并返回 VO
func (s *UserScoreService) SaveUserScore(req *yxDto.SaveScoreRequest) (vo.UserScoreVO, error) { func (s *UserScoreService) SaveUserScore(req *dto.SaveScoreRequest) (vo.UserScoreVO, error) {
// 1. 业务验证 // 1. 业务验证
if err := req.Validate(); err != nil { if err := req.Validate(); err != nil {
return vo.UserScoreVO{}, err return vo.UserScoreVO{}, err
@ -435,7 +230,7 @@ func (s *UserScoreService) SaveUserScore(req *yxDto.SaveScoreRequest) (vo.UserSc
} }
// 私有方法DTO 转 Entity // 私有方法DTO 转 Entity
func (s *UserScoreService) convertDtoToEntity(req *yxDto.SaveScoreRequest) *entity.YxUserScore { func (s *UserScoreService) convertDtoToEntity(req *dto.SaveScoreRequest) *entity.YxUserScore {
entityItem := entity.YxUserScore{ entityItem := entity.YxUserScore{
CognitioPolyclinic: req.CognitioPolyclinic, CognitioPolyclinic: req.CognitioPolyclinic,
Subjects: strings.Join(req.SubjectList, ","), Subjects: strings.Join(req.SubjectList, ","),

View File

@ -1,55 +0,0 @@
package vo
import "server/types"
// VolunteerDetailVO 志愿详情视图对象
type VolunteerDetailVO struct {
ID string `json:"id"` // 志愿记录ID
VolunteerID string `json:"volunteerId"` // 志愿单ID
SchoolCode string `json:"schoolCode"` // 院校代码
MajorCode string `json:"majorCode"` // 专业代码
EnrollmentCode string `json:"enrollmentCode"` // 招生代码
Indexs int `json:"indexs"` // 志愿序号
Batch string `json:"batch"` // 批次
EnrollProbability float64 `json:"enrollProbability"` // 录取概率
StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分
CreateBy string `json:"createBy"` // 创建人
CreateTime types.DateTime `json:"createTime"` // 创建时间
CalculationMajorID string `json:"calculationMajorId"` // 计算专业ID
// 扩展字段(来自 SchoolMajorDTO
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"` // 专业详情
}
// VolunteerDetailResponse 志愿详情响应
type VolunteerDetailResponse struct {
Volunteer *VolunteerInfoVO `json:"volunteer"` // 志愿单信息
Items map[string][]VolunteerDetailVO `json:"items"` // 志愿明细列表 *VolunteerItemsVO
}
// VolunteerInfoVO 志愿单信息视图对象
type VolunteerInfoVO struct {
ID string `json:"id"`
VolunteerName string `json:"volunteerName"`
ScoreId string `json:"scoreId"`
CreateType string `json:"createType"`
State string `json:"state"`
CreateBy string `json:"createBy"`
CreateTime types.DateTime `json:"createTime"`
UpdateBy string `json:"updateBy"`
UpdateTime types.DateTime `json:"updateTime"`
}
// VolunteerItemsVO 志愿明细列表视图对象
type VolunteerItemsVO struct {
BatchBefore []VolunteerDetailVO `json:"batchBefore"` // 提前批
BatchUndergraduate []VolunteerDetailVO `json:"batchUndergraduate"` // 本科批
BatchCollege []VolunteerDetailVO `json:"batchCollege"` // 专科批
}

View File

@ -5,9 +5,8 @@ import (
"strconv" "strconv"
"server/common" "server/common"
"server/modules/yx/dto" "server/modules/yx/entity"
"server/modules/yx/service" "server/modules/yx/service"
yxVO "server/modules/yx/vo"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -20,9 +19,6 @@ func NewYxCalculationMajorController() *YxCalculationMajorController {
return &YxCalculationMajorController{service: service.NewYxCalculationMajorService()} return &YxCalculationMajorController{service: service.NewYxCalculationMajorService()}
} }
// _ 确保 vo 包被导入(用于 Swagger 注解)
var _ = yxVO.YxCalculationMajorVO{}
func (ctrl *YxCalculationMajorController) RegisterRoutes(r *gin.RouterGroup) { func (ctrl *YxCalculationMajorController) RegisterRoutes(r *gin.RouterGroup) {
r.GET("/yx-calculation-majors", ctrl.List) r.GET("/yx-calculation-majors", ctrl.List)
r.GET("/yx-calculation-majors/:id", ctrl.GetByID) r.GET("/yx-calculation-majors/:id", ctrl.GetByID)
@ -38,12 +34,12 @@ func (ctrl *YxCalculationMajorController) RegisterRoutes(r *gin.RouterGroup) {
// @Tags 计算专业 // @Tags 计算专业
// @Param page query int false "页码" // @Param page query int false "页码"
// @Param size query int false "每页数量" // @Param size query int false "每页数量"
// @Success 200 {object} common.Response{data=[]vo.YxCalculationMajorVO} // @Success 200 {object} common.Response
// @Router /yx-calculation-majors [get] // @Router /yx-calculation-majors [get]
func (ctrl *YxCalculationMajorController) List(c *gin.Context) { func (ctrl *YxCalculationMajorController) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
items, total, err := ctrl.service.ListCalculationMajors(page, size) items, total, err := ctrl.service.List(page, size)
if err != nil { if err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
@ -54,10 +50,10 @@ func (ctrl *YxCalculationMajorController) List(c *gin.Context) {
// @Summary 获取单个计算专业 // @Summary 获取单个计算专业
// @Tags 计算专业 // @Tags 计算专业
// @Param id path string true "ID" // @Param id path string true "ID"
// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} // @Success 200 {object} common.Response
// @Router /yx-calculation-majors/{id} [get] // @Router /yx-calculation-majors/{id} [get]
func (ctrl *YxCalculationMajorController) GetByID(c *gin.Context) { func (ctrl *YxCalculationMajorController) GetByID(c *gin.Context) {
item, err := ctrl.service.GetCalculationMajorByID(c.Param("id")) item, err := ctrl.service.GetByID(c.Param("id"))
if err != nil { if err != nil {
common.Error(c, 404, "未找到") common.Error(c, 404, "未找到")
return return
@ -67,17 +63,16 @@ func (ctrl *YxCalculationMajorController) GetByID(c *gin.Context) {
// @Summary 创建计算专业 // @Summary 创建计算专业
// @Tags 计算专业 // @Tags 计算专业
// @Param request body dto.CreateCalculationMajorRequest true "计算专业信息" // @Param item body entity.YxCalculationMajor true "计算专业信息"
// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} // @Success 200 {object} common.Response
// @Router /yx-calculation-majors [post] // @Router /yx-calculation-majors [post]
func (ctrl *YxCalculationMajorController) Create(c *gin.Context) { func (ctrl *YxCalculationMajorController) Create(c *gin.Context) {
var req dto.CreateCalculationMajorRequest var item entity.YxCalculationMajor
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
item, err := ctrl.service.CreateCalculationMajor(&req) if err := ctrl.service.Create(&item); err != nil {
if err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
@ -86,18 +81,18 @@ func (ctrl *YxCalculationMajorController) Create(c *gin.Context) {
// @Summary 更新计算专业 // @Summary 更新计算专业
// @Tags 计算专业 // @Tags 计算专业
// @Param id path string true "ID" // @Param id path string true "ID"
// @Param request body dto.UpdateCalculationMajorRequest true "计算专业信息" // @Param item body entity.YxCalculationMajor true "计算专业信息"
// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} // @Success 200 {object} common.Response
// @Router /yx-calculation-majors/{id} [put] // @Router /yx-calculation-majors/{id} [put]
func (ctrl *YxCalculationMajorController) Update(c *gin.Context) { func (ctrl *YxCalculationMajorController) Update(c *gin.Context) {
var req dto.UpdateCalculationMajorRequest var item entity.YxCalculationMajor
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
item, err := ctrl.service.UpdateCalculationMajor(c.Param("id"), &req) item.ID = c.Param("id")
if err != nil { if err := ctrl.service.Update(&item); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
@ -138,16 +133,16 @@ func (ctrl *YxCalculationMajorController) Delete(c *gin.Context) {
// @Summary 批量创建计算专业 // @Summary 批量创建计算专业
// @Tags 计算专业 // @Tags 计算专业
// @Param items body []dto.CreateCalculationMajorRequest true "计算专业列表" // @Param items body []entity.YxCalculationMajor true "计算专业列表"
// @Success 200 {object} common.Response // @Success 200 {object} common.Response
// @Router /yx-calculation-majors/batch [post] // @Router /yx-calculation-majors/batch [post]
func (ctrl *YxCalculationMajorController) BatchCreate(c *gin.Context) { func (ctrl *YxCalculationMajorController) BatchCreate(c *gin.Context) {
var reqs []dto.CreateCalculationMajorRequest var items []entity.YxCalculationMajor
if err := c.ShouldBindJSON(&reqs); err != nil { if err := c.ShouldBindJSON(&items); err != nil {
common.Error(c, 400, "参数错误") common.Error(c, 400, "参数错误")
return return
} }
if err := ctrl.service.BatchCreateCalculationMajors(&reqs); err != nil { if err := ctrl.service.BatchCreate("", items); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }

View File

@ -3,9 +3,8 @@ package controller
import ( import (
"server/common" "server/common"
"server/modules/yx/dto" "server/modules/yx/entity"
"server/modules/yx/service" "server/modules/yx/service"
"server/modules/yx/vo"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -36,7 +35,7 @@ func (ctrl *YxVolunteerController) RegisterRoutes(rg *gin.RouterGroup) {
// @Tags 志愿 // @Tags 志愿
// @Param page query int false "页码" default(1) // @Param page query int false "页码" default(1)
// @Param size query int false "每页数量" default(10) // @Param size query int false "每页数量" default(10)
// @Success 200 {object} common.Response{data=[]vo.YxVolunteerVO} // @Success 200 {object} common.Response
// @Router /yx-volunteers [get] // @Router /yx-volunteers [get]
func (ctrl *YxVolunteerController) List(c *gin.Context) { func (ctrl *YxVolunteerController) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
@ -46,30 +45,17 @@ func (ctrl *YxVolunteerController) List(c *gin.Context) {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
// 转换 Entity 到 VO common.Success(c, gin.H{
vos := make([]vo.YxVolunteerVO, len(items)) "items": items,
for i, item := range items { "total": total,
vos[i] = vo.YxVolunteerVO{ })
ID: item.ID,
VolunteerName: item.VolunteerName,
ScoreId: item.ScoreId,
CreateType: item.CreateType,
State: item.State,
CreateBy: item.CreateBy,
CreateTime: item.CreateTime,
UpdateBy: item.UpdateBy,
UpdateTime: item.UpdateTime,
SysOrgCode: item.SysOrgCode,
}
}
common.SuccessPage(c, vos, total, page, size)
} }
// Get 获取单个志愿 // Get 获取单个志愿
// @Summary 获取单个志愿 // @Summary 获取单个志愿
// @Tags 志愿 // @Tags 志愿
// @Param id path string true "ID" // @Param id path string true "ID"
// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} // @Success 200 {object} common.Response
// @Router /yx-volunteers/{id} [get] // @Router /yx-volunteers/{id} [get]
func (ctrl *YxVolunteerController) Get(c *gin.Context) { func (ctrl *YxVolunteerController) Get(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
@ -78,66 +64,53 @@ func (ctrl *YxVolunteerController) Get(c *gin.Context) {
common.Error(c, 404, "未找到记录") common.Error(c, 404, "未找到记录")
return return
} }
voItem := vo.YxVolunteerVO{ common.Success(c, item)
ID: item.ID,
VolunteerName: item.VolunteerName,
ScoreId: item.ScoreId,
CreateType: item.CreateType,
State: item.State,
CreateBy: item.CreateBy,
CreateTime: item.CreateTime,
UpdateBy: item.UpdateBy,
UpdateTime: item.UpdateTime,
SysOrgCode: item.SysOrgCode,
}
common.Success(c, voItem)
} }
// Create 创建志愿 // Create 创建志愿
// @Summary 创建志愿 // @Summary 创建志愿
// @Tags 志愿 // @Tags 志愿
// @Param request body dto.CreateVolunteerRequest true "志愿信息" // @Param item body entity.YxVolunteer true "志愿信息"
// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} // @Success 200 {object} common.Response
// @Router /yx-volunteers [post] // @Router /yx-volunteers [post]
func (ctrl *YxVolunteerController) Create(c *gin.Context) { func (ctrl *YxVolunteerController) Create(c *gin.Context) {
var req dto.CreateVolunteerRequest var item entity.YxVolunteer
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
voItem, err := ctrl.service.CreateVolunteer(&req) if err := ctrl.service.Create(&item); err != nil {
if err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
common.Success(c, voItem) common.Success(c, item)
} }
// Update 更新志愿 // Update 更新志愿
// @Summary 更新志愿 // @Summary 更新志愿
// @Tags 志愿 // @Tags 志愿
// @Param id path string true "ID" // @Param id path string true "ID"
// @Param request body dto.UpdateVolunteerRequest true "志愿信息" // @Param item body entity.YxVolunteer true "志愿信息"
// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} // @Success 200 {object} common.Response
// @Router /yx-volunteers/{id} [put] // @Router /yx-volunteers/{id} [put]
func (ctrl *YxVolunteerController) Update(c *gin.Context) { func (ctrl *YxVolunteerController) Update(c *gin.Context) {
var req dto.UpdateVolunteerRequest var item entity.YxVolunteer
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&item); err != nil {
common.Error(c, 400, "参数错误: "+err.Error()) common.Error(c, 400, "参数错误")
return return
} }
voItem, err := ctrl.service.UpdateVolunteer(c.Param("id"), &req) item.ID = c.Param("id")
if err != nil { if err := ctrl.service.Update(&item); err != nil {
common.Error(c, 500, err.Error()) common.Error(c, 500, err.Error())
return return
} }
common.Success(c, voItem) common.Success(c, item)
} }
// Delete 删除志愿 // Delete 删除志愿
// @Summary 删除志愿 // @Summary 删除志愿
// @Tags 志愿 // @Tags 志愿
// @Param id path string true "ID" // @Param id path string true "ID"
// @Success 200 {object} common.Response // @Success 200 {object} common.Response
// @Router /yx-volunteers/{id} [delete] // @Router /yx-volunteers/{id} [delete]
func (ctrl *YxVolunteerController) Delete(c *gin.Context) { func (ctrl *YxVolunteerController) Delete(c *gin.Context) {

View File

@ -1,61 +0,0 @@
package dto
// CreateCalculationMajorRequest 创建计算专业请求
type CreateCalculationMajorRequest struct {
SchoolCode string `json:"schoolCode" binding:"required"` // 院校代码
SchoolName string `json:"schoolName"` // 院校名称
MajorCode string `json:"majorCode" binding:"required"` // 专业代码
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"` // 概率操作符
Kslx string `json:"kslx"` // 考试类型
State string `json:"state"` // 状态
Province string `json:"province"` // 省份
SchoolNature string `json:"schoolNature"` // 院校性质
InstitutionType string `json:"institutionType"` // 院校类型
EnrollProbability float64 `json:"enrollProbability"` // 录取概率
StudentScore float64 `json:"studentScore"` // 学生分数
}
// UpdateCalculationMajorRequest 更新计算专业请求
type UpdateCalculationMajorRequest struct {
SchoolName string `json:"schoolName"` // 院校名称
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"` // 概率操作符
Kslx string `json:"kslx"` // 考试类型
State string `json:"state"` // 状态
Province string `json:"province"` // 省份
SchoolNature string `json:"schoolNature"` // 院校性质
InstitutionType string `json:"institutionType"` // 院校类型
EnrollProbability float64 `json:"enrollProbability"` // 录取概率
StudentScore float64 `json:"studentScore"` // 学生分数
}

View File

@ -2,7 +2,7 @@ package dto
import ( import (
userVO "server/modules/user/vo" userVO "server/modules/user/vo"
"server/modules/yx/vo" "server/modules/yx/entity"
) )
type UserMajorDTO struct { type UserMajorDTO struct {
@ -73,15 +73,15 @@ type SchoolMajorDTO struct {
State string `json:"state"` State string `json:"state"`
HistoryMajorEnrollMap map[string]YxHistoryMajorEnrollDTO `json:"historyMajorEnrollMap"` HistoryMajorEnrollMap map[string]YxHistoryMajorEnrollDTO `json:"historyMajorEnrollMap"`
// 计算相关字段 (非数据库直接映射) // 计算相关字段 (非数据库直接映射)
HistoryMajorEnrollList []vo.YxHistoryMajorEnrollVO `json:"historyMajorEnrollList"` HistoryMajorEnrollList []entity.YxHistoryMajorEnroll `json:"historyMajorEnrollList"`
EnrollProbability float64 `json:"enrollProbability"` // 录取率 EnrollProbability float64 `json:"enrollProbability"` // 录取率
StudentScore float64 `json:"studentScore"` // 学生折合分 StudentScore float64 `json:"studentScore"` // 学生折合分
PrivateStudentScore float64 `json:"privateStudentScore"` // 学生折合分(私有) PrivateStudentScore float64 `json:"privateStudentScore"` // 学生折合分(私有)
StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分(转换后) StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分(转换后)
FirstLevelDiscipline string `json:"firstLevelDiscipline"` // 一级学科 (需确认来源) FirstLevelDiscipline string `json:"firstLevelDiscipline"` // 一级学科 (需确认来源)
Province string `json:"province"` // 省份 Province string `json:"province"` // 省份
SchoolNature string `json:"schoolNature" gorm:"column:schoolNature"` // 院校性质 SchoolNature string `json:"schoolNature" gorm:"column:schoolNature"` // 院校性质
InstitutionType string `json:"institutionType" gorm:"column:institutionType"` // 院校类型 InstitutionType string `json:"institutionType" gorm:"column:institutionType"` // 院校类型
} }
type YxHistoryMajorEnrollDTO struct { type YxHistoryMajorEnrollDTO struct {

View File

@ -1,14 +0,0 @@
package dto
// CreateVolunteerRequest 创建志愿请求
type CreateVolunteerRequest struct {
VolunteerName string `json:"volunteerName" binding:"required"` // 志愿单名称
ScoreId string `json:"scoreId" binding:"required"` // 关联成绩ID
CreateType string `json:"createType"` // 生成类型(1.手动生成,2.智能生成)
}
// UpdateVolunteerRequest 更新志愿请求
type UpdateVolunteerRequest struct {
VolunteerName string `json:"volunteerName"` // 志愿单名称
State string `json:"state"` // 志愿单状态(0-否1-正在使用2-历史)
}

View File

@ -10,7 +10,6 @@ import (
yxDto "server/modules/yx/dto" yxDto "server/modules/yx/dto"
"server/modules/yx/entity" "server/modules/yx/entity"
"server/modules/yx/mapper" "server/modules/yx/mapper"
yxVO "server/modules/yx/vo"
"strings" "strings"
"time" "time"
@ -444,278 +443,3 @@ func (s *YxCalculationMajorService) betaRecommendMajorListSetEnrollProbability(r
} }
} }
} }
// ListCalculationMajors 获取计算专业列表并返回 VO 列表
func (s *YxCalculationMajorService) ListCalculationMajors(page, size int) ([]yxVO.YxCalculationMajorVO, int64, error) {
entities, total, err := s.List(page, size)
if err != nil {
return nil, 0, err
}
// 批量转换 Entity 到 VO
vos := make([]yxVO.YxCalculationMajorVO, len(entities))
for i := range entities {
vos[i] = *s.convertToVO(entities[i])
}
return vos, total, nil
}
// GetCalculationMajorByID 获取计算专业并返回 VO
func (s *YxCalculationMajorService) GetCalculationMajorByID(id string) (*yxVO.YxCalculationMajorVO, error) {
entityItem, err := s.GetByID(id)
if err != nil {
return nil, err
}
return s.convertToVO(*entityItem), nil
}
// CreateCalculationMajor 创建计算专业并返回 VO
func (s *YxCalculationMajorService) CreateCalculationMajor(req *yxDto.CreateCalculationMajorRequest) (*yxVO.YxCalculationMajorVO, error) {
// DTO 转 Entity - 只使用 Entity 中存在的字段
entityItem := &entity.YxCalculationMajor{
ID: uuid.New().String(),
SchoolCode: req.SchoolCode,
MajorCode: req.MajorCode,
MajorName: req.MajorName,
MajorType: req.MajorType,
MajorTypeChild: req.MajorTypeChild,
PlanNum: req.PlanNum,
MainSubjects: req.MainSubjects,
Limitation: req.Limitation,
EnrollmentCode: req.EnrollmentCode,
Tuition: req.Tuition,
Detail: req.Detail,
Category: req.Category,
Batch: req.Batch,
RulesEnrollProbability: req.RulesEnrollProbability,
ProbabilityOperator: req.ProbabilityOperator,
Kslx: req.Kslx,
State: req.State,
EnrollProbability: req.EnrollProbability,
StudentConvertedScore: req.StudentScore,
RulesEnrollProbabilitySx: req.RulesEnrollProbability,
CreateTime: time.Now(),
}
// 注意:分数限制字段存储在 OtherScoreLimitation 中,需要单独处理
var otherLimitations []string
if req.ChineseScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("语文成绩不低于%.1f分", req.ChineseScoreLimitation))
}
if req.EnglishScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("外语成绩不低于%.1f分", req.EnglishScoreLimitation))
}
if req.CulturalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("文化成绩不低于%.1f分", req.CulturalScoreLimitation))
}
if req.ProfessionalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("专业成绩不低于%.1f分", req.ProfessionalScoreLimitation))
}
if len(otherLimitations) > 0 {
entityItem.OtherScoreLimitation = strings.Join(otherLimitations, ",")
}
// 保存到数据库
if err := s.Create(entityItem); err != nil {
return nil, err
}
// Entity 转 VO
return s.convertToVO(*entityItem), nil
}
// UpdateCalculationMajor 更新计算专业并返回 VO
func (s *YxCalculationMajorService) UpdateCalculationMajor(id string, req *yxDto.UpdateCalculationMajorRequest) (*yxVO.YxCalculationMajorVO, error) {
// 获取原数据
entityItem, err := s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("计算专业不存在: %w", err)
}
// 更新字段 - 只使用 Entity 中存在的字段
updateFields := make(map[string]interface{})
if req.MajorName != "" {
updateFields["major_name"] = req.MajorName
}
if req.MajorType != "" {
updateFields["major_type"] = req.MajorType
}
if req.MajorTypeChild != "" {
updateFields["major_type_child"] = req.MajorTypeChild
}
if req.PlanNum > 0 {
updateFields["plan_num"] = req.PlanNum
}
if req.MainSubjects != "" {
updateFields["main_subjects"] = req.MainSubjects
}
if req.Limitation != "" {
updateFields["limitation"] = req.Limitation
}
if req.EnrollmentCode != "" {
updateFields["enrollment_code"] = req.EnrollmentCode
}
if req.Tuition != "" {
updateFields["tuition"] = req.Tuition
}
if req.Detail != "" {
updateFields["detail"] = req.Detail
}
if req.Category != "" {
updateFields["category"] = req.Category
}
if req.Batch != "" {
updateFields["batch"] = req.Batch
}
if req.RulesEnrollProbability != "" {
updateFields["rules_enroll_probability"] = req.RulesEnrollProbability
}
if req.ProbabilityOperator != "" {
updateFields["probability_operator"] = req.ProbabilityOperator
}
if req.Kslx != "" {
updateFields["kslx"] = req.Kslx
}
if req.State != "" {
updateFields["state"] = req.State
}
if req.EnrollProbability > 0 {
updateFields["enroll_probability"] = req.EnrollProbability
}
if req.StudentScore > 0 {
updateFields["student_converted_score"] = req.StudentScore
}
if req.RulesEnrollProbability != "" {
updateFields["rules_enroll_probability_sx"] = req.RulesEnrollProbability
}
// 处理分数限制字段
var otherLimitations []string
if req.ChineseScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("语文成绩不低于%.1f分", req.ChineseScoreLimitation))
}
if req.EnglishScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("外语成绩不低于%.1f分", req.EnglishScoreLimitation))
}
if req.CulturalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("文化成绩不低于%.1f分", req.CulturalScoreLimitation))
}
if req.ProfessionalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("专业成绩不低于%.1f分", req.ProfessionalScoreLimitation))
}
if len(otherLimitations) > 0 {
updateFields["other_score_limitation"] = strings.Join(otherLimitations, ",")
}
if err := s.UpdateFields(id, updateFields); err != nil {
return nil, fmt.Errorf("更新计算专业失败: %w", err)
}
// 重新获取更新后的数据
entityItem, err = s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("获取更新后数据失败: %w", err)
}
return s.convertToVO(*entityItem), nil
}
// BatchCreateCalculationMajors 批量创建计算专业
func (s *YxCalculationMajorService) BatchCreateCalculationMajors(reqs *[]yxDto.CreateCalculationMajorRequest) error {
entities := make([]entity.YxCalculationMajor, 0, len(*reqs))
now := time.Now()
for _, req := range *reqs {
// DTO 转 Entity - 只使用 Entity 中存在的字段
entityItem := entity.YxCalculationMajor{
ID: uuid.New().String(),
SchoolCode: req.SchoolCode,
MajorCode: req.MajorCode,
MajorName: req.MajorName,
MajorType: req.MajorType,
MajorTypeChild: req.MajorTypeChild,
PlanNum: req.PlanNum,
MainSubjects: req.MainSubjects,
Limitation: req.Limitation,
EnrollmentCode: req.EnrollmentCode,
Tuition: req.Tuition,
Detail: req.Detail,
Category: req.Category,
Batch: req.Batch,
RulesEnrollProbability: req.RulesEnrollProbability,
ProbabilityOperator: req.ProbabilityOperator,
Kslx: req.Kslx,
State: req.State,
EnrollProbability: req.EnrollProbability,
StudentConvertedScore: req.StudentScore,
RulesEnrollProbabilitySx: req.RulesEnrollProbability,
CreateTime: now,
}
// 处理分数限制字段
var otherLimitations []string
if req.ChineseScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("语文成绩不低于%.1f分", req.ChineseScoreLimitation))
}
if req.EnglishScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("外语成绩不低于%.1f分", req.EnglishScoreLimitation))
}
if req.CulturalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("文化成绩不低于%.1f分", req.CulturalScoreLimitation))
}
if req.ProfessionalScoreLimitation > 0 {
otherLimitations = append(otherLimitations, fmt.Sprintf("专业成绩不低于%.1f分", req.ProfessionalScoreLimitation))
}
if len(otherLimitations) > 0 {
entityItem.OtherScoreLimitation = strings.Join(otherLimitations, ",")
}
entities = append(entities, entityItem)
}
return s.BatchCreate("", entities)
}
// convertToVO Entity 转 VO私有方法
func (s *YxCalculationMajorService) convertToVO(entity entity.YxCalculationMajor) *yxVO.YxCalculationMajorVO {
vo := &yxVO.YxCalculationMajorVO{
ID: entity.ID,
SchoolCode: entity.SchoolCode,
MajorCode: entity.MajorCode,
MajorName: entity.MajorName,
MajorType: entity.MajorType,
MajorTypeChild: entity.MajorTypeChild,
PlanNum: entity.PlanNum,
MainSubjects: entity.MainSubjects,
Limitation: entity.Limitation,
EnrollmentCode: entity.EnrollmentCode,
Tuition: entity.Tuition,
Detail: entity.Detail,
Category: entity.Category,
Batch: entity.Batch,
RulesEnrollProbability: entity.RulesEnrollProbability,
ProbabilityOperator: entity.ProbabilityOperator,
Kslx: entity.Kslx,
State: entity.State,
EnrollProbability: entity.EnrollProbability,
StudentScore: entity.StudentConvertedScore,
PrivateStudentScore: entity.PrivateStudentConvertedScore,
StudentConvertedScore: entity.StudentConvertedScore,
CreateTime: entity.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: "",
}
// 注意SchoolName, ChineseScoreLimitation 等字段需要从关联表或其他来源获取
// 这里设置为空字符串
vo.SchoolName = ""
vo.ChineseScoreLimitation = 0
vo.EnglishScoreLimitation = 0
vo.CulturalScoreLimitation = 0
vo.ProfessionalScoreLimitation = 0
vo.Province = ""
vo.SchoolNature = ""
vo.InstitutionType = ""
vo.FirstLevelDiscipline = ""
vo.CreateBy = ""
vo.UpdateBy = ""
return vo
}

View File

@ -7,7 +7,6 @@ import (
"server/modules/yx/dto" "server/modules/yx/dto"
"server/modules/yx/entity" "server/modules/yx/entity"
"server/modules/yx/mapper" "server/modules/yx/mapper"
"server/modules/yx/vo"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -46,23 +45,11 @@ func (s *YxHistoryMajorEnrollService) RecommendMajorDTOListSetHistoryInfo(dtoLis
return // Log error return // Log error
} }
// Group by SchoolCode + MajorName 并转换为 VO // Group by SchoolCode + MajorName
historyMap := make(map[string][]vo.YxHistoryMajorEnrollVO) historyMap := make(map[string][]entity.YxHistoryMajorEnroll)
for _, h := range historyList { for _, h := range historyList {
key := h.SchoolCode + "_" + h.MajorName key := h.SchoolCode + "_" + h.MajorName
vo := vo.YxHistoryMajorEnrollVO{ historyMap[key] = append(historyMap[key], h)
ID: h.ID,
Year: h.Year,
EnrollmentCode: h.EnrollmentCode,
EnrollmentCount: h.EnrollNum, // 使用 EnrollNum 替代
RulesEnrollProbability: h.RulesEnrollProbability,
ProbabilityOperator: h.ProbabilityOperator,
AdmissionLine: h.AdmissionLine,
ControlLine: h.ControlLine,
SchoolCode: h.SchoolCode,
MajorCode: h.MajorCode,
}
historyMap[key] = append(historyMap[key], vo)
} }
for i := range *dtoList { for i := range *dtoList {

View File

@ -4,17 +4,15 @@ package service
import ( import (
"fmt" "fmt"
"server/common" "server/common"
userVO "server/modules/user/vo"
"server/modules/yx/dto"
"server/modules/yx/entity" "server/modules/yx/entity"
"server/modules/yx/mapper" "server/modules/yx/mapper"
yxVO "server/modules/yx/vo" "server/modules/yx/vo"
"time" "time"
) )
// ScoreService 定义成绩服务的接口,用于解耦 // ScoreService 定义成绩服务的接口,用于解耦
type ScoreService interface { type ScoreService interface {
GetByID(id string) (userVO.UserScoreVO, error) GetByID(id string) (interface{}, error)
GetActiveScoreByID(userID string) (entity.YxUserScore, error) GetActiveScoreByID(userID string) (entity.YxUserScore, error)
GetActiveScoreID(userID string) (string, error) GetActiveScoreID(userID string) (string, error)
UpdateFields(id string, fields map[string]interface{}) error UpdateFields(id string, fields map[string]interface{}) error
@ -62,60 +60,6 @@ func (s *YxVolunteerService) CreateByScoreId(scoreId string, userId string) erro
return s.mapper.Create(&volunteer) return s.mapper.Create(&volunteer)
} }
// CreateVolunteer 创建志愿并返回 VO
func (s *YxVolunteerService) CreateVolunteer(req *dto.CreateVolunteerRequest) (*yxVO.YxVolunteerVO, error) {
// DTO 转 Entity
entityItem := &entity.YxVolunteer{
ID: common.GenerateStringID(),
VolunteerName: req.VolunteerName,
ScoreId: req.ScoreId,
CreateType: req.CreateType,
State: "0", // 默认未激活
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if req.CreateType == "" {
entityItem.CreateType = "1" // 默认手动生成
}
if err := s.mapper.Create(entityItem); err != nil {
return nil, fmt.Errorf("创建志愿失败: %w", err)
}
return s.convertToVO(*entityItem), nil
}
// UpdateVolunteer 更新志愿并返回 VO
func (s *YxVolunteerService) UpdateVolunteer(id string, req *dto.UpdateVolunteerRequest) (*yxVO.YxVolunteerVO, error) {
// 获取原数据
entityItemPtr, err := s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("志愿不存在: %w", err)
}
// 更新字段
updateFields := make(map[string]interface{})
if req.VolunteerName != "" {
updateFields["volunteer_name"] = req.VolunteerName
}
if req.State != "" {
updateFields["state"] = req.State
}
updateFields["update_time"] = time.Now()
if err := s.mapper.UpdateFields(id, updateFields); err != nil {
return nil, fmt.Errorf("更新志愿失败: %w", err)
}
// 重新获取更新后的数据
entityItemPtr, err = s.GetByID(id)
if err != nil {
return nil, fmt.Errorf("获取更新后数据失败: %w", err)
}
return s.convertToVO(*entityItemPtr), nil
}
// UpdateName 更新志愿表名称 // UpdateName 更新志愿表名称
func (s *YxVolunteerService) UpdateName(id, name, userID string) error { func (s *YxVolunteerService) UpdateName(id, name, userID string) error {
volunteer, err := s.GetByID(id) volunteer, err := s.GetByID(id)
@ -129,7 +73,7 @@ func (s *YxVolunteerService) UpdateName(id, name, userID string) error {
} }
// ListByUser 查询用户的志愿单列表(包含成绩信息) // ListByUser 查询用户的志愿单列表(包含成绩信息)
func (s *YxVolunteerService) ListByUser(userID string, page, size int) ([]yxVO.UserVolunteerVO, int64, error) { func (s *YxVolunteerService) ListByUser(userID string, page, size int) ([]vo.UserVolunteerVO, int64, error) {
return s.mapper.ListByUser(userID, page, size) return s.mapper.ListByUser(userID, page, size)
} }
@ -187,19 +131,3 @@ func (s *YxVolunteerService) SwitchVolunteer(id, userID string, scoreService Sco
} }
return scoreService.UpdateFields(volunteer.ScoreId, map[string]interface{}{"state": "1"}) return scoreService.UpdateFields(volunteer.ScoreId, map[string]interface{}{"state": "1"})
} }
// convertToVO Entity 转 VO私有方法
func (s *YxVolunteerService) convertToVO(entity entity.YxVolunteer) *yxVO.YxVolunteerVO {
return &yxVO.YxVolunteerVO{
ID: entity.ID,
VolunteerName: entity.VolunteerName,
ScoreId: entity.ScoreId,
CreateType: entity.CreateType,
State: entity.State,
CreateBy: entity.CreateBy,
CreateTime: entity.CreateTime,
UpdateBy: entity.UpdateBy,
UpdateTime: entity.UpdateTime,
SysOrgCode: entity.SysOrgCode,
}
}

View File

@ -1,19 +1,14 @@
package vo package vo
import ( import (
"server/types" "server/modules/yx/entity"
) )
// UserVolunteerVO 志愿单视图对象,关联成绩信息 // UserVolunteerVO 志愿单视图对象,关联成绩信息
type UserVolunteerVO struct { type UserVolunteerVO struct {
ID string `gorm:"column:id;primaryKey" json:"id"` entity.YxVolunteer
VolunteerName string `gorm:"column:volunteer_name" json:"volunteerName"` // 志愿单名称 ProfessionalCategory string `json:"professionalCategory"` // 专业类别
ScoreId string `gorm:"column:score_id" json:"scoreId"` // 使用成绩id Province string `json:"province"` // 省份
CreateType string `gorm:"column:create_type;default:1" json:"createType"` // 生成类型(1.手动生成,2.智能生成) CulturalScore float64 `json:"culturalScore"` // 文化分
State string `gorm:"column:state;default:0" json:"state"` // 志愿单状态(0-否1.正在使用2-历史) ProfessionalScore float64 `json:"professionalScore"` // 专业成绩分
UpdateTime types.DateTime `gorm:"column:update_time" json:"updateTime"` // 更新日期 }
ProfessionalCategory string `json:"professionalCategory"` // 专业类别
Province string `json:"province"` // 省份
CulturalScore float64 `json:"culturalScore"` // 文化分
ProfessionalScore float64 `json:"professionalScore"` // 专业成绩分
}

View File

@ -1,40 +0,0 @@
package vo
// YxCalculationMajorVO 计算专业视图对象
type YxCalculationMajorVO struct {
ID string `json:"id"`
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"` // 概率操作符
Kslx string `json:"kslx"` // 考试类型
State string `json:"state"` // 状态
Province string `json:"province"` // 省份
SchoolNature string `json:"schoolNature"` // 院校性质
InstitutionType string `json:"institutionType"` // 院校类型
EnrollProbability float64 `json:"enrollProbability"` // 录取概率
StudentScore float64 `json:"studentScore"` // 学生分数
PrivateStudentScore float64 `json:"privateStudentScore"` // 私有学生分数
StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分
FirstLevelDiscipline string `json:"firstLevelDiscipline"` // 一级学科
CreateBy string `json:"createBy"` // 创建人
CreateTime string `json:"createTime"` // 创建时间
UpdateBy string `json:"updateBy"` // 更新人
UpdateTime string `json:"updateTime"` // 更新时间
}

View File

@ -1,17 +0,0 @@
package vo
// YxHistoryMajorEnrollVO 历年专业招生视图对象
type YxHistoryMajorEnrollVO struct {
ID string `json:"id"`
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"` // 控制线
SchoolCode string `json:"schoolCode"` // 院校代码
MajorCode string `json:"majorCode"` // 专业代码
RulesEnrollProbabilitySx string `json:"rulesEnrollProbabilitySx"` // 录取规则概率(山东)
// 其他字段...
}

View File

@ -1,17 +0,0 @@
package vo
import "time"
// YxVolunteerVO 志愿视图对象
type YxVolunteerVO struct {
ID string `json:"id"`
VolunteerName string `json:"volunteerName"` // 志愿单名称
ScoreId string `json:"scoreId"` // 使用成绩id
CreateType string `json:"createType"` // 生成类型(1.手动生成,2.智能生成)
State string `json:"state"` // 志愿单状态(0-否1-正在使用2-历史)
CreateBy string `json:"createBy"` // 创建人
CreateTime time.Time `json:"createTime"` // 创建日期
UpdateBy string `json:"updateBy"` // 更新人
UpdateTime time.Time `json:"updateTime"` // 更新日期
SysOrgCode string `json:"sysOrgCode"` // 所属部门
}

View File

@ -1,71 +0,0 @@
package types
import (
"database/sql/driver"
"encoding/json"
"time"
)
// DateTime 自定义时间类型JSON序列化格式为 '2006-01-02 15:04:05'
type DateTime time.Time
// MarshalJSON 实现 json.Marshaler 接口,将时间序列化为 '2006-01-02 15:04:05' 格式
func (dt DateTime) MarshalJSON() ([]byte, error) {
t := time.Time(dt)
if t.IsZero() {
return []byte("null"), nil
}
formatted := t.Format("2006-01-02 15:04:05")
return json.Marshal(formatted)
}
// UnmarshalJSON 实现 json.Unmarshaler 接口,解析 '2006-01-02 15:04:05' 格式的时间
func (dt *DateTime) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t, err := time.Parse("2006-01-02 15:04:05", str)
if err != nil {
return err
}
*dt = DateTime(t)
return nil
}
// Value 实现 driver.Valuer 接口,用于数据库存储
func (dt DateTime) Value() (driver.Value, error) {
t := time.Time(dt)
return t, nil
}
// Scan 实现 sql.Scanner 接口,用于数据库读取
func (dt *DateTime) Scan(value interface{}) error {
if value == nil {
return nil
}
t, ok := value.(time.Time)
if !ok {
return nil
}
*dt = DateTime(t)
return nil
}
// Time 转换为标准 time.Time 类型
func (dt DateTime) Time() time.Time {
return time.Time(dt)
}
// NewDateTime 从 time.Time 创建 DateTime
func NewDateTime(t time.Time) DateTime {
return DateTime(t)
}
// Now 创建当前时间的 DateTime
func Now() DateTime {
return DateTime(time.Now())
}