From 7433724a54031f743bc45e52e390d8f36c244953 Mon Sep 17 00:00:00 2001 From: zwt13703 Date: Sat, 14 Mar 2026 20:50:13 +0800 Subject: [PATCH] default --- AGENTS.md | 217 ++ Help.md | 46 + IFLOW.md | 226 ++ README.md | 184 +- docs/Task1.md | 37 + server/common/base_mapper.go | 92 + server/common/base_service.go | 108 + server/common/constants.go | 66 + server/common/context.go | 62 + server/common/id_utils.go | 84 + server/common/logger.go | 210 ++ server/common/password.go | 190 ++ server/common/response.go | 35 + server/common/score_calculator.go | 197 ++ server/common/snowflake/snowflake.go | 122 + server/common/snowflake/snowflake_test.go | 37 + server/common/sql_utils.go | 14 + server/config/config.dev.yaml | 55 + server/config/config.go | 143 + server/config/config.prod.yaml | 52 + server/config/config.test.yaml | 52 + server/config/database.go | 111 + server/config/redis.go | 43 + server/docs/docs.go | 2369 +++++++++++++++++ server/docs/swagger.json | 2345 ++++++++++++++++ server/docs/swagger.yaml | 1559 +++++++++++ server/go.mod | 57 + server/go.sum | 188 ++ server/main.go | 171 ++ server/middleware/auth.go | 70 + server/middleware/cors.go | 31 + server/middleware/ratelimit.go | 142 + server/middleware/security.go | 111 + .../system/controller/sys_auth_controller.go | 81 + .../system/controller/sys_user_controller.go | 161 ++ server/modules/system/dto/auth_dto.go | 7 + server/modules/system/dto/sys_user_dto.go | 26 + server/modules/system/entity/sys_user.go | 55 + .../modules/system/mapper/sys_user_mapper.go | 43 + .../system/service/sys_user_service.go | 323 +++ server/modules/system/vo/login_user_vo.go | 12 + server/modules/system/vo/sys_user_vo.go | 20 + .../user/controller/user_auth_controller.go | 86 + .../user/controller/user_major_controller.go | 120 + .../user/controller/user_score_controller.go | 113 + .../controller/user_volunteer_controller.go | 199 ++ server/modules/user/dto/user_major_dto.go | 13 + server/modules/user/dto/user_volunteer_dto.go | 6 + .../user/service/user_score_service.go | 527 ++++ server/modules/user/vo/user_score_vo.go | 20 + server/modules/user/vo/volunteer_detail_vo.go | 55 + .../yx_calculation_major_controller.go | 173 ++ .../yx_history_major_enroll_controller.go | 168 ++ .../controller/yx_school_major_controller.go | 176 ++ .../yx/controller/yx_user_score_controller.go | 123 + .../yx/controller/yx_volunteer_controller.go | 150 ++ .../yx_volunteer_record_controller.go | 123 + .../modules/yx/dto/probability_count_dto.go | 9 + .../yx/dto/yx_calculation_major_dto.go | 61 + server/modules/yx/dto/yx_school_major_dto.go | 120 + server/modules/yx/dto/yx_user_score_dto.go | 143 + server/modules/yx/dto/yx_volunteer_dto.go | 14 + .../modules/yx/entity/yx_calculation_major.go | 40 + .../yx/entity/yx_history_major_enroll.go | 42 + .../entity/yx_history_score_control_line.go | 22 + server/modules/yx/entity/yx_school_major.go | 46 + server/modules/yx/entity/yx_user_score.go | 42 + server/modules/yx/entity/yx_volunteer.go | 22 + .../modules/yx/entity/yx_volunteer_record.go | 25 + .../yx/mapper/yx_calculation_major_mapper.go | 527 ++++ .../mapper/yx_history_major_enroll_mapper.go | 24 + .../yx_history_score_control_line_mapper.go | 35 + .../yx/mapper/yx_school_major_mapper.go | 71 + .../modules/yx/mapper/yx_user_score_mapper.go | 44 + .../modules/yx/mapper/yx_volunteer_mapper.go | 52 + .../yx/mapper/yx_volunteer_record_mapper.go | 34 + .../service/yx_calculation_major_service.go | 972 +++++++ .../yx_history_major_enroll_service.go | 136 + .../yx_history_score_control_line_service.go | 47 + .../yx/service/yx_school_major_service.go | 39 + .../yx/service/yx_user_score_service.go | 44 + .../yx/service/yx_volunteer_record_service.go | 49 + .../yx/service/yx_volunteer_service.go | 205 ++ server/modules/yx/vo/user_volunteer_vo.go | 19 + .../modules/yx/vo/yx_calculation_major_vo.go | 40 + .../yx/vo/yx_history_major_enroll_vo.go | 17 + server/modules/yx/vo/yx_volunteer_vo.go | 17 + server/tests/help.md | 6 + server/tests/init_test.go | 30 + server/tests/service_test.go | 89 + server/types/date_time.go | 71 + task_detail_2026_03_14.md | 32 + 92 files changed, 15391 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 Help.md create mode 100644 IFLOW.md create mode 100644 docs/Task1.md create mode 100644 server/common/base_mapper.go create mode 100644 server/common/base_service.go create mode 100644 server/common/constants.go create mode 100644 server/common/context.go create mode 100644 server/common/id_utils.go create mode 100644 server/common/logger.go create mode 100644 server/common/password.go create mode 100644 server/common/response.go create mode 100644 server/common/score_calculator.go create mode 100644 server/common/snowflake/snowflake.go create mode 100644 server/common/snowflake/snowflake_test.go create mode 100644 server/common/sql_utils.go create mode 100644 server/config/config.dev.yaml create mode 100644 server/config/config.go create mode 100644 server/config/config.prod.yaml create mode 100644 server/config/config.test.yaml create mode 100644 server/config/database.go create mode 100644 server/config/redis.go create mode 100644 server/docs/docs.go create mode 100644 server/docs/swagger.json create mode 100644 server/docs/swagger.yaml create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/main.go create mode 100644 server/middleware/auth.go create mode 100644 server/middleware/cors.go create mode 100644 server/middleware/ratelimit.go create mode 100644 server/middleware/security.go create mode 100644 server/modules/system/controller/sys_auth_controller.go create mode 100644 server/modules/system/controller/sys_user_controller.go create mode 100644 server/modules/system/dto/auth_dto.go create mode 100644 server/modules/system/dto/sys_user_dto.go create mode 100644 server/modules/system/entity/sys_user.go create mode 100644 server/modules/system/mapper/sys_user_mapper.go create mode 100644 server/modules/system/service/sys_user_service.go create mode 100644 server/modules/system/vo/login_user_vo.go create mode 100644 server/modules/system/vo/sys_user_vo.go create mode 100644 server/modules/user/controller/user_auth_controller.go create mode 100644 server/modules/user/controller/user_major_controller.go create mode 100644 server/modules/user/controller/user_score_controller.go create mode 100644 server/modules/user/controller/user_volunteer_controller.go create mode 100644 server/modules/user/dto/user_major_dto.go create mode 100644 server/modules/user/dto/user_volunteer_dto.go create mode 100644 server/modules/user/service/user_score_service.go create mode 100644 server/modules/user/vo/user_score_vo.go create mode 100644 server/modules/user/vo/volunteer_detail_vo.go create mode 100644 server/modules/yx/controller/yx_calculation_major_controller.go create mode 100644 server/modules/yx/controller/yx_history_major_enroll_controller.go create mode 100644 server/modules/yx/controller/yx_school_major_controller.go create mode 100644 server/modules/yx/controller/yx_user_score_controller.go create mode 100644 server/modules/yx/controller/yx_volunteer_controller.go create mode 100644 server/modules/yx/controller/yx_volunteer_record_controller.go create mode 100644 server/modules/yx/dto/probability_count_dto.go create mode 100644 server/modules/yx/dto/yx_calculation_major_dto.go create mode 100644 server/modules/yx/dto/yx_school_major_dto.go create mode 100644 server/modules/yx/dto/yx_user_score_dto.go create mode 100644 server/modules/yx/dto/yx_volunteer_dto.go create mode 100644 server/modules/yx/entity/yx_calculation_major.go create mode 100644 server/modules/yx/entity/yx_history_major_enroll.go create mode 100644 server/modules/yx/entity/yx_history_score_control_line.go create mode 100644 server/modules/yx/entity/yx_school_major.go create mode 100644 server/modules/yx/entity/yx_user_score.go create mode 100644 server/modules/yx/entity/yx_volunteer.go create mode 100644 server/modules/yx/entity/yx_volunteer_record.go create mode 100644 server/modules/yx/mapper/yx_calculation_major_mapper.go create mode 100644 server/modules/yx/mapper/yx_history_major_enroll_mapper.go create mode 100644 server/modules/yx/mapper/yx_history_score_control_line_mapper.go create mode 100644 server/modules/yx/mapper/yx_school_major_mapper.go create mode 100644 server/modules/yx/mapper/yx_user_score_mapper.go create mode 100644 server/modules/yx/mapper/yx_volunteer_mapper.go create mode 100644 server/modules/yx/mapper/yx_volunteer_record_mapper.go create mode 100644 server/modules/yx/service/yx_calculation_major_service.go create mode 100644 server/modules/yx/service/yx_history_major_enroll_service.go create mode 100644 server/modules/yx/service/yx_history_score_control_line_service.go create mode 100644 server/modules/yx/service/yx_school_major_service.go create mode 100644 server/modules/yx/service/yx_user_score_service.go create mode 100644 server/modules/yx/service/yx_volunteer_record_service.go create mode 100644 server/modules/yx/service/yx_volunteer_service.go create mode 100644 server/modules/yx/vo/user_volunteer_vo.go create mode 100644 server/modules/yx/vo/yx_calculation_major_vo.go create mode 100644 server/modules/yx/vo/yx_history_major_enroll_vo.go create mode 100644 server/modules/yx/vo/yx_volunteer_vo.go create mode 100644 server/tests/help.md create mode 100644 server/tests/init_test.go create mode 100644 server/tests/service_test.go create mode 100644 server/types/date_time.go create mode 100644 task_detail_2026_03_14.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f38bf33 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,217 @@ +# 艺考招生管理系统 API + +## 项目概述 + +艺考招生管理系统是一个基于 Go + Gin + GORM + Redis 的 RESTful API 服务,用于管理和计算艺考志愿填报、成绩统计和录取概率等功能。 + +### 核心技术栈 + +- **Go 1.21+** - 主要开发语言 +- **Gin** - Web 框架 +- **GORM** - ORM 框架 +- **MySQL 8.0** - 数据存储 +- **Redis** - 会话存储和限流 +- **Swaggo** - API 文档生成 + +### 项目架构 + +项目采用典型的分层架构模式: + +``` +server/ +├── main.go # 应用程序入口 +├── config/ # 配置文件 +│ ├── config.go # 应用配置 +│ ├── database.go # MySQL 配置 +│ └── redis.go # Redis 配置 +├── common/ # 公共组件 +│ ├── response.go # 统一响应格式 +│ ├── context.go # 上下文工具 +│ ├── logger.go # 日志工具 +│ └── password.go # 密码加密 +├── middleware/ # 中间件 +│ ├── auth.go # 登录鉴权 +│ ├── security.go # 安全校验 +│ ├── ratelimit.go # 接口限流 +│ └── cors.go # 跨域处理 +├── modules/ # 业务模块 +│ ├── system/ # 系统模块 (用户认证、权限管理) +│ ├── user/ # 用户模块 (用户相关功能) +│ └── yx/ # 艺考模块 (核心业务) +├── docs/ # API 文档 +└── logs/ # 日志目录 +``` + +### 业务模块 + +#### 1. 系统模块 (system) +- 用户认证 (登录/登出/用户信息) +- 用户管理 + +#### 2. 用户模块 (user) +- 成绩管理 +- 专业选择 +- 志愿填报 +- 用户认证 + +#### 3. 艺考模块 (yx) +- 院校专业管理 +- 历年招生数据 +- 计算专业逻辑 +- 用户成绩管理 +- 志愿管理 +- 志愿记录管理 + +## 部署与运行 + +### 环境要求 +- Go 1.21+ +- MySQL 8.0 +- Redis + +### 快速启动 + +```bash +cd server +go mod tidy +swag init +go run main.go +``` + +### 配置说明 + +系统支持多环境配置文件: + +- `config/config.dev.yaml` - 开发环境 +- `config/config.test.yaml` - 测试环境 +- `config/config.prod.yaml` - 生产环境 + +配置文件格式示例: +```yaml +server: + port: 8081 + +database: + driver: mysql + host: localhost + port: 3306 + database: yitisheng + username: root + password: "password" + +redis: + addr: localhost:6379 + password: "password" + db: 1 +``` + +## 安全机制 + +### 1. 登录鉴权 +- 基于 JWT Token 的身份验证 +- 支持白名单配置,允许特定接口无需登录访问 +- Token 存储在 Redis 中,支持过期时间设置 + +### 2. 安全校验 +- 请求头需携带签名进行验证 +- 防暴力破解机制 +- 支持白名单配置 + +### 3. 接口限流 +- 基于 Redis 滑动窗口算法 +- 支持按用户ID或IP限流 +- 可为不同接口配置不同限流规则 + +## API 接口 + +### 认证相关 +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | /api/user/auth/login | 用户登录 | +| POST | /api/user/auth/logout | 用户登出 | +| GET | /api/user/auth/info | 获取当前用户信息 | + +### 系统管理 +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /api/sys-users | 获取用户列表 | +| POST | /api/sys-users | 创建用户 | +| PUT | /api/sys-users/:id | 更新用户 | +| DELETE | /api/sys-users/:id | 删除用户 | + +### 艺考相关 +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /api/yx-school-majors | 获取院校专业列表 | +| GET | /api/yx-history-enrolls | 获取历年招生数据 | +| GET | /api/yx-calculation-majors | 获取计算专业数据 | +| GET | /api/yx-user-scores | 获取用户成绩 | +| GET | /api/yx-volunteers | 获取志愿列表 | +| POST | /api/yx-volunteers | 创建志愿 | +| PUT | /api/yx-volunteers/:id | 更新志愿 | +| DELETE | /api/yx-volunteers/:id | 删除志愿 | + +### 中间件执行顺序 + +``` +请求 -> 安全校验 -> 限流 -> 登录鉴权 -> Controller +``` + +## 数据实体 + +### YxVolunteer (志愿表) +- ID: 主键 +- VolunteerName: 志愿单名称 +- ScoreId: 关联成绩ID +- CreateType: 生成类型 (1.手动生成, 2.智能生成) +- State: 状态 (0-未使用, 1-使用中, 2-历史) +- CreateBy: 创建人 +- CreateTime: 创建时间 +- UpdateBy: 更新人 +- UpdateTime: 更新时间 + +## 常量定义 + +### 业务状态常量 +- StateActive ("1") - 使用中 +- StateInactive ("0") - 未使用/已删除 +- StateHistory ("2") - 历史记录 + +### 令牌相关 +- TokenHeader: "Authorization" +- HeaderTokenPrefix: "Bearer " +- RedisTokenExpire: 24小时 + +## 日志系统 + +- 支持 debug/info/warn/error 级别 +- 输出到 HTML 文件,按日期和启动次数命名 +- 可配置是否同时输出到控制台 + +## 开发规范 + +### 代码风格 +- 使用 Go 语言标准格式 (go fmt) +- 遵循 Go 语言命名规范 +- 使用有意义的变量和函数名 + +### 错误处理 +- 统一使用 common.Error(c, code, message) 返回错误 +- 业务错误码使用 HTTP 标准码或自定义业务码 +- 记录详细错误日志便于调试 + +### 接口文档 +- 使用 Swaggo 注解生成 API 文档 +- 所有公开接口都应有完整的文档注释 +- 包含请求参数、响应格式和错误码说明 + +## 额外说明 + +项目还包含一些 Java 代码文件,如 `ScoreUtil.java`,这表明项目可能正在从 Java 版本迁移到 Go 版本,或者 Java 版本作为参考。Go 版本中的 `server/modules/yx/service/score_util.go` 文件可能包含与 Java 版本相同功能的实现。 + +## 注意事项 + +- 开发环境中的安全校验默认关闭 (enable: false) +- 生产环境务必开启安全校验 +- 需要配置合适的限流规则防止恶意请求 +- 定期清理日志文件避免磁盘空间不足 \ No newline at end of file diff --git a/Help.md b/Help.md new file mode 100644 index 0000000..af6da5c --- /dev/null +++ b/Help.md @@ -0,0 +1,46 @@ +#### 关于 Swagger 文档更新流程: + +新增接口后更新文档步骤: + +在 Controller 方法上添加 Swagger 注解(@Summary、@Param、@Router 等) +在 server 目录运行 swag init 重新生成文档 +重启服务器 +常用 Swagger 注解说明: + +```go +// @Summary 接口简介 +// @Description 详细描述 +// @Tags 分组标签 +// @Accept json +// @Produce json +// @Param name query/path/body 类型 是否必填 "描述" +// @Success 200 {object} Response "成功描述" +// @Failure 400 {object} Response "失败描述" +// @Router /path [get/post/put/delete] +``` + +#### 批量修改/新增/删除 + +Mapper 层: | 方法 | 说明 | |------|------| | UpdateFields(id, fields) | 动态字段更新,只更新指定字段 | | BatchCreate(items, batchSize) | 批量插入,分批处理 | | BatchUpdate(items) | 批量更新 (根据主键) | | BatchUpsert(items, updateColumns) | 批量插入或更新 (存在则更新) | | BatchDelete(ids) | 批量删除 | + +Service 层: 同样的方法,自动处理 UUID 生成。 + +使用示例: + +``` +// 动态字段更新 - 只更新指定字段 +service.UpdateFields("xxx-id", map[string]interface{}{ + "school_name": "新名称", + "plan_num": 100, +}) + +// 批量插入 +items := []entity.SchoolMajor{{...}, {...}} +service.BatchCreate(items) + +// 批量插入或更新 (存在则更新指定字段) +service.BatchUpsert(items, []string{"school_name", "plan_num"}) + +// 批量删除 +service.BatchDelete([]string{"id1", "id2", "id3"}) +``` \ No newline at end of file diff --git a/IFLOW.md b/IFLOW.md new file mode 100644 index 0000000..ea88afd --- /dev/null +++ b/IFLOW.md @@ -0,0 +1,226 @@ +# IFLOW.md - 核心工作规则 + +## Global Protocols + +所有操作必须严格遵循以下系统约束: + +- **交互语言**:技术术语、工具与模型交互强制使用 **English**;用户输出强制使用 **中文**。 +- **最小改动**:仅对需求做针对性改动,严禁影响用户现有的其他功能。 +- **风格一致**:遵循项目现有的代码风格,使用项目已有的工具函数。 + +## Tool Priority + +在执行任何操作前,必须按照以下顺序选择工具,严禁跳级使用: + +**1. MCP 工具**:当 MCP 工具能够完成任务时,必须使用 MCP,禁止降级到内置工具或 Shell 命令。 + +**2. 内置工具**:仅当 MCP 工具**无法覆盖**该功能时,使用内置工具。 + +**3. Shell 命令**:Shell 命令是最后手段,同时遵循以下规则: + +- 只读类安全操作允许直接执行 + +| 类别 | 安全操作示例 | +| ---------------- | ------------------------------------------------- | +| Git 只读操作 | `git status`、`git log`、`git diff`、`git branch` | +| 包管理器只读操作 | `npm list`、`pnpm why`、`pip show` | +| 容器只读操作 | `docker ps`、`docker logs` | +| 环境检查 | `node -v`、`python -version`、`which xxx` | + +- 写入/删除/修改/安装等危险操作必须征得用户同意 + +| 类别 | 危险操作示例 | +| ------------ | ------------------------------------------------------------ | +| Git 写操作 | `commit`、`push`、`pull`、`merge`、`rebase`、`reset`、`checkout ` | +| 文件删除 | `rm`、`rmdir`、清空目录 | +| 批量文件修改 | `sed -i`(多文件)、批量重命名 | +| 包管理写操作 | `pnpm install/uninstall`、`pnpm add/remove`、`uv add/remove` | +| 容器写操作 | `docker rm`、`docker rmi`、`docker-compose down` | +| 系统级操作 | 修改环境变量、修改系统配置文件 | + +- 触发危险操作时告知用户 + + ``` + # 告知示例 + !!!即将执行危险操作!!!: + 命令:git push origin main + 影响:将本地 main 分支的提交推送到远程仓库 + + 是否继续?请回复"确认"或"取消" + ``` + +## Technology Stack + +如果是对已有项目二次开发/修改bug,则遵循项目已有技术栈。 + +如果是从0到1开发新的项目,尽可能使用下方给出的技术栈: + +### 后端 - Go(主力) + +| 配置项 | 要求 | +| -------- | -------------------------------------- | +| 语言版本 | Go 1.21+ | +| 开发框架 | Gin | +| ORM框架 | GORM | +| 代码规范 | Google Go 编程规范 | + +### 后端 - Java + +| 配置项 | 要求 | +| -------- | -------------------------------------- | +| 语言版本 | Java 17 | +| 开发框架 | Spring Boot 3.x + Spring Cloud Alibaba | +| ORM框架 | MyBatis Plus | +| 包管理器 | Maven | +| 代码规范 | 阿里巴巴Java开发手册(嵩山版) | + +### 后端 - Python(辅助/小工具) + +| 配置项 | 要求 | +| ---------- | ------------------------------------------------------------ | +| 语言版本 | Python 3.10+ | +| 开发框架 | FastAPI(轻量级API)/ Typer(CLI工具)/ Streamlit(数据可视化) | +| 包管理工具 | uv | +| 代码规范 | PEP 8 + Google Python Style Guide | +| 虚拟环境 | **强制启用**(uv venv) | + +### 后端 - 其他组件 + +| 组件 | 选型 | +| -------- | --------- | +| 数据库 | MySQL 8.x | +| 缓存 | Redis | + +### 前端 - TypeScript + Vue 3 + +| 配置项 | 要求 | +| -------- | ---------------------------- | +| 语言版本 | TypeScript 5.x | +| 开发框架 | Vue 3(Composition API) | +| UI组件库 | TailWind CSS | +| 包管理器 | pnpm | +| 构建工具 | Vite | +| 代码规范 | ESLint(严格模式)+ Prettier | + +### 桌面端 - Electron + +| 配置项 | 要求 | +| -------- | ------------------ | +| 基础框架 | Vue 3 + TypeScript | +| 打包工具 | electron-builder | + +## Workflow + +在开发过程中,严格按照以下阶段顺序执行任务。 + +**格式要求**: 每次回复必须在开头标注 `【当前阶段: [阶段名称]】` + +--- + +### Phase 0:上下文全量检索 + +**执行条件**:在生成任何建议或代码前。 + +**调用工具**:`mcp__auggie-mcp__codebase-retrieval` + +**检索策略**: + +- 禁止基于假设(Assumption)回答。 +- 使用自然语言(NL)构建语义查询(Where/What/How)。 +- **完整性检查**:必须获取相关类、函数、变量的完整定义与签名。若上下文不足,触发递归检索。 + +**需求对齐**:若检索后需求仍有模糊空间,**必须**向用户输出引导性问题列表,直至需求边界清晰(无遗漏、无冗余)。 + +--- + +### Phase 1: 产品需求分析 + +**角色**:产品经理 + +**方法**:通过`AskUserQuestion`工具进行多轮提问引导,直到需求完全量化。 + +**最小维度**: + +- 目标用户与使用场景。 +- 核心功能清单(按优先级 P0/P1/P2 排列)。 +- 业务规则与约束条件。 + +**输出**:`requirement.md`(需求规格书) + +--- + +### Phase 2: UI/UX 设计 + +**角色**:UI/UX 设计师 + +**方法**:基于`requirement.md`,通过多轮提问引导,定义交互与视觉规范。 + +**最小维度**: + +- 核心用户流程。 +- 页面结构与布局。 +- 组件状态定义。 + +**冲突检测**:与`requirement.md`中的约束进行一致性校验,如有冲突,必须提问澄清后再继续。 + +**输出**:`ui_ux_specifications.md`(UI/UX 规范) + +--- + +### Phase 3: 架构设计 + +**角色**:系统架构师 + +**方法**:基于`requirement.md`和`ui_ux_specifications.md`,通过多轮提问引导,设计技术方案。 + +**最小维度**: + +- 技术栈选型(遵循本文档`Technology Stack`章节)。 +- 系统分层、模块划分、目录结构。 +- API 契约定义。 + +**冲突检测**:与`requirement.md`中的约束进行一致性校验,如有冲突,必须提问澄清后再继续。 + +**输出**:`architecture_design_document.md`(架构设计文档) + +--- + +### Phase 4: 代码实现 + +**角色**:全栈开发工程师 + +**方法**: + +1. 根据 `requirement.md` 和 `architecture_design_document.md`,拆分开发任务 +2. 在 `task_list.md` 中记录任务清单,将**待开发/已开发/跳过**的任务通过不同的复选框进行标记 +3. 逐个任务开发,每个任务完成后更新状态 + +**输出**:`task_list.md`(任务清单,持续更新)、`deployment.md`(部署文档) + +--- + +### Phase 5: 代码审计 + +**执行条件**:每个任务模块开发完成后进行增量审计,全部完成后进行最终审计。 + +**角色**:代码审计工程师 + +**方法**:根据`task_list.md`,逐个对已完成代码进行 Code Review。 + +**审计范围**: + +- 功能完整性:是否覆盖`requirement.md`对应功能的全部需求 +- 代码质量:命名规范、无重复代码、适当抽象、注释完整 +- 安全检查:输入验证、SQL注入防护、XSS防护、敏感数据处理、权限控制 +- 性能检查:算法效率、数据库查询优化、资源释放 + +**问题分级与处理**: + +| 级别 | 定义 | 处理方式 | +| ---- | -------------------------------- | ------------------ | +| P0 | 安全漏洞、数据风险、核心功能缺失 | 阻断发布,立即修复 | +| P1 | 功能不完整、明显性能问题 | 当前迭代必须修复 | +| P2 | 代码规范、可维护性问题 | 可选 | +| P3 | 优化建议 | 可选 | + +**输出**:`audit_report.md`(审计报告)、`fix_changelog.md`(修复记录) \ No newline at end of file diff --git a/README.md b/README.md index 49359ef..e46710d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,184 @@ -# golang-api-server +# 艺考招生管理系统 API +基于 Go + Gin + GORM + Redis 的 RESTful API 服务。 + +## 技术栈 + +- Go 1.21+ +- Gin (Web框架) +- GORM (ORM框架) +- MySQL 8.0 +- Redis (会话存储/限流) +- Swaggo (API文档) + +## 项目结构 + +``` +server/ +├── main.go +├── config/ +│ ├── config.go # 应用配置 (日志/安全/限流) +│ ├── database.go # MySQL配置 +│ └── redis.go # Redis配置 +├── common/ +│ ├── response.go # 统一响应 +│ ├── context.go # 上下文工具 +│ ├── logger.go # 日志工具 +│ └── password.go # 密码加密 (PBE) +├── middleware/ +│ ├── auth.go # 登录鉴权 +│ ├── security.go # 安全校验 (防暴力入侵) +│ └── ratelimit.go # 接口限流 +├── modules/ +│ ├── system/ # 系统模块 +│ └── yx/ # 艺考模块 +├── logs/ # 日志目录 +│ └── 2025-12-17-1.html # HTML格式日志 +└── docs/ # Swagger文档 +``` + +## 快速开始 + +```bash +cd server +go mod tidy +swag init +go run main.go +``` + +go mod 失败的可以试试 +https://learnku.com/go/wikis/38122 + +## 配置说明 + +修改 `config/config.go`: + +```go +var AppConfig = &appConfig{ + // 日志配置 + Log: LogConfig{ + Level: "debug", // debug/info/warn/error + Dir: "logs", // 日志目录 + Console: true, // 是否输出到控制台 + }, + // 安全配置 + Security: SecurityConfig{ + Enable: true, // 是否启用 + HeaderKey: "X-App-Sign", // 签名字段 + SecretKey: "yts@2025#secure", // 签名密钥 + }, + // 限流配置 + RateLimit: RateLimitConfig{ + Enable: true, + Default: RateLimitRule{Interval: 2, MaxRequests: 1}, // 默认2秒1次 + Rules: map[string]RateLimitRule{ + "/api/auth/login": {Interval: 5, MaxRequests: 1}, // 登录5秒1次 + }, + }, +} +``` + +## 功能说明 + +### 1. 日志系统 + +- 支持 debug/info/warn/error 级别 +- 输出到 HTML 文件,按日期和启动次数命名 +- 可配置是否同时输出到控制台 + +```go +common.Debug("调试信息: %s", msg) +common.Info("普通信息: %s", msg) +common.Warn("警告信息: %s", msg) +common.LogError("错误信息: %s", msg) +``` + +### 2. 安全校验 (防暴力入侵) + +请求头需携带签名: + +``` +X-App-Sign: MD5(timestamp + secretKey) +X-App-Timestamp: 毫秒时间戳 +``` + +前端签名示例 (JavaScript): + +```javascript +const timestamp = Date.now().toString(); +const sign = md5(timestamp + "yts@2025#secure"); + +fetch("/api/xxx", { + headers: { + "X-App-Sign": sign, + "X-App-Timestamp": timestamp, + Authorization: "Bearer " + token, + }, +}); +``` + +### 3. 接口限流 + +- 基于 Redis 滑动窗口算法 +- 支持按用户ID或IP限流 +- 不同接口可配置不同规则 + +默认规则: 2秒1次 +超过限制返回: `{"code": 429, "message": "操作过快,请稍后再试"}` + +配置示例: + +```go +Rules: map[string]RateLimitRule{ + "/api/auth/login": {Interval: 5, MaxRequests: 1}, // 5秒1次 + "/api/yx-school-majors": {Interval: 1, MaxRequests: 5}, // 1秒5次 +} +``` + +## 中间件执行顺序 + +``` +请求 -> 安全校验 -> 限流 -> 登录鉴权 -> Controller +``` + +## 白名单配置 + +```go +// 安全校验白名单 +middleware.AddSecurityWhitelist("/api/public/xxx") + +// 限流白名单 +middleware.AddRateLimitWhitelist("/api/public/xxx") + +// 登录鉴权白名单 +middleware.AddWhiteList("/api/public/xxx") +``` + +## API 接口 + +### 认证 + +| 方法 | 路径 | 说明 | +| ---- | ---------------- | ------------ | +| POST | /api/auth/login | 登录 | +| POST | /api/auth/logout | 登出 | +| GET | /api/auth/info | 获取当前用户 | + +### 用户管理 `/api/sys-users` + +### 院校专业 `/api/yx-school-majors` + +### 历年招生 `/api/yx-history-enrolls` + +### 计算专业 `/api/yx-calculation-majors` + +## 日志文件示例 + +日志以 HTML 格式保存,支持浏览器直接打开查看: + +``` +logs/ +├── 2025-12-17-1.html # 第1次启动 +├── 2025-12-17-2.html # 第2次启动 +└── 2025-12-18-1.html # 新的一天 +``` diff --git a/docs/Task1.md b/docs/Task1.md new file mode 100644 index 0000000..6292f32 --- /dev/null +++ b/docs/Task1.md @@ -0,0 +1,37 @@ +给当前Golang系统 增加 postgresql 的支持,可以切换mysql或者postgresql(通过config yaml配置 主数据源)。 +然后将以下移动端用户信息表给我改成postgresql的建表格式(加上COMMENT信息),并增加对应的代码(Entity、Service、Mapper、Controller) +要求 系统要兼容mysql/postgresql 切换使用,目前只有两个表t_user、t_platform_user,你需要帮我在docs/ 下建立 sql文件夹,储存 mysql以及postgresql的建表语句。 + +``` +CREATE TABLE t_user ( + id BIGSERIAL PRIMARY KEY, -- 全局唯一用户ID(自增) + username VARCHAR(50) COMMENT '用户名(可选,后台管理用)', + nickname VARCHAR(100) COMMENT '用户昵称(各平台统一)', + avatar_url VARCHAR(500) COMMENT '用户头像URL', + phone VARCHAR(20) UNIQUE COMMENT '手机号(脱敏存储,如138****1234)', + gender TINYINT COMMENT '性别:0-未知,1-男,2-女', + status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-正常', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + deleted TINYINT DEFAULT 0 COMMENT '软删除:0-未删,1-已删' +); +COMMENT ON TABLE t_user IS '用户基础信息表'; +CREATE TABLE t_platform_user ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL COMMENT '关联t_user.id', + platform_type TINYINT NOT NULL COMMENT '平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序', + platform_openid VARCHAR(100) NOT NULL COMMENT '平台唯一标识(微信openid/抖音open_id)', + platform_unionid VARCHAR(100) COMMENT '平台统一标识(微信unionid,多小程序互通用)', + platform_session_key VARCHAR(100) COMMENT '平台会话密钥(微信session_key,加密存储)', + platform_extra JSONB COMMENT '平台扩展字段(如抖音的user_name、微信的city等)', + last_login_time TIMESTAMP COMMENT '最后登录时间', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0, + -- 联合唯一索引:同一平台的openid不能重复 + UNIQUE (platform_type, platform_openid), + -- 外键关联用户表 + CONSTRAINT fk_platform_user_user_id FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE +); +COMMENT ON TABLE t_platform_user IS '平台用户关联表(微信/抖音小程序用户信息)'; +``` diff --git a/server/common/base_mapper.go b/server/common/base_mapper.go new file mode 100644 index 0000000..2709b34 --- /dev/null +++ b/server/common/base_mapper.go @@ -0,0 +1,92 @@ +// Package common 公共组件 +package common + +import ( + "server/config" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// DefaultBatchSize 默认批量操作大小 +const DefaultBatchSize = 100 + +// BaseMapper 泛型 Mapper 基类,封装通用 CRUD 操作 +type BaseMapper[T any] struct { + db *gorm.DB +} + +// NewBaseMapper 创建 BaseMapper 实例 +func NewBaseMapper[T any]() *BaseMapper[T] { + return &BaseMapper[T]{ + db: config.DB, + } +} + +// GetDB 获取数据库实例(允许子类覆盖) +func (m *BaseMapper[T]) GetDB() *gorm.DB { + return m.db +} + +// FindAll 分页查询 +func (m *BaseMapper[T]) FindAll(page, size int) ([]T, int64, error) { + var items []T + var total int64 + query := m.GetDB().Model(new(T)) + query.Count(&total) + err := query.Offset((page - 1) * size).Limit(size).Find(&items).Error + return items, total, err +} + +// FindByID 根据 ID 查询 +func (m *BaseMapper[T]) FindByID(id string) (*T, error) { + var item T + err := m.GetDB().First(&item, "id = ?", id).Error + return &item, err +} + +// Create 创建单个记录 +func (m *BaseMapper[T]) Create(item *T) error { + return m.GetDB().Create(item).Error +} + +// Update 更新单个记录 +func (m *BaseMapper[T]) Update(item *T) error { + return m.GetDB().Save(item).Error +} + +// UpdateFields 更新指定字段 +func (m *BaseMapper[T]) UpdateFields(id string, fields map[string]interface{}) error { + return m.GetDB().Model(new(T)).Where("id = ?", id).Updates(fields).Error +} + +// Delete 删除记录 +func (m *BaseMapper[T]) Delete(id string) error { + return m.GetDB().Delete(new(T), "id = ?", id).Error +} + +// BatchCreate 批量创建 +func (m *BaseMapper[T]) BatchCreate(items []T, batchSize int) error { + if batchSize <= 0 { + batchSize = DefaultBatchSize + } + return m.GetDB().CreateInBatches(items, batchSize).Error +} + +// BatchUpdate 批量更新 +func (m *BaseMapper[T]) BatchUpdate(items []T) error { + return m.GetDB().Save(items).Error +} + +// BatchUpsert 批量插入或更新 +func (m *BaseMapper[T]) BatchUpsert(items []T, updateColumns []string) error { + return m.GetDB().Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "id"}}, + DoUpdates: clause.AssignmentColumns(updateColumns), + }).CreateInBatches(items, DefaultBatchSize).Error +} + +// BatchDelete 批量删除 +func (m *BaseMapper[T]) BatchDelete(ids []string) error { + return m.GetDB().Delete(new(T), "id IN ?", ids).Error +} \ No newline at end of file diff --git a/server/common/base_service.go b/server/common/base_service.go new file mode 100644 index 0000000..a5bbef3 --- /dev/null +++ b/server/common/base_service.go @@ -0,0 +1,108 @@ +// Package common 公共组件 +package common + +import ( + "reflect" +) + +// BaseService 泛型 Service 基类,封装通用业务逻辑 +type BaseService[T any] struct { + mapper *BaseMapper[T] +} + +// NewBaseService 创建 BaseService 实例 +func NewBaseService[T any]() *BaseService[T] { + return &BaseService[T]{ + mapper: NewBaseMapper[T](), + } +} + +// GetMapper 获取 Mapper 实例 +func (s *BaseService[T]) GetMapper() *BaseMapper[T] { + return s.mapper +} + +// List 分页查询 +func (s *BaseService[T]) List(page, size int) ([]T, int64, error) { + return s.mapper.FindAll(page, size) +} + +// GetByID 根据 ID 获取 +func (s *BaseService[T]) GetByID(id string) (*T, error) { + return s.mapper.FindByID(id) +} + +// Create 创建记录(自动生成 ID) +func (s *BaseService[T]) Create(item *T) error { + // 通过反射设置 ID 字段 + if err := setID(item); err != nil { + return err + } + return s.mapper.Create(item) +} + +// Update 更新记录 +func (s *BaseService[T]) Update(item *T) error { + return s.mapper.Update(item) +} + +// UpdateFields 更新指定字段 +func (s *BaseService[T]) UpdateFields(id string, fields map[string]interface{}) error { + return s.mapper.UpdateFields(id, fields) +} + +// Delete 删除记录 +func (s *BaseService[T]) Delete(id string) error { + return s.mapper.Delete(id) +} + +// BatchCreate 批量创建(自动生成 ID) +func (s *BaseService[T]) BatchCreate(items []T) error { + for i := range items { + if err := setID(&items[i]); err != nil { + return err + } + } + return s.mapper.BatchCreate(items, DefaultBatchSize) +} + +// BatchUpdate 批量更新 +func (s *BaseService[T]) BatchUpdate(items []T) error { + return s.mapper.BatchUpdate(items) +} + +// BatchUpsert 批量插入或更新 +func (s *BaseService[T]) BatchUpsert(items []T, updateColumns []string) error { + for i := range items { + if err := setID(&items[i]); err != nil { + return err + } + } + return s.mapper.BatchUpsert(items, updateColumns) +} + +// BatchDelete 批量删除 +func (s *BaseService[T]) BatchDelete(ids []string) error { + return s.mapper.BatchDelete(ids) +} + +// setID 通过反射设置 ID 字段 +func setID(item interface{}) error { + val := reflect.ValueOf(item).Elem() + + // 如果当前类型是指针,再次解引用以获取实际的 Struct + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + if val.Kind() == reflect.Struct { + idField := val.FieldByName("ID") + if idField.IsValid() && idField.Kind() == reflect.String { + // 如果 ID 为空,生成新 ID + if idField.String() == "" { + idField.SetString(GenerateStringID()) + } + } + } + return nil +} diff --git a/server/common/constants.go b/server/common/constants.go new file mode 100644 index 0000000..4ac3c6c --- /dev/null +++ b/server/common/constants.go @@ -0,0 +1,66 @@ +package common + +import "time" + +// Redis 相关常量 +const ( + // RedisTokenPrefix Redis中Token前缀 + RedisTokenPrefix = "login:token:" + // RedisTokenExpire Token过期时间 + RedisTokenExpire = 24 * time.Hour + + // RedisUserScorePrefix Redis中用户成绩前缀 + RedisUserScorePrefix = "user:score:" + // RedisUserScoreExpire 用户成绩过期时间 + RedisUserScoreExpire = 8 * time.Hour +) + +// HTTP/Context 相关常量 +const ( + // ContextUserKey 上下文中存储用户信息的key + ContextUserKey = "loginUser" + // TokenHeader 请求头中Token的key + // "X-Access-Token" + TokenHeader = "Authorization" + // HeaderTokenPrefix Token前缀 (如有需要) + HeaderTokenPrefix = "Bearer " +) + +// 业务状态常量 +const ( + StateActive = "1" // 使用中 + StateInactive = "0" // 未使用/已删除 + StateHistory = "2" // 历史记录 +) + +// 数值常量 +const ( + Number0 = 0 + Number0p75 = 0.75 + Number0p5 = 0.5 + Number5 = 5 + Number7p5 = 7.5 + Number100 = 100 +) + +// 数据类型常量 +const ( + TypeNormal = "1" // 普通类 + TypeArt = "2" // 艺术类 + + // YxConstant 相关常量 + NowYear = "2026" + + // 录取方式常量 + CulturalControlLineGuo = "文线专排" + SpecialControlLineGuo = "专过文排" + CulturalControlLineGuoMain = "文过专排主科" + + W1Z1 = "文*1+专*1" + W1JiaZ1 = "文+专" +) + +var ( + OldYearList = []string{"2025", "2024"} + ShowOldYearList = []string{"2025", "2024", "2023"} +) diff --git a/server/common/context.go b/server/common/context.go new file mode 100644 index 0000000..ec84267 --- /dev/null +++ b/server/common/context.go @@ -0,0 +1,62 @@ +// Package common 公共包 +package common + +import ( + "fmt" + "server/modules/system/entity" + + "github.com/gin-gonic/gin" +) + +// GetLoginUser 从上下文获取当前登录用户 +// 在Controller中使用: user := common.GetLoginUser(c) +func GetLoginUser(c *gin.Context) *entity.LoginUser { + value, exists := c.Get(ContextUserKey) + if !exists { + return nil + } + if user, ok := value.(*entity.LoginUser); ok { + return user + } + return nil +} + +// GetLoginUserID 获取当前登录用户ID +func GetLoginUserID(c *gin.Context) string { + user := GetLoginUser(c) + if user != nil { + return user.ID + } + return "" +} + +// GetLoginUsername 获取当前登录用户名 +func GetLoginUsername(c *gin.Context) string { + user := GetLoginUser(c) + if user != nil { + return user.Username + } + return "" +} + +// GetPage 从上下文获取页码 +func GetPage(c *gin.Context) int { + pageStr := c.DefaultQuery("page", "1") + var page int + fmt.Sscanf(pageStr, "%d", &page) + if page < 1 { + page = 1 + } + return page +} + +// GetSize 从上下文获取每页大小 +func GetSize(c *gin.Context) int { + sizeStr := c.DefaultQuery("size", "10") + var size int + fmt.Sscanf(sizeStr, "%d", &size) + if size < 1 { + size = 10 + } + return size +} diff --git a/server/common/id_utils.go b/server/common/id_utils.go new file mode 100644 index 0000000..1e2b165 --- /dev/null +++ b/server/common/id_utils.go @@ -0,0 +1,84 @@ +package common + +import ( + "errors" + "strconv" + "sync" + + "server/common/snowflake" +) + +var ( + defaultSnowflake *snowflake.Snowflake + once sync.Once +) + +// InitGenerator 初始化雪花算法生成器 +// workerId: 工作机器ID (0 ~ 31) +// datacenterId: 数据中心ID (0 ~ 31) +// 如果不需要区分数据中心,可以将 datacenterId 设置为 0 +func InitGenerator(workerId, datacenterId int64) error { + // 先校验参数 + if workerId < 0 || workerId > 31 { + return errors.New("workerId must be between 0 and 31") + } + if datacenterId < 0 || datacenterId > 31 { + return errors.New("datacenterId must be between 0 and 31") + } + + // 执行初始化 + once.Do(func() { + var err error + defaultSnowflake, err = snowflake.NewSnowflake(workerId, datacenterId) + if err != nil { + panic("InitGenerator failed: " + err.Error()) + } + }) + + return nil +} + +// InitGeneratorWithWorkerID 仅使用 workerId 初始化(兼容旧版本) +// datacenterId 默认为 0 +func InitGeneratorWithWorkerID(workerID int64) error { + return InitGenerator(workerID, 0) +} + +// getInstance 获取单例实例 +func getInstance() *snowflake.Snowflake { + once.Do(func() { + // 默认值:workerId=1, datacenterId=0 + var err error + defaultSnowflake, err = snowflake.NewSnowflake(1, 0) + if err != nil { + // 默认参数如果还失败,直接 panic + panic("Snowflake getInstance failed: " + err.Error()) + } + }) + + // 防御性编程:如果 once.Do 已经执行过(例如被 InitGenerator 执行了), + // 但因为 panic 或其他异常导致 defaultSnowflake 仍为 nil,这里进行补救 + if defaultSnowflake == nil { + // 此时忽略 sync.Once,直接强制初始化,防止 nil pointer crash + // 使用默认安全值 (1, 0) + defaultSnowflake, _ = snowflake.NewSnowflake(1, 0) + } + + return defaultSnowflake +} + +// GenerateLongID 生成 64 位整型 ID +func GenerateLongID() int64 { + id, err := getInstance().NextId() + if err != nil { + // 极端情况:时间回拨 + // 返回 0 或使用时间戳作为备用方案 + panic("GenerateLongID failed: " + err.Error()) + } + return id +} + +// GenerateStringID 生成字符串 ID +func GenerateStringID() string { + return strconv.FormatInt(GenerateLongID(), 10) +} diff --git a/server/common/logger.go b/server/common/logger.go new file mode 100644 index 0000000..99567ec --- /dev/null +++ b/server/common/logger.go @@ -0,0 +1,210 @@ +// Package common 日志工具 +package common + +import ( + "fmt" + "io" + "os" + "path/filepath" + "sync" + "time" + + "server/config" +) + +// 日志级别 +const ( + LevelDebug = iota + LevelInfo + LevelWarn + LevelError +) + +var levelNames = map[int]string{ + LevelDebug: "DEBUG", + LevelInfo: "INFO", + LevelWarn: "WARN", + LevelError: "ERROR", +} + +var levelValues = map[string]int{ + "debug": LevelDebug, + "info": LevelInfo, + "warn": LevelWarn, + "error": LevelError, +} + +// Logger 日志记录器 +type Logger struct { + level int + file *os.File + htmlWriter *htmlLogWriter + mu sync.Mutex + console bool +} + +var ( + defaultLogger *Logger + startCount int + onceInit sync.Once +) + +// InitLogger 初始化日志 +func InitLogger() { + once.Do(func() { + cfg := config.AppConfig.Log + level := levelValues[cfg.Level] + + // 创建日志目录 + if err := os.MkdirAll(cfg.Dir, 0755); err != nil { + fmt.Println("创建日志目录失败:", err) + return + } + + // 计算启动次数 + startCount = getStartCount(cfg.Dir) + + // 创建日志文件 + filename := fmt.Sprintf("%s-%d.html", time.Now().Format("2006-01-02"), startCount) + logFilepath := filepath.Join(cfg.Dir, filename) + + file, err := os.OpenFile(logFilepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + fmt.Println("创建日志文件失败:", err) + return + } + + htmlWriter := newHtmlLogWriter(file) + + defaultLogger = &Logger{ + level: level, + file: file, + htmlWriter: htmlWriter, + console: cfg.Console, + } + + fmt.Printf("日志文件: %s\n", logFilepath) + }) +} + +// getStartCount 获取今日启动次数 +func getStartCount(dir string) int { + today := time.Now().Format("2006-01-02") + pattern := filepath.Join(dir, today+"*.html") + matches, _ := filepath.Glob(pattern) + return len(matches) + 1 +} + +// CloseLogger 关闭日志 +func CloseLogger() { + if defaultLogger != nil && defaultLogger.file != nil { + defaultLogger.htmlWriter.writeFooter() + defaultLogger.file.Close() + } +} + +func (l *Logger) log(level int, format string, args ...interface{}) { + if l == nil || level < l.level { + return + } + + l.mu.Lock() + defer l.mu.Unlock() + + msg := fmt.Sprintf(format, args...) + timestamp := time.Now().Format("2006-01-02 15:04:05.000") + levelName := levelNames[level] + + // 写入HTML文件 + l.htmlWriter.writeLog(timestamp, levelName, msg) + + // 输出到控制台 + if l.console { + fmt.Printf("[%s] [%s] %s\n", timestamp, levelName, msg) + } +} + +// LogDebug 调试日志 +func LogDebug(format string, args ...interface{}) { + if defaultLogger != nil { + defaultLogger.log(LevelDebug, format, args...) + } +} + +// LogInfo 信息日志 +func LogInfo(format string, args ...interface{}) { + if defaultLogger != nil { + defaultLogger.log(LevelInfo, format, args...) + } +} + +// LogWarn 警告日志 +func LogWarn(format string, args ...interface{}) { + if defaultLogger != nil { + defaultLogger.log(LevelWarn, format, args...) + } +} + +// LogError 错误日志 +func LogError(format string, args ...interface{}) { + if defaultLogger != nil { + defaultLogger.log(LevelError, format, args...) + } +} + +// 简短别名 +var ( + Debug = LogDebug + Info = LogInfo + Warn = LogWarn +) + +// htmlLogWriter HTML日志写入器 +type htmlLogWriter struct { + writer io.Writer + initialized bool +} + +func newHtmlLogWriter(w io.Writer) *htmlLogWriter { + hw := &htmlLogWriter{writer: w} + hw.writeHeader() + return hw +} + +func (h *htmlLogWriter) writeHeader() { + html := ` + + + +应用日志 + + + +

应用日志 - ` + time.Now().Format("2006-01-02") + `

+
+` + h.writer.Write([]byte(html)) + h.initialized = true +} + +func (h *htmlLogWriter) writeLog(timestamp, level, msg string) { + html := fmt.Sprintf(`
[%s] [%s] %s
+`, level, timestamp, level, msg) + h.writer.Write([]byte(html)) +} + +func (h *htmlLogWriter) writeFooter() { + html := `
+ +` + h.writer.Write([]byte(html)) +} diff --git a/server/common/password.go b/server/common/password.go new file mode 100644 index 0000000..4cb7082 --- /dev/null +++ b/server/common/password.go @@ -0,0 +1,190 @@ +package common + +import ( + "bytes" + "crypto/cipher" + "crypto/des" + "crypto/md5" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" +) + +// PasswordUtil 对应 Java 类的常量 +const ( + // DefaultSalt 对应 Java 中的 SALT = "63293188" + DefaultSalt = "63293188" + // IterationCount 对应 Java 中的 ITERATIONCOUNT = 1000 + IterationCount = 1000 +) + +// GetSalt 生成 8 字节的随机盐 +func GetSalt() ([]byte, error) { + salt := make([]byte, 8) + _, err := rand.Read(salt) + if err != nil { + return nil, err + } + return salt, nil +} + +// GetStaticSalt 获取静态盐 +func GetStaticSalt() []byte { + return []byte(DefaultSalt) +} + +// Encrypt 加密 +// plaintext: 明文 +// password: 密码 +// salt: 盐 (传入 string, 对应 Java 的 salt 参数) +func Encrypt(plaintext, password, salt string) (string, error) { + // 1. 生成 Key 和 IV + key, iv := deriveKeyAndIV(password, salt, IterationCount) + + // 2. 创建 DES Cipher + block, err := des.NewCipher(key) + if err != nil { + return "", err + } + + // 3. 处理数据 (UTF-8 转 bytes 并填充) + data := []byte(plaintext) + data = pkcs5Padding(data, block.BlockSize()) + + // 4. 加密 (CBC 模式) + blockMode := cipher.NewCBCEncrypter(block, iv) + crypted := make([]byte, len(data)) + blockMode.CryptBlocks(crypted, data) + + // 5. 转十六进制字符串 (对应 Java 的 bytesToHexString) + // Java 的 Integer.toHexString 产生的是小写,这里保持一致, + // 但通常十六进制可以互通。 + return hex.EncodeToString(crypted), nil +} + +// Decrypt 解密 +// ciphertext: 密文 (Hex 字符串) +// password: 密码 +// salt: 盐 +func Decrypt(ciphertext, password, salt string) (string, error) { + // 1. 生成 Key 和 IV + key, iv := deriveKeyAndIV(password, salt, IterationCount) + + // 2. Hex 字符串转 bytes + decodedBytes, err := hex.DecodeString(ciphertext) + if err != nil { + return "", err + } + + // 3. 创建 DES Cipher + block, err := des.NewCipher(key) + if err != nil { + return "", err + } + + // 4. 解密 (CBC 模式) + blockMode := cipher.NewCBCDecrypter(block, iv) + // 密文长度检查 + if len(decodedBytes)%block.BlockSize() != 0 { + return "", errors.New("ciphertext is not a multiple of the block size") + } + origData := make([]byte, len(decodedBytes)) + blockMode.CryptBlocks(origData, decodedBytes) + + // 5. 去除填充 + origData, err = pkcs5UnPadding(origData) + if err != nil { + return "", err + } + + return string(origData), nil +} + +// deriveKeyAndIV 模拟 Java PBEWithMD5AndDES 的密钥派生逻辑 +// 逻辑:MD5(password || salt) -> 迭代 -> 结果前8字节是Key,后8字节是IV +func deriveKeyAndIV(password, salt string, iterations int) ([]byte, []byte) { + // Java PBEKeySpec 处理 password 为 char[],通常转 byte 时依赖编码, + // 此处假设使用 UTF-8 兼容(Java 默认行为通常如此,或者 ASCII) + passBytes := []byte(password) + saltBytes := []byte(salt) + + // 第一次迭代: Hash(pass + salt) + hash := md5.New() + hash.Write(passBytes) + hash.Write(saltBytes) + derived := hash.Sum(nil) + + // 后续迭代: Hash(prev_hash) + for i := 1; i < iterations; i++ { + hash.Reset() + hash.Write(derived) + derived = hash.Sum(nil) + } + + // MD5 结果是 16 字节 + // DES Key 需要 8 字节,IV 需要 8 字节 + // 正好平分 + key := derived[:8] + iv := derived[8:] + + return key, iv +} + +// pkcs5Padding 填充 +func pkcs5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +// pkcs5UnPadding 去除填充 +func pkcs5UnPadding(origData []byte) ([]byte, error) { + length := len(origData) + if length == 0 { + return nil, errors.New("decryption error: output length is zero") + } + unpadding := int(origData[length-1]) + if length < unpadding { + return nil, errors.New("decryption error: invalid padding") + } + return origData[:(length - unpadding)], nil +} + +// --- 测试主函数 --- +func main() { + // 测试数据 + plaintext := "admin" + password := "Wang5322570" + //salt := DefaultSalt + salt := "RCGTeGiH" + + fmt.Println("原文:", plaintext) + fmt.Println("密码:", password) + fmt.Println("盐值:", salt) + + // 加密 + encrypted, err := Encrypt(plaintext, password, salt) + if err != nil { + fmt.Println("加密失败:", err) + return + } + // Java代码可能输出小写或手动处理,这里使用 hex.EncodeToString (小写) + // 如果需要大写可以使用 strings.ToUpper(encrypted) + fmt.Println("加密密文 (Hex):", encrypted) + + // 解密 + decrypted, err := Decrypt(encrypted, password, salt) + if err != nil { + fmt.Println("解密失败:", err) + return + } + fmt.Println("解密原文:", decrypted) + + // 验证一致性 + if plaintext == decrypted { + fmt.Println(">> 测试通过!Go版本与逻辑一致。") + } else { + fmt.Println(">> 测试失败!") + } +} diff --git a/server/common/response.go b/server/common/response.go new file mode 100644 index 0000000..7dca734 --- /dev/null +++ b/server/common/response.go @@ -0,0 +1,35 @@ +// Package common 公共包 +package common + +import "github.com/gin-gonic/gin" + +// Response 统一响应结构 +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +// PageResponse 分页响应 +type PageResponse struct { + List interface{} `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + Size int `json:"size"` +} + +func Success(c *gin.Context, data interface{}) { + c.JSON(200, Response{Code: 200, Message: "success", Data: data}) +} + +func SuccessPage(c *gin.Context, list interface{}, total int64, page, size int) { + c.JSON(200, Response{ + Code: 200, + Message: "success", + Data: PageResponse{List: list, Total: total, Page: page, Size: size}, + }) +} + +func Error(c *gin.Context, code int, message string) { + c.JSON(code, Response{Code: code, Message: message, Data: nil}) +} diff --git a/server/common/score_calculator.go b/server/common/score_calculator.go new file mode 100644 index 0000000..5574b9b --- /dev/null +++ b/server/common/score_calculator.go @@ -0,0 +1,197 @@ +package common + +import ( + "math" + "regexp" + "server/modules/user/vo" + "server/modules/yx/dto" + "strconv" + "strings" +) + +// ScoreCalculator 分数计算工具函数集合 +// 移植自 Java 版本的 ScoreUtil.java + +var ( + BigDecimal0 = 0.0 + BigDecimal05 = 0.5 + BigDecimal075 = 0.75 + BigDecimal0133 = 0.133 + BigDecimal0667 = 0.667 + BigDecimal004 = 0.04 + BigDecimal0467 = 0.467 + BigDecimal0067 = 0.067 + BigDecimal0333 = 0.333 + BigDecimal0093 = 0.093 + BigDecimal02 = 0.2 + BigDecimal07 = 0.7 + BigDecimal03 = 0.3 + BigDecimal04 = 0.4 + BigDecimal06 = 0.6 + BigDecimal1 = 1.0 + BigDecimal100 = 100.0 + BigDecimal150 = 150.0 + BigDecimal95x = 95.0 + BigDecimal85x = 85.0 + BigDecimal7p5 = 7.5 + BigDecimal5 = 5.0 +) + +// ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability 计算历年录取分差 +func ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability(majorType string, + rulesEnrollProbability, probabilityOperator string, + HistoryMajorEnrollMap map[string]dto.YxHistoryMajorEnrollDTO) map[string]any { + + if len(HistoryMajorEnrollMap) == 0 { + return map[string]any{"scoreDifference": 0.0} + } + + sum := 0.0 + validYearCount := 0 + + for _, enrollData := range HistoryMajorEnrollMap { + admissionLine := enrollData.AdmissionLine + controlLine := enrollData.ControlLine + if admissionLine <= 0 || controlLine <= 0 { + continue + } + // 计算分差 + currentDiff := admissionLine - controlLine + + // currentDiff := enrollData.ScoreLineDifference + + // 特殊逻辑:高职高专(非体育类)需计算分差率(分差/省控线) + // Java: boolean isVocationalCollege = "高职高专".equals(enrollData.getBatch()); + // boolean isSportsMajor = "体育类".equals(enrollData.getMajorType()); + if "体育类" == majorType { + if "2024" == enrollData.Year && "专过文排" != enrollData.EnrollmentCode && "文过专排" != enrollData.EnrollmentCode { + currentDiff = currentDiff * Number7p5 + } else if "2024" == enrollData.Year && "文过专排" == enrollData.EnrollmentCode { // Placeholder + currentDiff = currentDiff * Number5 + } else if "2023" == enrollData.Year { + continue + } else if rulesEnrollProbability == enrollData.RulesEnrollProbability { // Need field + sum += currentDiff + validYearCount++ + continue + } + } else { + // 非高职高专 or 体育类:检查录取方式是否一致 + if probabilityOperator != enrollData.ProbabilityOperator { + continue + } + } + + sum += currentDiff + validYearCount++ + } + + averageDiff := 0.0 + if validYearCount > 0 { + averageDiff = sum / float64(validYearCount) + } + + return map[string]any{"scoreDifference": averageDiff} +} + +// ConvertIntoScore 计算折合分 +func ConvertIntoScore(rulesEnrollProbability string, culturalScore, professionalScore float64, operator string) float64 { + score := 0.0 + if operator != "" { + // operator: 例如: "文*0.5+专*0.5" + parts := strings.Split(operator, "+") + for _, part := range parts { + subParts := strings.Split(part, "*") + if len(subParts) == 2 { + ratio, _ := strconv.ParseFloat(subParts[1], 64) + if strings.Contains(subParts[0], "文") { + score += culturalScore * ratio + } else { + score += professionalScore * ratio + } + } + } + } + + return round(score, 4) +} + +// CrossingControlLine 判断是否过省控线 +func CrossingControlLine(rulesEnrollProbability string, culturalScore, professionalScore, culturalControlLine, specialControlLine float64) bool { + if rulesEnrollProbability == CulturalControlLineGuo { + return culturalScore >= culturalControlLine + } else if rulesEnrollProbability == SpecialControlLineGuo { + return professionalScore >= specialControlLine + } else if rulesEnrollProbability == CulturalControlLineGuoMain { + return culturalScore >= specialControlLine + } + return culturalScore >= culturalControlLine && professionalScore >= specialControlLine +} + +// CommonCheckEnrollProbability 计算录取率 +func CommonCheckEnrollProbability(nowYearDiff, historyThreeYearDiff float64) float64 { + enrollProbability := 0.0 + if nowYearDiff == 0 && historyThreeYearDiff > 0 { + enrollProbability = 75.0 / historyThreeYearDiff + } else if nowYearDiff == 0 && historyThreeYearDiff <= 0 { + enrollProbability = 50.0 + } else if nowYearDiff > 0 && historyThreeYearDiff <= 0 { + enrollProbability = nowYearDiff * 100 * 0.75 + } else if nowYearDiff < 0 && historyThreeYearDiff <= 0 { + enrollProbability = 0.0 + } else { + // (当前年分差/去年分差)*0.75 * 100 + if historyThreeYearDiff != 0 { + enrollProbability = (nowYearDiff / historyThreeYearDiff) * 100 * 0.75 + } + } + return enrollProbability +} + +// CommonCheckEnrollProbabilityBeilv 录取率倍率调整 +func CommonCheckEnrollProbabilityBeilv(enrollProbability float64) float64 { + if enrollProbability > 150 { + return 95.0 + } else if enrollProbability > 100 { + return 85.0 + } else if enrollProbability <= 0 { + return 0.0 + } + return enrollProbability +} + +// OtherScoreJudge 其他录取要求 +func OtherScoreJudge(professionalScore float64, userScoreVO vo.UserScoreVO, schoolMajorDTO dto.SchoolMajorDTO) bool { + // 简单实现,参考 Java 逻辑 + if schoolMajorDTO.EnglishScoreLimitation > 0 && userScoreVO.EnglishScore < schoolMajorDTO.EnglishScoreLimitation { + return false + } + if schoolMajorDTO.CulturalScoreLimitation > 0 && userScoreVO.CulturalScore < schoolMajorDTO.CulturalScoreLimitation { + return false + } + // 专业分限制等... + return true +} + +// HasComputeEnrollProbabilityPermissions 判断是否有权限计算 +func HasComputeEnrollProbabilityPermissions(nowBatch, majorBatch string) bool { + return "" != nowBatch && "" != majorBatch +} + +// round 四舍五入 +func round(val float64, precision int) float64 { + ratio := math.Pow(10, float64(precision)) + return math.Round(val*ratio) / ratio +} + +// ReplaceLastZeroChar 格式化操作符字符串 (Java: replaceLastZeroChar) +func ReplaceLastZeroChar(input string) string { + if input == "" { + return "" + } + re := regexp.MustCompile(`(\d+\.\d*?)0+(\D|$)`) + input = re.ReplaceAllString(input, "$1$2") + input = strings.ReplaceAll(input, "1.", "1") + // 简化处理 + return input +} \ No newline at end of file diff --git a/server/common/snowflake/snowflake.go b/server/common/snowflake/snowflake.go new file mode 100644 index 0000000..91df396 --- /dev/null +++ b/server/common/snowflake/snowflake.go @@ -0,0 +1,122 @@ +package snowflake + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// 定义常量 +const ( + // 位数分配 + sequenceBits = 12 // 序列号占用的位数 + workerIdBits = 5 // 工作机器ID占用的位数 + datacenterIdBits = 5 // 数据中心ID占用的位数 + + // 最大值 + maxSequence = -1 ^ (-1 << sequenceBits) // 4095 + maxWorkerId = -1 ^ (-1 << workerIdBits) // 31 + maxDatacenterId = -1 ^ (-1 << datacenterIdBits) // 31 + + // 位移偏移量 + workerIdShift = sequenceBits // 12 + datacenterIdShift = sequenceBits + workerIdBits // 12 + 5 = 17 + timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits // 12 + 5 + 5 = 22 +) + +// 起始时间戳 (纪元),可以使用程序上线的时间,这里设置为 2020-01-01 00:00:00 UTC +var epoch int64 = 1577836800000 + +// Snowflake 结构体 +type Snowflake struct { + mu sync.Mutex // 互斥锁,保证并发安全 + lastTime int64 // 上次生成ID的时间戳 + workerId int64 // 工作机器ID + datacenterId int64 // 数据中心ID + sequence int64 // 当前毫秒内的序列号 +} + +// NewSnowflake 初始化一个 Snowflake 实例 +// workerId: 工作机器ID (0 ~ 31) +// datacenterId: 数据中心ID (0 ~ 31) +func NewSnowflake(workerId, datacenterId int64) (*Snowflake, error) { + if workerId < 0 || workerId > maxWorkerId { + return nil, errors.New(fmt.Sprintf("worker Id can't be greater than %d or less than 0", maxWorkerId)) + } + if datacenterId < 0 || datacenterId > maxDatacenterId { + return nil, errors.New(fmt.Sprintf("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)) + } + + return &Snowflake{ + lastTime: 0, + workerId: workerId, + datacenterId: datacenterId, + sequence: 0, + }, nil +} + +// NextId 生成下一个 ID +func (s *Snowflake) NextId() (int64, error) { + s.mu.Lock() + defer s.mu.Unlock() + + // 获取当前时间戳(毫秒) + now := time.Now().UnixMilli() + + // 如果当前时间小于上次生成ID的时间,说明时钟回拨,抛出异常 + if now < s.lastTime { + return 0, errors.New(fmt.Sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", s.lastTime-now)) + } + + // 如果是同一毫秒内 + if now == s.lastTime { + // 序列号自增 + s.sequence = (s.sequence + 1) & maxSequence + // 如果序列号溢出(超过4095),则等待下一毫秒 + if s.sequence == 0 { + now = s.waitNextMillis(now) + } + } else { + // 不同毫秒,序列号重置为0 + s.sequence = 0 + } + + // 更新最后时间戳 + s.lastTime = now + + // 组装 ID + // (当前时间 - 起始时间) << 时间戳位移 | 数据中心ID << 数据中心位移 | 工作ID << 工作位移 | 序列号 + id := ((now - epoch) << timestampLeftShift) | + (s.datacenterId << datacenterIdShift) | + (s.workerId << workerIdShift) | + s.sequence + + return id, nil +} + +// waitNextMillis 阻塞等待下一毫秒 +func (s *Snowflake) waitNextMillis(lastTime int64) int64 { + now := time.Now().UnixMilli() + for now <= lastTime { + now = time.Now().UnixMilli() + } + return now +} + +// ParseId 解析 ID,用于调试或查看 ID 组成部分 +func ParseId(id int64) map[string]interface{} { + timestamp := (id >> timestampLeftShift) + epoch + datacenterId := (id >> datacenterIdShift) & maxDatacenterId + workerId := (id >> workerIdShift) & maxWorkerId + sequence := id & maxSequence + + return map[string]interface{}{ + "id": id, + "timestamp": timestamp, + "time_str": time.UnixMilli(timestamp).Format("2006-01-02 15:04:05.000"), + "datacenterId": datacenterId, + "workerId": workerId, + "sequence": sequence, + } +} diff --git a/server/common/snowflake/snowflake_test.go b/server/common/snowflake/snowflake_test.go new file mode 100644 index 0000000..8c61648 --- /dev/null +++ b/server/common/snowflake/snowflake_test.go @@ -0,0 +1,37 @@ +package snowflake // 注意:这里必须是 package snowflake,不能是 main + +import ( + "fmt" + "testing" +) + +// 这是一个测试函数,用于验证功能 +func TestGenerateID(t *testing.T) { + // 1. 初始化生成器 + sf, err := NewSnowflake(1, 1) + if err != nil { + t.Fatalf("初始化失败: %v", err) + } + + fmt.Println("=== 开始生成 ID ===") + + // 2. 生成几个 ID + for i := 0; i < 5; i++ { + id, err := sf.NextId() + if err != nil { + t.Errorf("生成 ID 失败: %v", err) + } else { + fmt.Printf("生成 ID: %d\n", id) + } + } + + // 3. 解析 ID 查看详情 + id, _ := sf.NextId() + info := ParseId(id) + fmt.Printf("\nID 详情解析:\n") + fmt.Printf("ID: %d\n", info["id"]) + fmt.Printf("时间: %s\n", info["time_str"]) + fmt.Printf("数据中心: %d\n", info["datacenterId"]) + fmt.Printf("工作机器: %d\n", info["workerId"]) + fmt.Printf("序列号: %d\n", info["sequence"]) +} diff --git a/server/common/sql_utils.go b/server/common/sql_utils.go new file mode 100644 index 0000000..1fa9924 --- /dev/null +++ b/server/common/sql_utils.go @@ -0,0 +1,14 @@ +package common + +import "regexp" + +// 验证表名格式的辅助函数 +func IsValidTableName(tableName string) bool { + if tableName == "" { + return false + } + + // 表名只能包含字母、数字、下划线和点号,且长度合理 + matched, err := regexp.MatchString(`^[a-zA-Z_][a-zA-Z0-9_.]{0,100}$`, tableName) + return err == nil && matched +} diff --git a/server/config/config.dev.yaml b/server/config/config.dev.yaml new file mode 100644 index 0000000..5cc4ef9 --- /dev/null +++ b/server/config/config.dev.yaml @@ -0,0 +1,55 @@ +server: + port: 8081 + worker_id: 1 # 工作机器ID (0-31),单实例使用1 + datacenter_id: 0 # 数据中心ID (0-31),默认0 # 雪花算法机器ID (0-1023),分布式环境下不同实例需设置不同值 + +log: + level: debug + dir: logs + console: true + +security: + enable: false + header_key: X-App-Sign + secret_key: yts@2025#secure + +rate_limit: + enable: true + default: + interval: 2 + max_requests: 3 + rules: + /api/user/auth/login: + interval: 5 + max_requests: 1 + /api/yx-school-majors: + interval: 1 + max_requests: 5 + /api/user/score/save-score: + interval: 1 + max_requests: 1 + /user/major/list: + interval: 1 + max_requests: 5 + +swagger: + user: admin + password: password + +database: + driver: mysql + host: 10.13.13.1 + port: 3306 + database: yitisheng + username: root + password: "Db$7Hn#4Jm9Pq2!Xz" + charset: utf8mb4 + max_idle_conns: 20 + max_open_conns: 100 + conn_max_lifetime: 1 + log_mode: true + +redis: + addr: 10.13.13.1:56379 + password: "Rd@5Wk8#Nv3Yt6$Bm" + db: 1 diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 0000000..3cef927 --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,143 @@ +// Package config 应用配置 +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// AppConfig 应用配置 +var AppConfig = &appConfig{} + +type appConfig struct { + Log LogConfig `yaml:"log"` + Server ServerConfig `yaml:"server"` + Security SecurityConfig `yaml:"security"` + RateLimit RateLimitConfig `yaml:"rate_limit"` + Swagger SwaggerConfig `yaml:"swagger"` + Database DatabaseConfig `yaml:"database"` + Redis RedisConfig `yaml:"redis"` +} + +// LogConfig 日志配置 +type LogConfig struct { + Level string `yaml:"level"` // 日志级别 + Dir string `yaml:"dir"` // 日志目录 + Console bool `yaml:"console"` // 是否输出到控制台 +} + +// ServerConfig 服务配置 +type ServerConfig struct { + Port int `yaml:"port"` // 服务端口 + WorkerID int `yaml:"worker_id"` // 工作机器ID (0-31),用于雪花算法 + DatacenterID int `yaml:"datacenter_id"` // 数据中心ID (0-31),用于雪花算法 +} + +// SecurityConfig 安全配置 +type SecurityConfig struct { + Enable bool `yaml:"enable"` // 是否启用 + HeaderKey string `yaml:"header_key"` // 请求头字段名 + SecretKey string `yaml:"secret_key"` // 签名密钥 +} + +// RateLimitConfig 限流配置 +type RateLimitConfig struct { + Enable bool `yaml:"enable"` // 是否启用 + Default RateLimitRule `yaml:"default"` // 默认规则 + Rules map[string]RateLimitRule `yaml:"rules"` // 特定路径规则 +} + +// RateLimitRule 限流规则 +type RateLimitRule struct { + Interval int `yaml:"interval"` // 时间间隔(秒) + MaxRequests int `yaml:"max_requests"` // 最大请求次数 +} + +// SwaggerConfig Swagger文档认证配置 +type SwaggerConfig struct { + User string `yaml:"user"` + Password string `yaml:"password"` +} + +// DatabaseConfig 数据库配置 +type DatabaseConfig struct { + Driver string `yaml:"driver"` + Host string `yaml:"host"` + Port int `yaml:"port"` + Database string `yaml:"database"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Charset string `yaml:"charset"` + MaxIdleConns int `yaml:"max_idle_conns"` + MaxOpenConns int `yaml:"max_open_conns"` + ConnMaxLifetime int `yaml:"conn_max_lifetime"` // 小时 + LogMode bool `yaml:"log_mode"` // 是否开启SQL日志 +} + +// RedisConfig Redis配置 +type RedisConfig struct { + Addr string `yaml:"addr"` + Password string `yaml:"password"` + DB int `yaml:"db"` +} + +// LoadConfig 加载配置 +func LoadConfig() { + var configFile string + + // 1. 优先检查命令行参数 (简单解析) + // 格式: ./app -c config.prod.yaml 或 ./app -config config.prod.yaml + args := os.Args + for i, arg := range args { + if (arg == "-c" || arg == "-config") && i+1 < len(args) { + configFile = args[i+1] + break + } + } + + // 2. 如果命令行参数未指定,则根据环境变量查找 + if configFile == "" { + env := os.Getenv("GO_ENV") + if env == "" { + env = "dev" + } + + // 查找顺序: + // 1. 当前目录 config.{env}.yaml (方便部署时直接放在执行文件旁) + // 2. config/config.{env}.yaml (开发习惯) + // 3. ../config/config.{env}.yaml (测试环境习惯) + searchPaths := []string{ + fmt.Sprintf("config.%s.yaml", env), + fmt.Sprintf("config/config.%s.yaml", env), + fmt.Sprintf("../config/config.%s.yaml", env), + } + + for _, path := range searchPaths { + if _, err := os.Stat(path); err == nil { + configFile = path + break + } + } + + // 如果都没找到,默认回退到 config/config.{env}.yaml 以便报错信息准确 + if configFile == "" { + configFile = fmt.Sprintf("config/config.%s.yaml", env) + } + } + + fmt.Printf("正在加载配置文件: %s\n", configFile) + + data, err := os.ReadFile(configFile) + if err != nil { + fmt.Printf("读取配置文件失败: %v, 使用默认配置\n", err) + return + } + + err = yaml.Unmarshal(data, AppConfig) + if err != nil { + fmt.Printf("解析配置文件失败: %v\n", err) + panic(err) + } +} diff --git a/server/config/config.prod.yaml b/server/config/config.prod.yaml new file mode 100644 index 0000000..eecef66 --- /dev/null +++ b/server/config/config.prod.yaml @@ -0,0 +1,52 @@ +server: + port: 8081 + worker_id: 1 # 工作机器ID (0-31),多实例部署需配置不同值 + datacenter_id: 0 # 数据中心ID (0-31),多机房部署需配置不同值 # 雪花算法机器ID (0-1023),分布式环境下不同实例需设置不同值,多实例部署时需手动配置 + +log: + level: info + dir: logs + console: true + +security: + enable: true + header_key: X-App-Sign + secret_key: yts@2025#secure + +rate_limit: + enable: true + default: + interval: 2 + max_requests: 5 + rules: + /api/user/auth/login: + interval: 5 + max_requests: 1 + /api/yx-school-majors: + interval: 1 + max_requests: 5 + /api/user/score/save-score: + interval: 1 + max_requests: 2 + +swagger: + user: admin + password: password + +database: + driver: mysql + host: 127.0.0.1 + port: 3306 + database: yitisheng + username: root + password: "Db$7Hn#4Jm9Pq2!Xz" + charset: utf8mb4 + max_idle_conns: 50 + max_open_conns: 200 + conn_max_lifetime: 1 + log_mode: false + +redis: + addr: 127.0.0.1:56379 + password: "Rd@5Wk8#Nv3Yt6$Bm" + db: 1 diff --git a/server/config/config.test.yaml b/server/config/config.test.yaml new file mode 100644 index 0000000..169c6a9 --- /dev/null +++ b/server/config/config.test.yaml @@ -0,0 +1,52 @@ +server: + port: 8080 + worker_id: 1 # 工作机器ID (0-31),测试环境使用1 + datacenter_id: 0 # 数据中心ID (0-31),默认0 + +log: + level: debug + dir: logs + console: true + +security: + enable: true + header_key: X-App-Sign + secret_key: yts@2025#secure + +rate_limit: + enable: true + default: + interval: 2 + max_requests: 10 + rules: + /api/user/auth/login: + interval: 5 + max_requests: 1 + /api/yx-school-majors: + interval: 1 + max_requests: 10 + /api/user/score/save-score: + interval: 1 + max_requests: 5 + +swagger: + user: admin + password: password + +database: + driver: mysql + host: 127.0.0.1 + port: 3306 + database: yitisheng + username: root + password: "Db$7Hn#4Jm9Pq2!Xz" + charset: utf8mb4 + max_idle_conns: 20 + max_open_conns: 100 + conn_max_lifetime: 1 + log_mode: false + +redis: + addr: 127.0.0.1:56379 + password: "Rd@5Wk8#Nv3Yt6$Bm" + db: 1 diff --git a/server/config/database.go b/server/config/database.go new file mode 100644 index 0000000..6033ffd --- /dev/null +++ b/server/config/database.go @@ -0,0 +1,111 @@ +// Package config 配置包,负责应用程序的配置管理 +package config + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// DB 全局数据库连接实例 +var DB *gorm.DB + +// getLogWriter 获取日志输出目标 +func getLogWriter() io.Writer { + logConfig := AppConfig.Log + if logConfig.Dir == "" { + return os.Stdout + } + + if err := os.MkdirAll(logConfig.Dir, 0755); err != nil { + fmt.Printf("创建日志目录失败: %v\n", err) + return os.Stdout + } + + filename := fmt.Sprintf("sql-%s.log", time.Now().Format("2006-01-02")) + logPath := filepath.Join(logConfig.Dir, filename) + + file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + fmt.Printf("打开SQL日志文件失败: %v\n", err) + return os.Stdout + } + + if logConfig.Console { + return io.MultiWriter(file, os.Stdout) + } + return file +} + +// InitDB 初始化数据库连接 +func InitDB() { + dbConfig := AppConfig.Database + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Asia%%2FShanghai", + dbConfig.Username, + dbConfig.Password, + dbConfig.Host, + dbConfig.Port, + dbConfig.Database, + dbConfig.Charset, + ) + + var gormConfig *gorm.Config + if dbConfig.LogMode { + writer := getLogWriter() + newLogger := logger.New( + log.New(writer, "\r\n", log.LstdFlags), // io writer + logger.Config{ + SlowThreshold: time.Second, // Slow SQL threshold + LogLevel: logger.Info, // Log level + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger + ParameterizedQueries: false, // 包含参数在 SQL 日志中 + Colorful: false, // 写入文件时建议关闭彩色打印,否则会有乱码 + }, + ) + gormConfig = &gorm.Config{ + Logger: newLogger, + } + } else { + gormConfig = &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + } + } + + var err error + DB, err = gorm.Open(mysql.Open(dsn), gormConfig) + if err != nil { + log.Fatal("数据库连接失败:", err) + } + + // 获取底层 sql.DB 以配置连接池 + sqlDB, err := DB.DB() + if err != nil { + log.Fatal("获取数据库实例失败:", err) + } + + // 连接池配置 + sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns) + sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns) + sqlDB.SetConnMaxLifetime(time.Duration(dbConfig.ConnMaxLifetime) * time.Hour) + + fmt.Println("数据库连接成功") +} + +// CloseDB 关闭数据库连接 +// 在程序退出时调用,释放所有连接 +func CloseDB() { + if DB != nil { + sqlDB, err := DB.DB() + if err == nil { + sqlDB.Close() + fmt.Println("数据库连接已关闭") + } + } +} diff --git a/server/config/redis.go b/server/config/redis.go new file mode 100644 index 0000000..7cc3172 --- /dev/null +++ b/server/config/redis.go @@ -0,0 +1,43 @@ +// Package config Redis配置 +package config + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/redis/go-redis/v9" +) + +// RDB 全局Redis客户端 +var RDB *redis.Client + +// InitRedis 初始化Redis连接 +func InitRedis() { + redisConfig := AppConfig.Redis + RDB = redis.NewClient(&redis.Options{ + Addr: redisConfig.Addr, + Password: redisConfig.Password, + DB: redisConfig.DB, + PoolSize: 10, // 连接池大小 + }) + + // 测试连接 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, err := RDB.Ping(ctx).Result() + if err != nil { + log.Fatal("Redis连接失败:", err) + } + fmt.Println("Redis连接成功") +} + +// CloseRedis 关闭Redis连接 +func CloseRedis() { + if RDB != nil { + RDB.Close() + fmt.Println("Redis连接已关闭") + } +} diff --git a/server/docs/docs.go b/server/docs/docs.go new file mode 100644 index 0000000..7f524c6 --- /dev/null +++ b/server/docs/docs.go @@ -0,0 +1,2369 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/sys-users": { + "get": { + "tags": [ + "用户管理" + ], + "summary": "获取用户列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "用户管理" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys-users/{id}": { + "get": { + "tags": [ + "用户管理" + ], + "summary": "获取单个用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "用户管理" + ], + "summary": "更新用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "用户信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "用户管理" + ], + "summary": "删除用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "用户管理" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys-users/{id}/password": { + "put": { + "tags": [ + "用户管理" + ], + "summary": "修改密码", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "密码信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.UpdatePasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/info": { + "get": { + "tags": [ + "认证" + ], + "summary": "Sys获取当前登录用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "Sys用户登录", + "parameters": [ + { + "description": "登录信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server_modules_system_controller.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/logout": { + "post": { + "tags": [ + "认证" + ], + "summary": "Sys用户登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/info": { + "get": { + "tags": [ + "认证" + ], + "summary": "获取当前登录用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "登录信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server_modules_user_controller.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/logout": { + "post": { + "tags": [ + "认证" + ], + "summary": "用户登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/major/list": { + "get": { + "tags": [ + "用户专业" + ], + "summary": "获取当前用户可检索列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + }, + { + "type": "string", + "default": "\"\"", + "description": "批次(本科提前批/本科A段/本科B段/本科/高职高专)", + "name": "batch", + "in": "query" + }, + { + "type": "string", + "default": "\"\"", + "description": "录取概率类型(难录取/可冲击/较稳妥/可保底)", + "name": "probability", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的激活分数", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/list": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的成绩列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/save-score": { + "post": { + "tags": [ + "用户分数" + ], + "summary": "保存用户成绩", + "parameters": [ + { + "description": "成绩信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SaveScoreRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/{id}": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取指定 ID 的分数", + "parameters": [ + { + "type": "string", + "description": "成绩ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors": { + "get": { + "tags": [ + "计算专业" + ], + "summary": "获取计算专业列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "计算专业" + ], + "summary": "创建计算专业", + "parameters": [ + { + "description": "计算专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors/batch": { + "post": { + "tags": [ + "计算专业" + ], + "summary": "批量创建计算专业", + "parameters": [ + { + "description": "计算专业列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "计算专业" + ], + "summary": "批量删除计算专业", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors/{id}": { + "get": { + "tags": [ + "计算专业" + ], + "summary": "获取单个计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "计算专业" + ], + "summary": "更新计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "计算专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "计算专业" + ], + "summary": "删除计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "计算专业" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls": { + "get": { + "tags": [ + "历年招生" + ], + "summary": "获取历年招生列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "历年招生" + ], + "summary": "创建历年招生记录", + "parameters": [ + { + "description": "历年招生信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls/batch": { + "post": { + "tags": [ + "历年招生" + ], + "summary": "批量创建历年招生记录", + "parameters": [ + { + "description": "历年招生列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "历年招生" + ], + "summary": "批量删除历年招生记录", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls/{id}": { + "get": { + "tags": [ + "历年招生" + ], + "summary": "获取单个历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "历年招生" + ], + "summary": "更新历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "历年招生信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "历年招生" + ], + "summary": "删除历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "历年招生" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors": { + "get": { + "tags": [ + "院校专业" + ], + "summary": "获取院校专业列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "院校专业" + ], + "summary": "创建院校专业", + "parameters": [ + { + "description": "院校专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors/batch": { + "post": { + "tags": [ + "院校专业" + ], + "summary": "批量创建院校专业", + "parameters": [ + { + "description": "院校专业列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "院校专业" + ], + "summary": "批量删除院校专业", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors/{id}": { + "get": { + "tags": [ + "院校专业" + ], + "summary": "获取单个院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "院校专业" + ], + "summary": "更新院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "院校专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "院校专业" + ], + "summary": "删除院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "院校专业" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-user-scores": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取用户分数列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "用户分数" + ], + "summary": "创建用户分数", + "parameters": [ + { + "description": "用户分数信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxUserScore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-user-scores/{id}": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取单个用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "用户分数" + ], + "summary": "更新用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "用户分数信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxUserScore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "用户分数" + ], + "summary": "删除用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteer-records": { + "get": { + "tags": [ + "志愿明细" + ], + "summary": "获取志愿明细列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "志愿明细" + ], + "summary": "创建志愿明细", + "parameters": [ + { + "description": "志愿明细信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteerRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteer-records/{id}": { + "get": { + "tags": [ + "志愿明细" + ], + "summary": "获取单个志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "志愿明细" + ], + "summary": "更新志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "志愿明细信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteerRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "志愿明细" + ], + "summary": "删除志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteers": { + "get": { + "tags": [ + "志愿" + ], + "summary": "获取志愿列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "志愿" + ], + "summary": "创建志愿", + "parameters": [ + { + "description": "志愿信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteers/{id}": { + "get": { + "tags": [ + "志愿" + ], + "summary": "获取单个志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "志愿" + ], + "summary": "更新志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "志愿信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "志愿" + ], + "summary": "删除志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + } + }, + "definitions": { + "common.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + }, + "controller.UpdatePasswordRequest": { + "type": "object", + "required": [ + "newPassword", + "oldPassword" + ], + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + } + }, + "dto.SaveScoreRequest": { + "type": "object", + "required": [ + "ChineseScore", + "CognitioPolyclinic", + "CulturalScore", + "EnglishScore", + "ProfessionalCategory", + "ProfessionalCategoryChildren", + "ProfessionalCategoryChildrenScore", + "ProfessionalScore", + "Province", + "SubjectList" + ], + "properties": { + "ChineseScore": { + "type": "number" + }, + "CognitioPolyclinic": { + "type": "string" + }, + "CulturalScore": { + "type": "number" + }, + "EnglishScore": { + "type": "number" + }, + "ProfessionalCategory": { + "type": "string" + }, + "ProfessionalCategoryChildren": { + "type": "array", + "items": { + "type": "string" + } + }, + "ProfessionalCategoryChildrenScore": { + "type": "object", + "additionalProperties": { + "type": "number", + "format": "float64" + } + }, + "ProfessionalScore": { + "type": "number" + }, + "Province": { + "type": "string" + }, + "SubjectList": { + "type": "array", + "items": { + "type": "string" + } + }, + "createBy": { + "type": "string" + } + } + }, + "entity.SysUser": { + "type": "object", + "properties": { + "activitiSync": { + "description": "同步工作流引擎", + "type": "integer" + }, + "avatar": { + "description": "头像", + "type": "string" + }, + "birthday": { + "description": "生日", + "type": "string" + }, + "bpmStatus": { + "description": "流程状态", + "type": "string" + }, + "clientId": { + "description": "设备ID", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建时间", + "type": "string" + }, + "delFlag": { + "description": "删除状态(0-正常,1-已删除)", + "type": "integer" + }, + "departIds": { + "description": "负责部门", + "type": "string" + }, + "dyOpenId": { + "description": "抖音openId", + "type": "string" + }, + "email": { + "description": "电子邮件", + "type": "string" + }, + "id": { + "type": "string" + }, + "ip": { + "description": "注册时ip", + "type": "string" + }, + "loginTenantId": { + "description": "上次登录租户ID", + "type": "integer" + }, + "orgCode": { + "description": "机构编码", + "type": "string" + }, + "phone": { + "description": "电话", + "type": "string" + }, + "programType": { + "description": "所属程序", + "type": "string" + }, + "realname": { + "description": "真实姓名", + "type": "string" + }, + "sex": { + "description": "性别(0-未知,1-男,2-女)", + "type": "integer" + }, + "showLinediff": { + "description": "是否显示历年线差", + "type": "string" + }, + "status": { + "description": "状态(1-正常,2-冻结)", + "type": "integer" + }, + "telephone": { + "description": "座机号", + "type": "string" + }, + "thirdId": { + "description": "第三方登录唯一标识", + "type": "string" + }, + "thirdType": { + "description": "第三方类型", + "type": "string" + }, + "updateBy": { + "description": "更新人", + "type": "string" + }, + "updateTime": { + "description": "更新时间", + "type": "string" + }, + "userIdentity": { + "description": "身份(1普通成员 2上级)", + "type": "integer" + }, + "username": { + "description": "登录账号", + "type": "string" + }, + "workNo": { + "description": "工号", + "type": "string" + }, + "wxOpenId": { + "description": "微信openId", + "type": "string" + } + } + }, + "entity.YxCalculationMajor": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "enrollProbability": { + "type": "number" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kslx": { + "type": "string" + }, + "limitation": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "otherScoreLimitation": { + "type": "string" + }, + "planNum": { + "type": "integer" + }, + "privateProbabilityOperator": { + "type": "string" + }, + "privateRulesEnrollProbability": { + "type": "string" + }, + "privateStudentConvertedScore": { + "type": "number" + }, + "probabilityOperator": { + "type": "string" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "rulesEnrollProbabilitySx": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "scoreId": { + "type": "string" + }, + "state": { + "type": "string" + }, + "studentConvertedScore": { + "type": "number" + }, + "studentOldConvertedScore": { + "type": "number" + }, + "tuition": { + "type": "string" + } + } + }, + "entity.YxHistoryMajorEnroll": { + "type": "object", + "properties": { + "actualPitcherNum": { + "type": "integer" + }, + "admissionLine": { + "type": "number" + }, + "admissionNum": { + "type": "integer" + }, + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "checkMaster": { + "type": "string" + }, + "controlLine": { + "type": "number" + }, + "createBy": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "enrollNum": { + "type": "integer" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "institutionCode": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "oneVolunteerAdmissionNum": { + "type": "integer" + }, + "probabilityOperator": { + "type": "string" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "schoolName": { + "type": "string" + }, + "scoreLineDifference": { + "type": "number" + }, + "sysOrgCode": { + "type": "string" + }, + "tuition": { + "type": "string" + }, + "updateBy": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "year": { + "type": "string" + } + } + }, + "entity.YxSchoolMajor": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "checkMaster": { + "type": "string" + }, + "chineseScoreLimitation": { + "type": "number" + }, + "createBy": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "culturalControlLine": { + "type": "number" + }, + "culturalScoreLimitation": { + "type": "number" + }, + "detail": { + "type": "string" + }, + "englishScoreLimitation": { + "type": "number" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kslx": { + "type": "string" + }, + "limitation": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "planNum": { + "type": "integer" + }, + "privateProbabilityOperator": { + "type": "string" + }, + "privateRulesEnrollProbability": { + "type": "string" + }, + "probabilityOperator": { + "type": "string" + }, + "professionalScoreLimitation": { + "type": "number" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "rulesEnrollProbabilitySx": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "schoolName": { + "type": "string" + }, + "semester": { + "type": "string" + }, + "specialControlLine": { + "type": "number" + }, + "state": { + "type": "string" + }, + "tuition": { + "type": "string" + }, + "updateBy": { + "type": "string" + }, + "updateTime": { + "type": "string" + } + } + }, + "entity.YxUserScore": { + "type": "object", + "properties": { + "batch": { + "description": "录取批次", + "type": "string" + }, + "calculationTableName": { + "description": "记录结果表名", + "type": "string" + }, + "chineseScore": { + "description": "语文成绩", + "type": "number" + }, + "cognitioPolyclinic": { + "description": "文理分班(文科/理科)", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建时间", + "type": "string" + }, + "culturalScore": { + "description": "文化成绩分", + "type": "number" + }, + "educationalLevel": { + "description": "学历层次(1-本科,2-专科)", + "type": "string" + }, + "englishScore": { + "description": "英语成绩", + "type": "number" + }, + "fzby": { + "description": "服装表演", + "type": "number" + }, + "id": { + "type": "string" + }, + "jwtNum": { + "description": "较稳妥专业数量", + "type": "integer" + }, + "kbdNum": { + "description": "可保底专业数量", + "type": "integer" + }, + "kcjNum": { + "description": "可冲击专业数量", + "type": "integer" + }, + "nlqNum": { + "description": "难录取专业数量", + "type": "integer" + }, + "professionalCategory": { + "description": "专业类别(美术类/...)", + "type": "string" + }, + "professionalCategoryChildren": { + "description": "子级专业类别", + "type": "string" + }, + "professionalScore": { + "description": "专业成绩分", + "type": "number" + }, + "province": { + "description": "高考省份", + "type": "string" + }, + "ranking": { + "description": "位次", + "type": "integer" + }, + "state": { + "description": "状态(0-未使用,1-使用中)", + "type": "string" + }, + "subjects": { + "description": "选课", + "type": "string" + }, + "type": { + "description": "填报类型(1-普通类 2-艺术类)", + "type": "string" + }, + "updateBy": { + "description": "修改人", + "type": "string" + }, + "updateTime": { + "description": "修改时间", + "type": "string" + }, + "xjysby": { + "description": "戏剧影视表演", + "type": "number" + }, + "xjysdy": { + "description": "戏剧影视导演", + "type": "number" + }, + "yybyqy": { + "description": "音乐表演器乐", + "type": "number" + }, + "yybysy": { + "description": "音乐表演声乐", + "type": "number" + }, + "yyjy": { + "description": "音乐教育", + "type": "number" + } + } + }, + "entity.YxVolunteer": { + "type": "object", + "properties": { + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建日期", + "type": "string" + }, + "createType": { + "description": "生成类型(1.手动生成,2.智能生成)", + "type": "string" + }, + "id": { + "type": "string" + }, + "scoreId": { + "description": "使用成绩id", + "type": "string" + }, + "state": { + "description": "志愿单状态(0-否,1.正在使用,2-历史)", + "type": "string" + }, + "sysOrgCode": { + "description": "所属部门", + "type": "string" + }, + "updateBy": { + "description": "更新人", + "type": "string" + }, + "updateTime": { + "description": "更新日期", + "type": "string" + }, + "volunteerName": { + "description": "志愿单名称", + "type": "string" + } + } + }, + "entity.YxVolunteerRecord": { + "type": "object", + "properties": { + "batch": { + "description": "录取批次", + "type": "string" + }, + "calculationMajorId": { + "description": "专业折算id", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建日期", + "type": "string" + }, + "enrollProbability": { + "description": "录取概率", + "type": "number" + }, + "enrollmentCode": { + "description": "招生代码", + "type": "string" + }, + "fctj": { + "description": "服从调剂", + "type": "integer" + }, + "id": { + "type": "string" + }, + "indexs": { + "description": "志愿顺序", + "type": "integer" + }, + "majorCode": { + "description": "专业编码", + "type": "string" + }, + "schoolCode": { + "description": "学校编码", + "type": "string" + }, + "studentConvertedScore": { + "description": "折合分数", + "type": "number" + }, + "volunteerId": { + "description": "志愿单id", + "type": "string" + } + } + }, + "server_modules_system_controller.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "server_modules_user_controller.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "2.0", + Host: "localhost:8080", + BasePath: "/api", + Schemes: []string{}, + Title: "艺考招生管理系统 API", + Description: "提供用户认证、院校专业、历年招生、计算专业的管理接口", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/server/docs/swagger.json b/server/docs/swagger.json new file mode 100644 index 0000000..fd6031b --- /dev/null +++ b/server/docs/swagger.json @@ -0,0 +1,2345 @@ +{ + "swagger": "2.0", + "info": { + "description": "提供用户认证、院校专业、历年招生、计算专业的管理接口", + "title": "艺考招生管理系统 API", + "contact": {}, + "version": "2.0" + }, + "host": "localhost:8080", + "basePath": "/api", + "paths": { + "/sys-users": { + "get": { + "tags": [ + "用户管理" + ], + "summary": "获取用户列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "用户管理" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys-users/{id}": { + "get": { + "tags": [ + "用户管理" + ], + "summary": "获取单个用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "用户管理" + ], + "summary": "更新用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "用户信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "用户管理" + ], + "summary": "删除用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "用户管理" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys-users/{id}/password": { + "put": { + "tags": [ + "用户管理" + ], + "summary": "修改密码", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "密码信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.UpdatePasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/info": { + "get": { + "tags": [ + "认证" + ], + "summary": "Sys获取当前登录用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "Sys用户登录", + "parameters": [ + { + "description": "登录信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server_modules_system_controller.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/sys/auth/logout": { + "post": { + "tags": [ + "认证" + ], + "summary": "Sys用户登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/info": { + "get": { + "tags": [ + "认证" + ], + "summary": "获取当前登录用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "登录信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server_modules_user_controller.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/auth/logout": { + "post": { + "tags": [ + "认证" + ], + "summary": "用户登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/major/list": { + "get": { + "tags": [ + "用户专业" + ], + "summary": "获取当前用户可检索列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + }, + { + "type": "string", + "default": "\"\"", + "description": "批次(本科提前批/本科A段/本科B段/本科/高职高专)", + "name": "batch", + "in": "query" + }, + { + "type": "string", + "default": "\"\"", + "description": "录取概率类型(难录取/可冲击/较稳妥/可保底)", + "name": "probability", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的激活分数", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/list": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取当前用户的成绩列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/save-score": { + "post": { + "tags": [ + "用户分数" + ], + "summary": "保存用户成绩", + "parameters": [ + { + "description": "成绩信息", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SaveScoreRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/user/score/{id}": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取指定 ID 的分数", + "parameters": [ + { + "type": "string", + "description": "成绩ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors": { + "get": { + "tags": [ + "计算专业" + ], + "summary": "获取计算专业列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "计算专业" + ], + "summary": "创建计算专业", + "parameters": [ + { + "description": "计算专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors/batch": { + "post": { + "tags": [ + "计算专业" + ], + "summary": "批量创建计算专业", + "parameters": [ + { + "description": "计算专业列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "计算专业" + ], + "summary": "批量删除计算专业", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-calculation-majors/{id}": { + "get": { + "tags": [ + "计算专业" + ], + "summary": "获取单个计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "计算专业" + ], + "summary": "更新计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "计算专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxCalculationMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "计算专业" + ], + "summary": "删除计算专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "计算专业" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls": { + "get": { + "tags": [ + "历年招生" + ], + "summary": "获取历年招生列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "历年招生" + ], + "summary": "创建历年招生记录", + "parameters": [ + { + "description": "历年招生信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls/batch": { + "post": { + "tags": [ + "历年招生" + ], + "summary": "批量创建历年招生记录", + "parameters": [ + { + "description": "历年招生列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "历年招生" + ], + "summary": "批量删除历年招生记录", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-history-enrolls/{id}": { + "get": { + "tags": [ + "历年招生" + ], + "summary": "获取单个历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "历年招生" + ], + "summary": "更新历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "历年招生信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxHistoryMajorEnroll" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "历年招生" + ], + "summary": "删除历年招生记录", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "历年招生" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors": { + "get": { + "tags": [ + "院校专业" + ], + "summary": "获取院校专业列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "院校专业" + ], + "summary": "创建院校专业", + "parameters": [ + { + "description": "院校专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors/batch": { + "post": { + "tags": [ + "院校专业" + ], + "summary": "批量创建院校专业", + "parameters": [ + { + "description": "院校专业列表", + "name": "items", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "院校专业" + ], + "summary": "批量删除院校专业", + "parameters": [ + { + "description": "ID列表", + "name": "ids", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-school-majors/{id}": { + "get": { + "tags": [ + "院校专业" + ], + "summary": "获取单个院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "院校专业" + ], + "summary": "更新院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "院校专业信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxSchoolMajor" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "院校专业" + ], + "summary": "删除院校专业", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "patch": { + "tags": [ + "院校专业" + ], + "summary": "动态字段更新", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "要更新的字段", + "name": "fields", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-user-scores": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取用户分数列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "用户分数" + ], + "summary": "创建用户分数", + "parameters": [ + { + "description": "用户分数信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxUserScore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-user-scores/{id}": { + "get": { + "tags": [ + "用户分数" + ], + "summary": "获取单个用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "用户分数" + ], + "summary": "更新用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "用户分数信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxUserScore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "用户分数" + ], + "summary": "删除用户分数", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteer-records": { + "get": { + "tags": [ + "志愿明细" + ], + "summary": "获取志愿明细列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "志愿明细" + ], + "summary": "创建志愿明细", + "parameters": [ + { + "description": "志愿明细信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteerRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteer-records/{id}": { + "get": { + "tags": [ + "志愿明细" + ], + "summary": "获取单个志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "志愿明细" + ], + "summary": "更新志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "志愿明细信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteerRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "志愿明细" + ], + "summary": "删除志愿明细", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteers": { + "get": { + "tags": [ + "志愿" + ], + "summary": "获取志愿列表", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "每页数量", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "post": { + "tags": [ + "志愿" + ], + "summary": "创建志愿", + "parameters": [ + { + "description": "志愿信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + }, + "/yx-volunteers/{id}": { + "get": { + "tags": [ + "志愿" + ], + "summary": "获取单个志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "put": { + "tags": [ + "志愿" + ], + "summary": "更新志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "志愿信息", + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.YxVolunteer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + }, + "delete": { + "tags": [ + "志愿" + ], + "summary": "删除志愿", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/common.Response" + } + } + } + } + } + }, + "definitions": { + "common.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + }, + "controller.UpdatePasswordRequest": { + "type": "object", + "required": [ + "newPassword", + "oldPassword" + ], + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + } + }, + "dto.SaveScoreRequest": { + "type": "object", + "required": [ + "ChineseScore", + "CognitioPolyclinic", + "CulturalScore", + "EnglishScore", + "ProfessionalCategory", + "ProfessionalCategoryChildren", + "ProfessionalCategoryChildrenScore", + "ProfessionalScore", + "Province", + "SubjectList" + ], + "properties": { + "ChineseScore": { + "type": "number" + }, + "CognitioPolyclinic": { + "type": "string" + }, + "CulturalScore": { + "type": "number" + }, + "EnglishScore": { + "type": "number" + }, + "ProfessionalCategory": { + "type": "string" + }, + "ProfessionalCategoryChildren": { + "type": "array", + "items": { + "type": "string" + } + }, + "ProfessionalCategoryChildrenScore": { + "type": "object", + "additionalProperties": { + "type": "number", + "format": "float64" + } + }, + "ProfessionalScore": { + "type": "number" + }, + "Province": { + "type": "string" + }, + "SubjectList": { + "type": "array", + "items": { + "type": "string" + } + }, + "createBy": { + "type": "string" + } + } + }, + "entity.SysUser": { + "type": "object", + "properties": { + "activitiSync": { + "description": "同步工作流引擎", + "type": "integer" + }, + "avatar": { + "description": "头像", + "type": "string" + }, + "birthday": { + "description": "生日", + "type": "string" + }, + "bpmStatus": { + "description": "流程状态", + "type": "string" + }, + "clientId": { + "description": "设备ID", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建时间", + "type": "string" + }, + "delFlag": { + "description": "删除状态(0-正常,1-已删除)", + "type": "integer" + }, + "departIds": { + "description": "负责部门", + "type": "string" + }, + "dyOpenId": { + "description": "抖音openId", + "type": "string" + }, + "email": { + "description": "电子邮件", + "type": "string" + }, + "id": { + "type": "string" + }, + "ip": { + "description": "注册时ip", + "type": "string" + }, + "loginTenantId": { + "description": "上次登录租户ID", + "type": "integer" + }, + "orgCode": { + "description": "机构编码", + "type": "string" + }, + "phone": { + "description": "电话", + "type": "string" + }, + "programType": { + "description": "所属程序", + "type": "string" + }, + "realname": { + "description": "真实姓名", + "type": "string" + }, + "sex": { + "description": "性别(0-未知,1-男,2-女)", + "type": "integer" + }, + "showLinediff": { + "description": "是否显示历年线差", + "type": "string" + }, + "status": { + "description": "状态(1-正常,2-冻结)", + "type": "integer" + }, + "telephone": { + "description": "座机号", + "type": "string" + }, + "thirdId": { + "description": "第三方登录唯一标识", + "type": "string" + }, + "thirdType": { + "description": "第三方类型", + "type": "string" + }, + "updateBy": { + "description": "更新人", + "type": "string" + }, + "updateTime": { + "description": "更新时间", + "type": "string" + }, + "userIdentity": { + "description": "身份(1普通成员 2上级)", + "type": "integer" + }, + "username": { + "description": "登录账号", + "type": "string" + }, + "workNo": { + "description": "工号", + "type": "string" + }, + "wxOpenId": { + "description": "微信openId", + "type": "string" + } + } + }, + "entity.YxCalculationMajor": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "enrollProbability": { + "type": "number" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kslx": { + "type": "string" + }, + "limitation": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "otherScoreLimitation": { + "type": "string" + }, + "planNum": { + "type": "integer" + }, + "privateProbabilityOperator": { + "type": "string" + }, + "privateRulesEnrollProbability": { + "type": "string" + }, + "privateStudentConvertedScore": { + "type": "number" + }, + "probabilityOperator": { + "type": "string" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "rulesEnrollProbabilitySx": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "scoreId": { + "type": "string" + }, + "state": { + "type": "string" + }, + "studentConvertedScore": { + "type": "number" + }, + "studentOldConvertedScore": { + "type": "number" + }, + "tuition": { + "type": "string" + } + } + }, + "entity.YxHistoryMajorEnroll": { + "type": "object", + "properties": { + "actualPitcherNum": { + "type": "integer" + }, + "admissionLine": { + "type": "number" + }, + "admissionNum": { + "type": "integer" + }, + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "checkMaster": { + "type": "string" + }, + "controlLine": { + "type": "number" + }, + "createBy": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "enrollNum": { + "type": "integer" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "institutionCode": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "oneVolunteerAdmissionNum": { + "type": "integer" + }, + "probabilityOperator": { + "type": "string" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "schoolName": { + "type": "string" + }, + "scoreLineDifference": { + "type": "number" + }, + "sysOrgCode": { + "type": "string" + }, + "tuition": { + "type": "string" + }, + "updateBy": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "year": { + "type": "string" + } + } + }, + "entity.YxSchoolMajor": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "checkMaster": { + "type": "string" + }, + "chineseScoreLimitation": { + "type": "number" + }, + "createBy": { + "type": "string" + }, + "createTime": { + "type": "string" + }, + "culturalControlLine": { + "type": "number" + }, + "culturalScoreLimitation": { + "type": "number" + }, + "detail": { + "type": "string" + }, + "englishScoreLimitation": { + "type": "number" + }, + "enrollmentCode": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kslx": { + "type": "string" + }, + "limitation": { + "type": "string" + }, + "mainSubjects": { + "type": "string" + }, + "majorCode": { + "type": "string" + }, + "majorName": { + "type": "string" + }, + "majorType": { + "type": "string" + }, + "majorTypeChild": { + "type": "string" + }, + "planNum": { + "type": "integer" + }, + "privateProbabilityOperator": { + "type": "string" + }, + "privateRulesEnrollProbability": { + "type": "string" + }, + "probabilityOperator": { + "type": "string" + }, + "professionalScoreLimitation": { + "type": "number" + }, + "rulesEnrollProbability": { + "type": "string" + }, + "rulesEnrollProbabilitySx": { + "type": "string" + }, + "schoolCode": { + "type": "string" + }, + "schoolName": { + "type": "string" + }, + "semester": { + "type": "string" + }, + "specialControlLine": { + "type": "number" + }, + "state": { + "type": "string" + }, + "tuition": { + "type": "string" + }, + "updateBy": { + "type": "string" + }, + "updateTime": { + "type": "string" + } + } + }, + "entity.YxUserScore": { + "type": "object", + "properties": { + "batch": { + "description": "录取批次", + "type": "string" + }, + "calculationTableName": { + "description": "记录结果表名", + "type": "string" + }, + "chineseScore": { + "description": "语文成绩", + "type": "number" + }, + "cognitioPolyclinic": { + "description": "文理分班(文科/理科)", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建时间", + "type": "string" + }, + "culturalScore": { + "description": "文化成绩分", + "type": "number" + }, + "educationalLevel": { + "description": "学历层次(1-本科,2-专科)", + "type": "string" + }, + "englishScore": { + "description": "英语成绩", + "type": "number" + }, + "fzby": { + "description": "服装表演", + "type": "number" + }, + "id": { + "type": "string" + }, + "jwtNum": { + "description": "较稳妥专业数量", + "type": "integer" + }, + "kbdNum": { + "description": "可保底专业数量", + "type": "integer" + }, + "kcjNum": { + "description": "可冲击专业数量", + "type": "integer" + }, + "nlqNum": { + "description": "难录取专业数量", + "type": "integer" + }, + "professionalCategory": { + "description": "专业类别(美术类/...)", + "type": "string" + }, + "professionalCategoryChildren": { + "description": "子级专业类别", + "type": "string" + }, + "professionalScore": { + "description": "专业成绩分", + "type": "number" + }, + "province": { + "description": "高考省份", + "type": "string" + }, + "ranking": { + "description": "位次", + "type": "integer" + }, + "state": { + "description": "状态(0-未使用,1-使用中)", + "type": "string" + }, + "subjects": { + "description": "选课", + "type": "string" + }, + "type": { + "description": "填报类型(1-普通类 2-艺术类)", + "type": "string" + }, + "updateBy": { + "description": "修改人", + "type": "string" + }, + "updateTime": { + "description": "修改时间", + "type": "string" + }, + "xjysby": { + "description": "戏剧影视表演", + "type": "number" + }, + "xjysdy": { + "description": "戏剧影视导演", + "type": "number" + }, + "yybyqy": { + "description": "音乐表演器乐", + "type": "number" + }, + "yybysy": { + "description": "音乐表演声乐", + "type": "number" + }, + "yyjy": { + "description": "音乐教育", + "type": "number" + } + } + }, + "entity.YxVolunteer": { + "type": "object", + "properties": { + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建日期", + "type": "string" + }, + "createType": { + "description": "生成类型(1.手动生成,2.智能生成)", + "type": "string" + }, + "id": { + "type": "string" + }, + "scoreId": { + "description": "使用成绩id", + "type": "string" + }, + "state": { + "description": "志愿单状态(0-否,1.正在使用,2-历史)", + "type": "string" + }, + "sysOrgCode": { + "description": "所属部门", + "type": "string" + }, + "updateBy": { + "description": "更新人", + "type": "string" + }, + "updateTime": { + "description": "更新日期", + "type": "string" + }, + "volunteerName": { + "description": "志愿单名称", + "type": "string" + } + } + }, + "entity.YxVolunteerRecord": { + "type": "object", + "properties": { + "batch": { + "description": "录取批次", + "type": "string" + }, + "calculationMajorId": { + "description": "专业折算id", + "type": "string" + }, + "createBy": { + "description": "创建人", + "type": "string" + }, + "createTime": { + "description": "创建日期", + "type": "string" + }, + "enrollProbability": { + "description": "录取概率", + "type": "number" + }, + "enrollmentCode": { + "description": "招生代码", + "type": "string" + }, + "fctj": { + "description": "服从调剂", + "type": "integer" + }, + "id": { + "type": "string" + }, + "indexs": { + "description": "志愿顺序", + "type": "integer" + }, + "majorCode": { + "description": "专业编码", + "type": "string" + }, + "schoolCode": { + "description": "学校编码", + "type": "string" + }, + "studentConvertedScore": { + "description": "折合分数", + "type": "number" + }, + "volunteerId": { + "description": "志愿单id", + "type": "string" + } + } + }, + "server_modules_system_controller.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "server_modules_user_controller.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/server/docs/swagger.yaml b/server/docs/swagger.yaml new file mode 100644 index 0000000..ac94e1a --- /dev/null +++ b/server/docs/swagger.yaml @@ -0,0 +1,1559 @@ +basePath: /api +definitions: + common.Response: + properties: + code: + type: integer + data: {} + message: + type: string + type: object + controller.UpdatePasswordRequest: + properties: + newPassword: + type: string + oldPassword: + type: string + required: + - newPassword + - oldPassword + type: object + dto.SaveScoreRequest: + properties: + ChineseScore: + type: number + CognitioPolyclinic: + type: string + CulturalScore: + type: number + EnglishScore: + type: number + ProfessionalCategory: + type: string + ProfessionalCategoryChildren: + items: + type: string + type: array + ProfessionalCategoryChildrenScore: + additionalProperties: + format: float64 + type: number + type: object + ProfessionalScore: + type: number + Province: + type: string + SubjectList: + items: + type: string + type: array + createBy: + type: string + required: + - ChineseScore + - CognitioPolyclinic + - CulturalScore + - EnglishScore + - ProfessionalCategory + - ProfessionalCategoryChildren + - ProfessionalCategoryChildrenScore + - ProfessionalScore + - Province + - SubjectList + type: object + entity.SysUser: + properties: + activitiSync: + description: 同步工作流引擎 + type: integer + avatar: + description: 头像 + type: string + birthday: + description: 生日 + type: string + bpmStatus: + description: 流程状态 + type: string + clientId: + description: 设备ID + type: string + createBy: + description: 创建人 + type: string + createTime: + description: 创建时间 + type: string + delFlag: + description: 删除状态(0-正常,1-已删除) + type: integer + departIds: + description: 负责部门 + type: string + dyOpenId: + description: 抖音openId + type: string + email: + description: 电子邮件 + type: string + id: + type: string + ip: + description: 注册时ip + type: string + loginTenantId: + description: 上次登录租户ID + type: integer + orgCode: + description: 机构编码 + type: string + phone: + description: 电话 + type: string + programType: + description: 所属程序 + type: string + realname: + description: 真实姓名 + type: string + sex: + description: 性别(0-未知,1-男,2-女) + type: integer + showLinediff: + description: 是否显示历年线差 + type: string + status: + description: 状态(1-正常,2-冻结) + type: integer + telephone: + description: 座机号 + type: string + thirdId: + description: 第三方登录唯一标识 + type: string + thirdType: + description: 第三方类型 + type: string + updateBy: + description: 更新人 + type: string + updateTime: + description: 更新时间 + type: string + userIdentity: + description: 身份(1普通成员 2上级) + type: integer + username: + description: 登录账号 + type: string + workNo: + description: 工号 + type: string + wxOpenId: + description: 微信openId + type: string + type: object + entity.YxCalculationMajor: + properties: + batch: + type: string + category: + type: string + createTime: + type: string + detail: + type: string + enrollProbability: + type: number + enrollmentCode: + type: string + id: + type: string + kslx: + type: string + limitation: + type: string + mainSubjects: + type: string + majorCode: + type: string + majorName: + type: string + majorType: + type: string + majorTypeChild: + type: string + otherScoreLimitation: + type: string + planNum: + type: integer + privateProbabilityOperator: + type: string + privateRulesEnrollProbability: + type: string + privateStudentConvertedScore: + type: number + probabilityOperator: + type: string + rulesEnrollProbability: + type: string + rulesEnrollProbabilitySx: + type: string + schoolCode: + type: string + scoreId: + type: string + state: + type: string + studentConvertedScore: + type: number + studentOldConvertedScore: + type: number + tuition: + type: string + type: object + entity.YxHistoryMajorEnroll: + properties: + actualPitcherNum: + type: integer + admissionLine: + type: number + admissionNum: + type: integer + batch: + type: string + category: + type: string + checkMaster: + type: string + controlLine: + type: number + createBy: + type: string + createTime: + type: string + detail: + type: string + enrollNum: + type: integer + enrollmentCode: + type: string + id: + type: string + institutionCode: + type: string + mainSubjects: + type: string + majorCode: + type: string + majorName: + type: string + majorType: + type: string + majorTypeChild: + type: string + oneVolunteerAdmissionNum: + type: integer + probabilityOperator: + type: string + rulesEnrollProbability: + type: string + schoolCode: + type: string + schoolName: + type: string + scoreLineDifference: + type: number + sysOrgCode: + type: string + tuition: + type: string + updateBy: + type: string + updateTime: + type: string + year: + type: string + type: object + entity.YxSchoolMajor: + properties: + batch: + type: string + category: + type: string + checkMaster: + type: string + chineseScoreLimitation: + type: number + createBy: + type: string + createTime: + type: string + culturalControlLine: + type: number + culturalScoreLimitation: + type: number + detail: + type: string + englishScoreLimitation: + type: number + enrollmentCode: + type: string + id: + type: string + kslx: + type: string + limitation: + type: string + mainSubjects: + type: string + majorCode: + type: string + majorName: + type: string + majorType: + type: string + majorTypeChild: + type: string + planNum: + type: integer + privateProbabilityOperator: + type: string + privateRulesEnrollProbability: + type: string + probabilityOperator: + type: string + professionalScoreLimitation: + type: number + rulesEnrollProbability: + type: string + rulesEnrollProbabilitySx: + type: string + schoolCode: + type: string + schoolName: + type: string + semester: + type: string + specialControlLine: + type: number + state: + type: string + tuition: + type: string + updateBy: + type: string + updateTime: + type: string + type: object + entity.YxUserScore: + properties: + batch: + description: 录取批次 + type: string + calculationTableName: + description: 记录结果表名 + type: string + chineseScore: + description: 语文成绩 + type: number + cognitioPolyclinic: + description: 文理分班(文科/理科) + type: string + createBy: + description: 创建人 + type: string + createTime: + description: 创建时间 + type: string + culturalScore: + description: 文化成绩分 + type: number + educationalLevel: + description: 学历层次(1-本科,2-专科) + type: string + englishScore: + description: 英语成绩 + type: number + fzby: + description: 服装表演 + type: number + id: + type: string + jwtNum: + description: 较稳妥专业数量 + type: integer + kbdNum: + description: 可保底专业数量 + type: integer + kcjNum: + description: 可冲击专业数量 + type: integer + nlqNum: + description: 难录取专业数量 + type: integer + professionalCategory: + description: 专业类别(美术类/...) + type: string + professionalCategoryChildren: + description: 子级专业类别 + type: string + professionalScore: + description: 专业成绩分 + type: number + province: + description: 高考省份 + type: string + ranking: + description: 位次 + type: integer + state: + description: 状态(0-未使用,1-使用中) + type: string + subjects: + description: 选课 + type: string + type: + description: 填报类型(1-普通类 2-艺术类) + type: string + updateBy: + description: 修改人 + type: string + updateTime: + description: 修改时间 + type: string + xjysby: + description: 戏剧影视表演 + type: number + xjysdy: + description: 戏剧影视导演 + type: number + yybyqy: + description: 音乐表演器乐 + type: number + yybysy: + description: 音乐表演声乐 + type: number + yyjy: + description: 音乐教育 + type: number + type: object + entity.YxVolunteer: + properties: + createBy: + description: 创建人 + type: string + createTime: + description: 创建日期 + type: string + createType: + description: 生成类型(1.手动生成,2.智能生成) + type: string + id: + type: string + scoreId: + description: 使用成绩id + type: string + state: + description: 志愿单状态(0-否,1.正在使用,2-历史) + type: string + sysOrgCode: + description: 所属部门 + type: string + updateBy: + description: 更新人 + type: string + updateTime: + description: 更新日期 + type: string + volunteerName: + description: 志愿单名称 + type: string + type: object + entity.YxVolunteerRecord: + properties: + batch: + description: 录取批次 + type: string + calculationMajorId: + description: 专业折算id + type: string + createBy: + description: 创建人 + type: string + createTime: + description: 创建日期 + type: string + enrollProbability: + description: 录取概率 + type: number + enrollmentCode: + description: 招生代码 + type: string + fctj: + description: 服从调剂 + type: integer + id: + type: string + indexs: + description: 志愿顺序 + type: integer + majorCode: + description: 专业编码 + type: string + schoolCode: + description: 学校编码 + type: string + studentConvertedScore: + description: 折合分数 + type: number + volunteerId: + description: 志愿单id + type: string + type: object + server_modules_system_controller.LoginRequest: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object + server_modules_user_controller.LoginRequest: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object +host: localhost:8080 +info: + contact: {} + description: 提供用户认证、院校专业、历年招生、计算专业的管理接口 + title: 艺考招生管理系统 API + version: "2.0" +paths: + /sys-users: + get: + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取用户列表 + tags: + - 用户管理 + post: + parameters: + - description: 用户信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.SysUser' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建用户 + tags: + - 用户管理 + /sys-users/{id}: + delete: + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除用户 + tags: + - 用户管理 + get: + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个用户 + tags: + - 用户管理 + patch: + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + - description: 要更新的字段 + in: body + name: fields + required: true + schema: + additionalProperties: true + type: object + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 动态字段更新 + tags: + - 用户管理 + put: + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + - description: 用户信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.SysUser' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新用户 + tags: + - 用户管理 + /sys-users/{id}/password: + put: + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + - description: 密码信息 + in: body + name: request + required: true + schema: + $ref: '#/definitions/controller.UpdatePasswordRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 修改密码 + tags: + - 用户管理 + /sys/auth/info: + get: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: Sys获取当前登录用户信息 + tags: + - 认证 + /sys/auth/login: + post: + consumes: + - application/json + parameters: + - description: 登录信息 + in: body + name: request + required: true + schema: + $ref: '#/definitions/server_modules_system_controller.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: Sys用户登录 + tags: + - 认证 + /sys/auth/logout: + post: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: Sys用户登出 + tags: + - 认证 + /user/auth/info: + get: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前登录用户信息 + tags: + - 认证 + /user/auth/login: + post: + consumes: + - application/json + parameters: + - description: 登录信息 + in: body + name: request + required: true + schema: + $ref: '#/definitions/server_modules_user_controller.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 用户登录 + tags: + - 认证 + /user/auth/logout: + post: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 用户登出 + tags: + - 认证 + /user/major/list: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + - default: '""' + description: 批次(本科提前批/本科A段/本科B段/本科/高职高专) + in: query + name: batch + type: string + - default: '""' + description: 录取概率类型(难录取/可冲击/较稳妥/可保底) + in: query + name: probability + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前用户可检索列表 + tags: + - 用户专业 + /user/score: + get: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前用户的激活分数 + tags: + - 用户分数 + /user/score/{id}: + get: + parameters: + - description: 成绩ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取指定 ID 的分数 + tags: + - 用户分数 + /user/score/list: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取当前用户的成绩列表 + tags: + - 用户分数 + /user/score/save-score: + post: + parameters: + - description: 成绩信息 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.SaveScoreRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 保存用户成绩 + tags: + - 用户分数 + /yx-calculation-majors: + get: + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取计算专业列表 + tags: + - 计算专业 + post: + parameters: + - description: 计算专业信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxCalculationMajor' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建计算专业 + tags: + - 计算专业 + /yx-calculation-majors/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除计算专业 + tags: + - 计算专业 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个计算专业 + tags: + - 计算专业 + patch: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 要更新的字段 + in: body + name: fields + required: true + schema: + additionalProperties: true + type: object + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 动态字段更新 + tags: + - 计算专业 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 计算专业信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxCalculationMajor' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新计算专业 + tags: + - 计算专业 + /yx-calculation-majors/batch: + delete: + parameters: + - description: ID列表 + in: body + name: ids + required: true + schema: + items: + type: string + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量删除计算专业 + tags: + - 计算专业 + post: + parameters: + - description: 计算专业列表 + in: body + name: items + required: true + schema: + items: + $ref: '#/definitions/entity.YxCalculationMajor' + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量创建计算专业 + tags: + - 计算专业 + /yx-history-enrolls: + get: + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取历年招生列表 + tags: + - 历年招生 + post: + parameters: + - description: 历年招生信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxHistoryMajorEnroll' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建历年招生记录 + tags: + - 历年招生 + /yx-history-enrolls/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除历年招生记录 + tags: + - 历年招生 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个历年招生记录 + tags: + - 历年招生 + patch: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 要更新的字段 + in: body + name: fields + required: true + schema: + additionalProperties: true + type: object + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 动态字段更新 + tags: + - 历年招生 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 历年招生信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxHistoryMajorEnroll' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新历年招生记录 + tags: + - 历年招生 + /yx-history-enrolls/batch: + delete: + parameters: + - description: ID列表 + in: body + name: ids + required: true + schema: + items: + type: string + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量删除历年招生记录 + tags: + - 历年招生 + post: + parameters: + - description: 历年招生列表 + in: body + name: items + required: true + schema: + items: + $ref: '#/definitions/entity.YxHistoryMajorEnroll' + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量创建历年招生记录 + tags: + - 历年招生 + /yx-school-majors: + get: + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取院校专业列表 + tags: + - 院校专业 + post: + parameters: + - description: 院校专业信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxSchoolMajor' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建院校专业 + tags: + - 院校专业 + /yx-school-majors/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除院校专业 + tags: + - 院校专业 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个院校专业 + tags: + - 院校专业 + patch: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 要更新的字段 + in: body + name: fields + required: true + schema: + additionalProperties: true + type: object + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 动态字段更新 + tags: + - 院校专业 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 院校专业信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxSchoolMajor' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新院校专业 + tags: + - 院校专业 + /yx-school-majors/batch: + delete: + parameters: + - description: ID列表 + in: body + name: ids + required: true + schema: + items: + type: string + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量删除院校专业 + tags: + - 院校专业 + post: + parameters: + - description: 院校专业列表 + in: body + name: items + required: true + schema: + items: + $ref: '#/definitions/entity.YxSchoolMajor' + type: array + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 批量创建院校专业 + tags: + - 院校专业 + /yx-user-scores: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取用户分数列表 + tags: + - 用户分数 + post: + parameters: + - description: 用户分数信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxUserScore' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建用户分数 + tags: + - 用户分数 + /yx-user-scores/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除用户分数 + tags: + - 用户分数 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个用户分数 + tags: + - 用户分数 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 用户分数信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxUserScore' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新用户分数 + tags: + - 用户分数 + /yx-volunteer-records: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取志愿明细列表 + tags: + - 志愿明细 + post: + parameters: + - description: 志愿明细信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxVolunteerRecord' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建志愿明细 + tags: + - 志愿明细 + /yx-volunteer-records/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除志愿明细 + tags: + - 志愿明细 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个志愿明细 + tags: + - 志愿明细 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 志愿明细信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxVolunteerRecord' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新志愿明细 + tags: + - 志愿明细 + /yx-volunteers: + get: + parameters: + - default: 1 + description: 页码 + in: query + name: page + type: integer + - default: 10 + description: 每页数量 + in: query + name: size + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取志愿列表 + tags: + - 志愿 + post: + parameters: + - description: 志愿信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxVolunteer' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 创建志愿 + tags: + - 志愿 + /yx-volunteers/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 删除志愿 + tags: + - 志愿 + get: + parameters: + - description: ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 获取单个志愿 + tags: + - 志愿 + put: + parameters: + - description: ID + in: path + name: id + required: true + type: string + - description: 志愿信息 + in: body + name: item + required: true + schema: + $ref: '#/definitions/entity.YxVolunteer' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/common.Response' + summary: 更新志愿 + tags: + - 志愿 +securityDefinitions: + Bearer: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..91d00ce --- /dev/null +++ b/server/go.mod @@ -0,0 +1,57 @@ +module server + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.4.0 + github.com/redis/go-redis/v9 v9.3.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.2 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..1942bcb --- /dev/null +++ b/server/go.sum @@ -0,0 +1,188 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= +github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..925fab4 --- /dev/null +++ b/server/main.go @@ -0,0 +1,171 @@ +// Package main 应用程序入口 +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "server/common" + "server/config" + _ "server/docs" + "server/middleware" + sysController "server/modules/system/controller" + userController "server/modules/user/controller" + yxController "server/modules/yx/controller" + + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +// @title 艺考招生管理系统 API +// @version 2.0 +// @description 提供用户认证、院校专业、历年招生、计算专业的管理接口 +// @host localhost:8080 +// @BasePath /api +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization + +func main() { + // 加载配置 + config.LoadConfig() + + // 初始化日志 + common.InitLogger() + common.Info("========== 应用启动 ==========") + + // 初始化雪花算法ID生成器(从配置获取workerID,默认为1) + workerID := int64(config.AppConfig.Server.WorkerID) + if workerID <= 0 { + workerID = 1 // 默认workerID + } + datacenterID := int64(config.AppConfig.Server.DatacenterID) + if datacenterID < 0 { + datacenterID = 0 // 默认datacenterID + } + if err := common.InitGenerator(workerID, datacenterID); err != nil { + common.LogError("雪花算法初始化失败: %v", err) + log.Fatalf("雪花算法初始化失败: %v\n", err) + } + common.Info("雪花算法ID生成器初始化完成 (WorkerID: %d)", workerID) + + // 初始化数据库 + config.InitDB() + common.Info("数据库初始化完成") + + // 初始化Redis + config.InitRedis() + common.Info("Redis初始化完成") + + // 创建 Gin 引擎 + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(gin.Recovery()) + + // 请求日志中间件 + r.Use(requestLogMiddleware()) + + // 跨域中间件 + r.Use(middleware.CorsMiddleware()) + + // Swagger 文档 (需要Basic Auth验证) + authorized := r.Group("/swagger", gin.BasicAuth(gin.Accounts{ + config.AppConfig.Swagger.User: config.AppConfig.Swagger.Password, + })) + authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + // API 路由组 + api := r.Group("/api") + + // 中间件顺序: 安全校验 -> 限流 -> 登录鉴权 + api.Use(middleware.SecurityMiddleware()) + api.Use(middleware.RateLimitMiddleware()) + api.Use(middleware.AuthMiddleware()) + + // 注册 System 模块路由 + sysController.NewAuthController().RegisterRoutes(api) + sysController.NewSysUserController().RegisterRoutes(api) + + // 注册 YX 模块路由 + yxController.NewYxSchoolMajorController().RegisterRoutes(api) + yxController.NewYxHistoryMajorEnrollController().RegisterRoutes(api) + yxController.NewYxCalculationMajorController().RegisterRoutes(api) + yxController.NewYxUserScoreController().RegisterRoutes(api) + yxController.NewYxVolunteerController().RegisterRoutes(api) + yxController.NewYxVolunteerRecordController().RegisterRoutes(api) + + // 注册 User 模块路由 + userController.NewUserScoreController().RegisterRoutes(api) + userController.NewAuthController().RegisterRoutes(api) + userController.NewUserMajorController().RegisterRoutes(api) + userController.NewUserVolunteerController().RegisterRoutes(api) + + // 创建 HTTP 服务器 + port := config.AppConfig.Server.Port + if port == 0 { + port = 8080 // 默认端口 + } + srv := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: r, + } + + // 启动服务器 + go func() { + common.Info("服务器启动: http://localhost:%d", port) + common.Info("Swagger文档: http://localhost:%d/swagger/index.html", port) + log.Printf("服务器启动: http://localhost:%d\n", port) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + common.LogError("启动失败: %s", err) + log.Fatalf("启动失败: %s\n", err) + } + }() + + // 优雅关闭 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + common.Info("正在关闭服务器...") + log.Println("正在关闭服务器...") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + common.LogError("服务器关闭异常: %v", err) + log.Fatal("服务器关闭异常:", err) + } + + // 关闭资源 + config.CloseDB() + config.CloseRedis() + common.Info("========== 应用关闭 ==========") + common.CloseLogger() + + log.Println("服务器已退出") +} + +// requestLogMiddleware 请求日志中间件 +func requestLogMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + method := c.Request.Method + + c.Next() + + latency := time.Since(start) + status := c.Writer.Status() + clientIP := c.ClientIP() + + common.Info("%s %s %d %v %s", method, path, status, latency, clientIP) + } +} diff --git a/server/middleware/auth.go b/server/middleware/auth.go new file mode 100644 index 0000000..83259d0 --- /dev/null +++ b/server/middleware/auth.go @@ -0,0 +1,70 @@ +// Package middleware 中间件 +package middleware + +import ( + "strings" + + "server/common" + "server/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 白名单路径 (不需要登录即可访问) +var whiteList = []string{ + "/api/sys/auth/login", + "/api/sys/auth/register", + "/api/user/auth/login", + "/api/user/auth/register", + "/swagger/", + "/swagger/index.html", +} + +// AuthMiddleware 登录鉴权中间件 +// 类似Java中的Shiro Filter +func AuthMiddleware() gin.HandlerFunc { + userService := service.NewSysUserService() + + return func(c *gin.Context) { + path := c.Request.URL.Path + + // 检查是否在白名单中 + for _, white := range whiteList { + if strings.HasPrefix(path, white) { + c.Next() + return + } + } + + // 获取Token + token := c.GetHeader(common.TokenHeader) + if token == "" { + common.Error(c, 401, "未登录") + c.Abort() + return + } + + // 如果有前缀则处理前缀 + if common.HeaderTokenPrefix != "" && strings.HasPrefix(token, common.HeaderTokenPrefix) { + token = token[len(common.HeaderTokenPrefix):] + } + + // 验证Token并获取用户信息 + loginUser, err := userService.GetLoginUser(token) + if err != nil { + common.Error(c, 401, "登录已失效,请重新登录") + c.Abort() + return + } + + // 存入上下文 + c.Set(common.ContextUserKey, loginUser) + + c.Next() + } +} + +// AddWhiteList 添加白名单路径 +func AddWhiteList(paths ...string) { + whiteList = append(whiteList, paths...) +} diff --git a/server/middleware/cors.go b/server/middleware/cors.go new file mode 100644 index 0000000..bc6d174 --- /dev/null +++ b/server/middleware/cors.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// CorsMiddleware 跨域中间件 +func CorsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + origin := c.Request.Header.Get("Origin") + + if origin != "" { + // 允许所有来源,生产环境请修改为特定域名 + c.Header("Access-Control-Allow-Origin", origin) + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") + c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, X-App-Sign") + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") + c.Header("Access-Control-Allow-Credentials", "true") + } + + // 放行 OPTIONS 请求 + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + + c.Next() + } +} diff --git a/server/middleware/ratelimit.go b/server/middleware/ratelimit.go new file mode 100644 index 0000000..e77bcbb --- /dev/null +++ b/server/middleware/ratelimit.go @@ -0,0 +1,142 @@ +// Package middleware 限流中间件 +package middleware + +import ( + "context" + "fmt" + "strings" + "time" + + "server/common" + "server/config" + + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" +) + +// RateLimitMiddleware 限流中间件 +// 基于 Redis 实现,支持按用户ID或IP限流 +// 不同接口可配置不同的限流规则 +func RateLimitMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + cfg := config.AppConfig.RateLimit + + // 未启用则跳过 + if !cfg.Enable { + c.Next() + return + } + + // 白名单路径跳过 + path := c.Request.URL.Path + if isRateLimitWhitelist(path) { + c.Next() + return + } + + // 获取限流规则 + rule := getRule(path, cfg) + + // 获取限流key (优先用户ID,否则用IP) + key := getRateLimitKey(c, path) + + // 检查是否超过限制 + if !checkRateLimit(key, rule) { + common.Warn("请求过于频繁: Key=%s Path=%s", key, path) + c.JSON(429, map[string]interface{}{ + "code": 429, + "message": "操作过快,请稍后再试", + "data": nil, + }) + c.Abort() + return + } + + c.Next() + } +} + +// getRule 获取路径对应的限流规则 +func getRule(path string, cfg config.RateLimitConfig) config.RateLimitRule { + // 精确匹配 + if rule, ok := cfg.Rules[path]; ok { + return rule + } + + // 前缀匹配 + for rulePath, rule := range cfg.Rules { + if strings.HasPrefix(path, rulePath) { + return rule + } + } + + // 返回默认规则 + return cfg.Default +} + +// getRateLimitKey 获取限流key +func getRateLimitKey(c *gin.Context, path string) string { + // 优先使用用户ID + if user := common.GetLoginUser(c); user != nil { + return fmt.Sprintf("ratelimit:%s:%s", user.ID, path) + } + // 否则使用IP + return fmt.Sprintf("ratelimit:%s:%s", c.ClientIP(), path) +} + +// checkRateLimit 检查是否超过限流 +// 使用 Redis 滑动窗口算法 +func checkRateLimit(key string, rule config.RateLimitRule) bool { + ctx := context.Background() + rdb := config.RDB + + now := time.Now().UnixMilli() + windowStart := now - int64(rule.Interval*1000) + + // 使用 Redis 事务 + pipe := rdb.Pipeline() + + // 移除窗口外的记录 + pipe.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", windowStart)) + + // 获取当前窗口内的请求数 + countCmd := pipe.ZCard(ctx, key) + + // 添加当前请求 + pipe.ZAdd(ctx, key, redis.Z{ + Score: float64(now), + Member: fmt.Sprintf("%d", now), + }) + + // 设置过期时间 + pipe.Expire(ctx, key, time.Duration(rule.Interval)*time.Second) + + _, err := pipe.Exec(ctx) + if err != nil { + common.LogError("限流检查失败: %v", err) + return true // 出错时放行 + } + + count := countCmd.Val() + return count < int64(rule.MaxRequests) +} + +// 限流白名单 +var rateLimitWhitelist = []string{ + "/swagger/", + "/api/auth/logout", +} + +func isRateLimitWhitelist(path string) bool { + for _, white := range rateLimitWhitelist { + if strings.HasPrefix(path, white) { + return true + } + } + return false +} + +// AddRateLimitWhitelist 添加限流白名单 +func AddRateLimitWhitelist(paths ...string) { + rateLimitWhitelist = append(rateLimitWhitelist, paths...) +} diff --git a/server/middleware/security.go b/server/middleware/security.go new file mode 100644 index 0000000..aede502 --- /dev/null +++ b/server/middleware/security.go @@ -0,0 +1,111 @@ +// Package middleware 安全校验中间件 +package middleware + +import ( + "crypto/md5" + "encoding/hex" + "strconv" + "time" + + "server/common" + "server/config" + + "github.com/gin-gonic/gin" +) + +// SecurityMiddleware 安全校验中间件 +// 防止暴力入侵,校验请求头签名 +// 请求头需携带: +// - X-App-Sign: 签名值 = MD5(timestamp + secretKey) +// - X-App-Timestamp: 时间戳(毫秒) +func SecurityMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + cfg := config.AppConfig.Security + + // 未启用则跳过 + if !cfg.Enable { + c.Next() + return + } + + // 白名单路径跳过 + path := c.Request.URL.Path + if isSecurityWhitelist(path) { + c.Next() + return + } + + // 获取签名和时间戳 + sign := c.GetHeader(cfg.HeaderKey) + timestamp := c.GetHeader("X-App-Timestamp") + + if sign == "" || timestamp == "" { + common.Warn("安全校验失败: 缺少签名头 IP=%s Path=%s", c.ClientIP(), path) + common.Error(c, 403, "非法请求") + c.Abort() + return + } + + // 验证时间戳 (5分钟内有效) + ts, err := strconv.ParseInt(timestamp, 10, 64) + if err != nil { + common.Warn("安全校验失败: 时间戳格式错误 IP=%s", c.ClientIP()) + common.Error(c, 403, "非法请求") + c.Abort() + return + } + + now := time.Now().UnixMilli() + if abs(now-ts) > 5*60*1000 { // 5分钟 + common.Warn("安全校验失败: 时间戳过期 IP=%s Timestamp=%d", c.ClientIP(), ts) + common.Error(c, 403, "请求已过期") + c.Abort() + return + } + + // 验证签名 + expectedSign := generateSign(timestamp, cfg.SecretKey) + if sign != expectedSign { + common.Warn("安全校验失败: 签名错误 IP=%s Sign=%s Expected=%s", c.ClientIP(), sign, expectedSign) + common.Error(c, 403, "签名错误") + c.Abort() + return + } + + c.Next() + } +} + +// generateSign 生成签名 +func generateSign(timestamp, secretKey string) string { + data := timestamp + secretKey + hash := md5.Sum([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +// 安全校验白名单 +var securityWhitelist = []string{ + "/swagger/", + "/swagger/index.html", +} + +func isSecurityWhitelist(path string) bool { + for _, white := range securityWhitelist { + if len(path) >= len(white) && path[:len(white)] == white { + return true + } + } + return false +} + +func abs(n int64) int64 { + if n < 0 { + return -n + } + return n +} + +// AddSecurityWhitelist 添加安全校验白名单 +func AddSecurityWhitelist(paths ...string) { + securityWhitelist = append(securityWhitelist, paths...) +} diff --git a/server/modules/system/controller/sys_auth_controller.go b/server/modules/system/controller/sys_auth_controller.go new file mode 100644 index 0000000..9ec4424 --- /dev/null +++ b/server/modules/system/controller/sys_auth_controller.go @@ -0,0 +1,81 @@ +// Package controller 控制器层 +package controller + +import ( + "server/common" + "server/modules/system/dto" + "server/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// AuthController 认证控制器 +type AuthController struct { + userService *service.SysUserService +} + +func NewAuthController() *AuthController { + return &AuthController{userService: service.NewSysUserService()} +} + +func (ctrl *AuthController) RegisterRoutes(r *gin.RouterGroup) { + r.POST("/sys/auth/login", ctrl.SysLogin) + r.POST("/sys/auth/logout", ctrl.Logout) + r.GET("/sys/auth/info", ctrl.GetUserInfo) +} + +// SysLogin 用户登录 +// @Summary Sys用户登录 +// @Tags 认证 +// @Accept json +// @Produce json +// @Param request body LoginRequest true "登录信息" +// @Success 200 {object} common.Response +// @Router /sys/auth/login [post] +func (ctrl *AuthController) SysLogin(c *gin.Context) { + var req dto.LoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "用户名和密码不能为空") + return + } + + loginUser, token, err := ctrl.userService.SysLogin(req.Username, req.Password) + if err != nil { + common.Error(c, 401, err.Error()) + return + } + + common.Success(c, gin.H{ + "token": token, + "user": loginUser, + }) +} + +// Logout 用户登出 +// @Summary Sys用户登出 +// @Tags 认证 +// @Success 200 {object} common.Response +// @Router /sys/auth/logout [post] +func (ctrl *AuthController) Logout(c *gin.Context) { + token := c.GetHeader("Authorization") + if len(token) > 7 { + token = token[7:] // 去除 "Bearer " + } + + ctrl.userService.Logout(token) + common.Success(c, nil) +} + +// GetUserInfo 获取当前登录用户信息 +// @Summary Sys获取当前登录用户信息 +// @Tags 认证 +// @Success 200 {object} common.Response +// @Router /sys/auth/info [get] +func (ctrl *AuthController) GetUserInfo(c *gin.Context) { + user := common.GetLoginUser(c) + if user == nil { + common.Error(c, 401, "未登录") + return + } + common.Success(c, user) +} diff --git a/server/modules/system/controller/sys_user_controller.go b/server/modules/system/controller/sys_user_controller.go new file mode 100644 index 0000000..4a97e30 --- /dev/null +++ b/server/modules/system/controller/sys_user_controller.go @@ -0,0 +1,161 @@ +// Package controller 控制器层 +package controller + +import ( + "strconv" + + "server/common" + "server/modules/system/dto" + "server/modules/system/service" + "server/modules/system/vo" // 用于 Swagger 注解 + + "github.com/gin-gonic/gin" +) + +type SysUserController struct { + service *service.SysUserService +} + +func NewSysUserController() *SysUserController { + return &SysUserController{service: service.NewSysUserService()} +} + +// _ 确保 vo 包被导入(用于 Swagger 注解) +var _ = vo.SysUserVO{} + +func (ctrl *SysUserController) RegisterRoutes(r *gin.RouterGroup) { + r.GET("/sys-users", ctrl.List) + r.GET("/sys-users/:id", ctrl.GetByID) + r.POST("/sys-users", ctrl.Create) + r.PUT("/sys-users/:id", ctrl.Update) + r.PATCH("/sys-users/:id", ctrl.UpdateFields) + r.DELETE("/sys-users/:id", ctrl.Delete) + r.PUT("/sys-users/:id/password", ctrl.UpdatePassword) +} + +// @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, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + 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) +} + +// @Summary 获取单个用户 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Success 200 {object} common.Response{data=vo.SysUserVO} +// @Router /sys-users/{id} [get] +func (ctrl *SysUserController) GetByID(c *gin.Context) { + item, err := ctrl.service.GetUserByID(c.Param("id")) + if err != nil { + common.Error(c, 404, "未找到") + return + } + common.Success(c, item) +} + +// @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 + } + item, err := ctrl.service.CreateUser(&req, common.GetLoginUserID(c)) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 更新用户 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Param request body dto.UpdateUserRequest true "用户信息" +// @Success 200 {object} common.Response{data=vo.SysUserVO} +// @Router /sys-users/{id} [put] +func (ctrl *SysUserController) Update(c *gin.Context) { + var req dto.UpdateUserRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误: "+err.Error()) + return + } + item, err := ctrl.service.UpdateUser(c.Param("id"), &req) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 动态字段更新 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Param fields body map[string]interface{} true "要更新的字段" +// @Success 200 {object} common.Response +// @Router /sys-users/{id} [patch] +func (ctrl *SysUserController) UpdateFields(c *gin.Context) { + var fields map[string]interface{} + if err := c.ShouldBindJSON(&fields); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.UpdateFields(c.Param("id"), fields); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 删除用户 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Success 200 {object} common.Response +// @Router /sys-users/{id} [delete] +func (ctrl *SysUserController) Delete(c *gin.Context) { + if err := ctrl.service.Delete(c.Param("id")); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// UpdatePasswordRequest 修改密码请求 +type UpdatePasswordRequest struct { + OldPassword string `json:"oldPassword" binding:"required"` + NewPassword string `json:"newPassword" binding:"required"` +} + +// @Summary 修改密码 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Param request body UpdatePasswordRequest true "密码信息" +// @Success 200 {object} common.Response +// @Router /sys-users/{id}/password [put] +func (ctrl *SysUserController) UpdatePassword(c *gin.Context) { + var req UpdatePasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.UpdatePassword(c.Param("id"), req.OldPassword, req.NewPassword); err != nil { + common.Error(c, 400, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/system/dto/auth_dto.go b/server/modules/system/dto/auth_dto.go new file mode 100644 index 0000000..995c6fc --- /dev/null +++ b/server/modules/system/dto/auth_dto.go @@ -0,0 +1,7 @@ +package dto + +// LoginRequest 登录请求 +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} \ No newline at end of file diff --git a/server/modules/system/dto/sys_user_dto.go b/server/modules/system/dto/sys_user_dto.go new file mode 100644 index 0000000..a6f7bd6 --- /dev/null +++ b/server/modules/system/dto/sys_user_dto.go @@ -0,0 +1,26 @@ +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"` // 机构编码 +} \ No newline at end of file diff --git a/server/modules/system/entity/sys_user.go b/server/modules/system/entity/sys_user.go new file mode 100644 index 0000000..ea299a7 --- /dev/null +++ b/server/modules/system/entity/sys_user.go @@ -0,0 +1,55 @@ +// Package entity 实体层 +package entity + +import "time" + +// SysUser 用户表实体 +type SysUser struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + Username string `gorm:"column:username" json:"username"` // 登录账号 + Realname string `gorm:"column:realname" json:"realname"` // 真实姓名 + Password string `gorm:"column:password" json:"-"` // 密码 (不返回给前端) + Salt string `gorm:"column:salt" json:"-"` // md5密码盐 + Avatar string `gorm:"column:avatar" json:"avatar"` // 头像 + Birthday *time.Time `gorm:"column:birthday" json:"birthday"` // 生日 + Sex int `gorm:"column:sex" json:"sex"` // 性别(0-未知,1-男,2-女) + Email string `gorm:"column:email" json:"email"` // 电子邮件 + Phone string `gorm:"column:phone" json:"phone"` // 电话 + OrgCode string `gorm:"column:org_code" json:"orgCode"` // 机构编码 + Status int `gorm:"column:status" json:"status"` // 状态(1-正常,2-冻结) + DelFlag int `gorm:"column:del_flag" json:"delFlag"` // 删除状态(0-正常,1-已删除) + ThirdID string `gorm:"column:third_id" json:"thirdId"` // 第三方登录唯一标识 + ThirdType string `gorm:"column:third_type" json:"thirdType"` // 第三方类型 + ActivitiSync int `gorm:"column:activiti_sync" json:"activitiSync"` // 同步工作流引擎 + WorkNo string `gorm:"column:work_no" json:"workNo"` // 工号 + Telephone string `gorm:"column:telephone" json:"telephone"` // 座机号 + CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建人 + CreateTime *time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间 + UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新人 + UpdateTime *time.Time `gorm:"column:update_time" json:"updateTime"` // 更新时间 + UserIdentity int `gorm:"column:user_identity" json:"userIdentity"` // 身份(1普通成员 2上级) + DepartIds string `gorm:"column:depart_ids" json:"departIds"` // 负责部门 + ClientID string `gorm:"column:client_id" json:"clientId"` // 设备ID + LoginTenantID int `gorm:"column:login_tenant_id" json:"loginTenantId"` // 上次登录租户ID + BpmStatus string `gorm:"column:bpm_status" json:"bpmStatus"` // 流程状态 + WxOpenID string `gorm:"column:wx_open_id" json:"wxOpenId"` // 微信openId + DyOpenID string `gorm:"column:dy_open_id" json:"dyOpenId"` // 抖音openId + IP string `gorm:"column:ip" json:"ip"` // 注册时ip + ShowLinediff string `gorm:"column:show_linediff" json:"showLinediff"` // 是否显示历年线差 + ProgramType string `gorm:"column:program_type" json:"programType"` // 所属程序 +} + +func (SysUser) TableName() string { + return "sys_user" +} + +// LoginUser 登录用户信息 (存储在Redis中) +type LoginUser 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"` + Token string `json:"token"` +} diff --git a/server/modules/system/mapper/sys_user_mapper.go b/server/modules/system/mapper/sys_user_mapper.go new file mode 100644 index 0000000..a9f5b1e --- /dev/null +++ b/server/modules/system/mapper/sys_user_mapper.go @@ -0,0 +1,43 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "server/common" + "server/modules/system/entity" + + "gorm.io/gorm" +) + +type SysUserMapper struct { + *common.BaseMapper[entity.SysUser] +} + +func NewSysUserMapper() *SysUserMapper { + return &SysUserMapper{ + BaseMapper: common.NewBaseMapper[entity.SysUser](), + } +} + +// GetDB 获取数据库实例,添加逻辑删除过滤 +func (m *SysUserMapper) GetDB() *gorm.DB { + return m.BaseMapper.GetDB().Where("del_flag = 0") +} + +// Delete 逻辑删除 +func (m *SysUserMapper) Delete(id string) error { + return m.BaseMapper.GetDB().Model(&entity.SysUser{}).Where("id = ?", id).Update("del_flag", 1).Error +} + +// FindByUsername 根据用户名查找用户 +func (m *SysUserMapper) FindByUsername(username string) (*entity.SysUser, error) { + var item entity.SysUser + err := m.GetDB().First(&item, "username = ?", username).Error + return &item, err +} + +// FindByPhone 根据手机号查找用户 +func (m *SysUserMapper) FindByPhone(phone string) (*entity.SysUser, error) { + var item entity.SysUser + err := m.GetDB().First(&item, "phone = ?", phone).Error + return &item, err +} diff --git a/server/modules/system/service/sys_user_service.go b/server/modules/system/service/sys_user_service.go new file mode 100644 index 0000000..6b33a15 --- /dev/null +++ b/server/modules/system/service/sys_user_service.go @@ -0,0 +1,323 @@ +// Package service 业务逻辑层 +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "time" + + "server/common" + "server/config" + "server/modules/system/dto" + "server/modules/system/entity" + "server/modules/system/mapper" + "server/modules/system/vo" + + "github.com/google/uuid" +) + +type SysUserService struct { + *common.BaseService[entity.SysUser] + mapper *mapper.SysUserMapper +} + +func NewSysUserService() *SysUserService { + mapper := mapper.NewSysUserMapper() + return &SysUserService{ + BaseService: common.NewBaseService[entity.SysUser](), + mapper: mapper, + } +} + +// 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 用户登录(手机号登录) +func (s *SysUserService) Login(username, password string) (*entity.LoginUser, string, error) { + user, err := s.mapper.FindByPhone(username) + if err != nil { + return nil, "", errors.New("用户不存在") + } + + if user.Status == 2 { + return nil, "", errors.New("账号已被冻结") + } + + encrypted, err := common.Encrypt(user.Username, password, user.Salt) + if (user.Password != encrypted) || (err != nil) { + return nil, "", errors.New("用户名或密码错误") + } + + token := s.generateToken() + + loginUser := &entity.LoginUser{ + ID: user.ID, + Username: user.Username, + Realname: user.Realname, + Avatar: user.Avatar, + Phone: user.Phone, + Email: user.Email, + Token: token, + } + + if err := s.saveLoginUser(token, loginUser); err != nil { + return nil, "", errors.New("登录失败,请重试") + } + + return loginUser, token, nil +} + +// SysLogin 用户登录(用户名登录) +func (s *SysUserService) SysLogin(username, password string) (*entity.LoginUser, string, error) { + user, err := s.mapper.FindByUsername(username) + if err != nil { + return nil, "", errors.New("用户不存在") + } + + if user.Status == 2 { + return nil, "", errors.New("账号已被冻结") + } + + encrypted, err := common.Encrypt(username, password, user.Salt) + if (user.Password != encrypted) || (err != nil) { + return nil, "", errors.New("用户名或密码错误") + } + + token := s.generateToken() + + loginUser := &entity.LoginUser{ + ID: user.ID, + Username: user.Username, + Realname: user.Realname, + Avatar: user.Avatar, + Phone: user.Phone, + Email: user.Email, + Token: token, + } + + if err := s.saveLoginUser(token, loginUser); err != nil { + return nil, "", errors.New("登录失败,请重试") + } + + return loginUser, token, nil +} + +// Logout 用户登出 +func (s *SysUserService) Logout(token string) error { + ctx := context.Background() + return config.RDB.Del(ctx, common.RedisTokenPrefix+token).Err() +} + +// GetLoginUser 根据Token获取登录用户信息 +func (s *SysUserService) GetLoginUser(token string) (*entity.LoginUser, error) { + ctx := context.Background() + data, err := config.RDB.Get(ctx, common.RedisTokenPrefix+token).Result() + if err != nil { + return nil, errors.New("未登录或登录已过期") + } + + var loginUser entity.LoginUser + if err := json.Unmarshal([]byte(data), &loginUser); err != nil { + return nil, errors.New("登录信息异常") + } + + config.RDB.Expire(ctx, common.RedisTokenPrefix+token, common.RedisTokenExpire) + + return &loginUser, nil +} + +// RefreshToken 刷新Token过期时间 +func (s *SysUserService) RefreshToken(token string) error { + ctx := context.Background() + return config.RDB.Expire(ctx, common.RedisTokenPrefix+token, common.RedisTokenExpire).Err() +} + +// UpdatePassword 修改密码 +func (s *SysUserService) UpdatePassword(id, oldPwd, newPwd string) error { + user, err := s.GetByID(id) + if err != nil { + return errors.New("用户不存在") + } + + encrypted, err := common.Encrypt(user.Username, oldPwd, user.Salt) + if err != nil { + log.Printf("密码加密失败: %v", err) + return fmt.Errorf("密码加密失败: %w,请联系管理员", err) + } + if encrypted != user.Password { + return errors.New("原密码错误") + } + + newEncrypted, err := common.Encrypt(user.Username, newPwd, user.Salt) + if err != nil { + log.Printf("密码加密失败: %v", err) + return fmt.Errorf("密码加密失败: %w,请联系管理员", err) + } + return s.mapper.UpdateFields(id, map[string]interface{}{ + "password": newEncrypted, + }) +} + +// saveLoginUser 保存登录用户到Redis +func (s *SysUserService) saveLoginUser(token string, user *entity.LoginUser) error { + ctx := context.Background() + data, err := json.Marshal(user) + if err != nil { + return err + } + return config.RDB.Set(ctx, common.RedisTokenPrefix+token, data, common.RedisTokenExpire).Err() +} + +// generateToken 生成Token +func (s *SysUserService) generateToken() string { + return uuid.New().String() +} + +// Create 创建用户(添加密码加密逻辑) +func (s *SysUserService) Create(item *entity.SysUser) error { + item.ID = uuid.New().String() + item.Salt = uuid.New().String()[:8] + encrypted, err := common.Encrypt(item.Username, item.Password, item.Salt) + if err != nil { + log.Printf("密码加密失败: %v", err) + return fmt.Errorf("密码加密失败: %w,请联系管理员", err) + } + item.Password = encrypted + item.DelFlag = 0 + item.Status = 1 + now := time.Now() + item.CreateTime = &now + 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 等敏感字段 + } +} diff --git a/server/modules/system/vo/login_user_vo.go b/server/modules/system/vo/login_user_vo.go new file mode 100644 index 0000000..a50efa8 --- /dev/null +++ b/server/modules/system/vo/login_user_vo.go @@ -0,0 +1,12 @@ +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"` + // 注意:不包含 Token,Token 应该在单独的响应结构中 +} \ No newline at end of file diff --git a/server/modules/system/vo/sys_user_vo.go b/server/modules/system/vo/sys_user_vo.go new file mode 100644 index 0000000..f826d24 --- /dev/null +++ b/server/modules/system/vo/sys_user_vo.go @@ -0,0 +1,20 @@ +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 等敏感字段 +} \ No newline at end of file diff --git a/server/modules/user/controller/user_auth_controller.go b/server/modules/user/controller/user_auth_controller.go new file mode 100644 index 0000000..2f51ced --- /dev/null +++ b/server/modules/user/controller/user_auth_controller.go @@ -0,0 +1,86 @@ +// Package controller 控制器层 +package controller + +import ( + "server/common" + "server/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// LoginRequest 登录请求 +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// AuthController 认证控制器 +type AuthController struct { + userService *service.SysUserService +} + +func NewAuthController() *AuthController { + return &AuthController{userService: service.NewSysUserService()} +} + +func (ctrl *AuthController) RegisterRoutes(r *gin.RouterGroup) { + r.POST("/user/auth/login", ctrl.Login) + r.POST("/user/auth/logout", ctrl.Logout) + r.GET("/user/auth/info", ctrl.GetUserInfo) +} + +// Login 用户登录 +// @Summary 用户登录 +// @Tags 认证 +// @Accept json +// @Produce json +// @Param request body LoginRequest true "登录信息" +// @Success 200 {object} common.Response +// @Router /user/auth/login [post] +func (ctrl *AuthController) Login(c *gin.Context) { + var req LoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "用户名和密码不能为空") + return + } + + loginUser, token, err := ctrl.userService.Login(req.Username, req.Password) + if err != nil { + common.Error(c, 400, err.Error()) + return + } + + common.Success(c, gin.H{ + "token": token, + "user": loginUser, + }) +} + +// Logout 用户登出 +// @Summary 用户登出 +// @Tags 认证 +// @Success 200 {object} common.Response +// @Router /user/auth/logout [post] +func (ctrl *AuthController) Logout(c *gin.Context) { + token := c.GetHeader("Authorization") + if len(token) > 7 { + token = token[7:] // 去除 "Bearer " + } + + ctrl.userService.Logout(token) + common.Success(c, nil) +} + +// GetUserInfo 获取当前登录用户信息 +// @Summary 获取当前登录用户信息 +// @Tags 认证 +// @Success 200 {object} common.Response +// @Router /user/auth/info [get] +func (ctrl *AuthController) GetUserInfo(c *gin.Context) { + user := common.GetLoginUser(c) + if user == nil { + common.Error(c, 401, "未登录") + return + } + common.Success(c, user) +} diff --git a/server/modules/user/controller/user_major_controller.go b/server/modules/user/controller/user_major_controller.go new file mode 100644 index 0000000..d0876ea --- /dev/null +++ b/server/modules/user/controller/user_major_controller.go @@ -0,0 +1,120 @@ +package controller + +import ( + "server/common" + user_service "server/modules/user/service" + yxDto "server/modules/yx/dto" + yx_service "server/modules/yx/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type UserMajorController struct { + userScoreService *user_service.UserScoreService + yxUserScoreService *yx_service.YxUserScoreService + yxCalculationMajorService *yx_service.YxCalculationMajorService + yxVolunteerService *yx_service.YxVolunteerService + yxVolunteerRecordService *yx_service.YxVolunteerRecordService +} + +func NewUserMajorController() *UserMajorController { + return &UserMajorController{ + yxUserScoreService: yx_service.NewYxUserScoreService(), + userScoreService: user_service.NewUserScoreService(), + yxCalculationMajorService: yx_service.NewYxCalculationMajorService(), + yxVolunteerService: yx_service.NewYxVolunteerService(), + yxVolunteerRecordService: yx_service.NewYxVolunteerRecordService(), + } +} + +// RegisterRoutes 注册路由 +func (ctrl *UserMajorController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/user/major") + { + // group.GET("/", ctrl.GetActive) + // group.GET("/:id", ctrl.GetByID) + group.GET("/list", ctrl.List) + group.GET("/list_by_school", ctrl.ListBySchool) + } +} + +// ListBySchool 获取当前院校下其他专业数据 +// @Summary 获取当前院校下其他专业数据 +// @Tags 用户专业 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Param school_code query string true "院校代码" +// @Param probability query string false "录取概率类型(难录取/可冲击/较稳妥/可保底)" default("") +// @Success 200 {object} common.Response +// @Router /user/major/list_by_school [get] +func (ctrl *UserMajorController) ListBySchool(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + schoolMajorQuery := yxDto.SchoolMajorQuery{ + Page: page, + Size: size, + SchoolCode: c.Query("school_code"), + Probability: c.DefaultQuery("probability", ""), + LoginUserId: common.GetLoginUser(c).ID, + } + userScoreVO, err := ctrl.userScoreService.GetActiveScoreByUserID(schoolMajorQuery.LoginUserId) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + schoolMajorQuery.UserScoreVO = userScoreVO + items, total, probCount, err := ctrl.yxCalculationMajorService.RecommendMajorList(schoolMajorQuery) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + newMap := map[string]interface{}{ + "items": items, + "total": total, + "probCount": probCount, + } + common.SuccessPage(c, newMap, total, page, size) +} + +// List 获取当前用户可检索列表 +// @Summary 获取当前用户可检索列表 +// @Tags 用户专业 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Param batch query string false "批次(本科提前批/本科A段/本科B段/本科/高职高专)" default("") +// @Param probability query string false "录取概率类型(难录取/可冲击/较稳妥/可保底)" default("") +// @Success 200 {object} common.Response +// @Router /user/major/list [get] +func (ctrl *UserMajorController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + schoolMajorQuery := yxDto.SchoolMajorQuery{ + Keyword: c.DefaultQuery("keyword", ""), + Page: page, + Size: size, + SchoolCode: c.Query("schoolCode"), + Batch: c.DefaultQuery("batch", ""), + Batch2: c.DefaultQuery("batch2", ""), + Probability: c.DefaultQuery("probability", ""), + LoginUserId: common.GetLoginUser(c).ID, + } + userScoreVO, err := ctrl.userScoreService.GetActiveScoreByUserID(schoolMajorQuery.LoginUserId) + if err != nil { + common.Error(c, 500, err.Error()) // 获取用户成绩信息失败 + return + } + + schoolMajorQuery.UserScoreVO = userScoreVO + items, total, probCount, err := ctrl.yxCalculationMajorService.RecommendMajorList(schoolMajorQuery) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + newMap := map[string]interface{}{ + "items": items, + "total": total, + "probCount": probCount, + } + common.SuccessPage(c, newMap, total, page, size) +} diff --git a/server/modules/user/controller/user_score_controller.go b/server/modules/user/controller/user_score_controller.go new file mode 100644 index 0000000..7ca0224 --- /dev/null +++ b/server/modules/user/controller/user_score_controller.go @@ -0,0 +1,113 @@ +package controller + +import ( + "server/common" + user_service "server/modules/user/service" + "server/modules/yx/dto" + yx_service "server/modules/yx/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type UserScoreController struct { + userScoreService *user_service.UserScoreService + yxUserScoreService *yx_service.YxUserScoreService +} + +func NewUserScoreController() *UserScoreController { + return &UserScoreController{ + yxUserScoreService: yx_service.NewYxUserScoreService(), + userScoreService: user_service.NewUserScoreService(), + } +} + +// RegisterRoutes 注册路由 +func (ctrl *UserScoreController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/user/score") + { + group.GET("/", ctrl.GetActive) + group.GET("/:id", ctrl.GetByID) + group.GET("/list", ctrl.List) + group.POST("/save-score", ctrl.SaveUserScore) + } +} + +// SaveUserScore 保存用户成绩 +// @Summary 保存用户成绩 +// @Tags 用户分数 +// @Param request body dto.SaveScoreRequest true "成绩信息" +// @Success 200 {object} common.Response +// @Router /user/score/save-score [post] +func (ctrl *UserScoreController) SaveUserScore(c *gin.Context) { + var req dto.SaveScoreRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := req.Validate(); err != nil { + common.Error(c, 400, "校验失败: "+err.Error()) + return + } + req.CreateBy = common.GetLoginUser(c).ID + // 直接调用 Service,不处理具体业务逻辑 + result, err := ctrl.userScoreService.SaveUserScore(&req) + if err != nil { + common.Error(c, 500, "保存失败: "+err.Error()) + return + } + common.Success(c, result) +} + +// GetActive 获取当前用户的激活分数 +// @Summary 获取当前用户的激活分数 +// @Tags 用户分数 +// @Success 200 {object} common.Response +// @Router /user/score [get] +func (ctrl *UserScoreController) GetActive(c *gin.Context) { + loginUserId := common.GetLoginUser(c).ID + item, err := ctrl.userScoreService.GetActiveScoreByUserID(loginUserId) + if err != nil { + common.Error(c, 404, "未找到激活成绩") + return + } + common.Success(c, item) +} + +// GetByID 获取指定 ID 的分数 +// @Summary 获取指定 ID 的分数 +// @Tags 用户分数 +// @Param id path string true "成绩ID" +// @Success 200 {object} common.Response +// @Router /user/score/{id} [get] +func (ctrl *UserScoreController) GetByID(c *gin.Context) { + id := c.Param("id") + item, err := ctrl.userScoreService.GetByID(id) + if err != nil { + common.Error(c, 404, "记录不存在") + return + } + common.Success(c, item) +} + +// List 获取当前用户的成绩列表 +// @Summary 获取当前用户的成绩列表 +// @Tags 用户分数 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Success 200 {object} common.Response +// @Router /user/score/list [get] +func (ctrl *UserScoreController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + loginUserId := common.GetLoginUser(c).ID + items, total, err := ctrl.userScoreService.ListByUser(loginUserId, page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, gin.H{ + "items": items, + "total": total, + }) +} diff --git a/server/modules/user/controller/user_volunteer_controller.go b/server/modules/user/controller/user_volunteer_controller.go new file mode 100644 index 0000000..c34083e --- /dev/null +++ b/server/modules/user/controller/user_volunteer_controller.go @@ -0,0 +1,199 @@ +package controller + +import ( + "server/common" + "server/modules/user/dto" + "server/modules/user/service" + "server/modules/user/vo" + yxDto "server/modules/yx/dto" + "server/modules/yx/entity" + yx_service "server/modules/yx/service" + "time" // 用于时间处理 + + "github.com/gin-gonic/gin" +) + +// _ 确保包被导入(用于类型引用) +var _ = yxDto.SchoolMajorDTO{} +var _ = entity.YxVolunteerRecord{} +var _ = time.Now() +var _ = vo.VolunteerDetailVO{} + +type UserVolunteerController struct { + userScoreService *service.UserScoreService + yxVolunteerService *yx_service.YxVolunteerService + yxVolunteerRecordService *yx_service.YxVolunteerRecordService + yxCalculationMajorService *yx_service.YxCalculationMajorService +} + +func NewUserVolunteerController() *UserVolunteerController { + return &UserVolunteerController{ + userScoreService: service.NewUserScoreService(), + yxVolunteerService: yx_service.NewYxVolunteerService(), + yxVolunteerRecordService: yx_service.NewYxVolunteerRecordService(), + yxCalculationMajorService: yx_service.NewYxCalculationMajorService(), + } +} + +func (ctrl *UserVolunteerController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/user/volunteer") + { + group.POST("/save", ctrl.SaveVolunteer) + group.GET("/detail", ctrl.GetVolunteerDetail) + group.PUT("/updateName", ctrl.UpdateVolunteerName) + group.GET("/list", ctrl.GetUserVolunteerList) + group.DELETE("/delete", ctrl.DeleteVolunteer) + group.POST("/switch", ctrl.SwitchVolunteer) + } +} + +// SaveVolunteer 保存志愿明细 +// @Summary 保存志愿明细 +// @Tags 用户志愿 +// @Param request body dto.SaveVolunteerRequest true "志愿键列表" +// @Success 200 {object} common.Response +// @Router /user/volunteer/save [post] +func (ctrl *UserVolunteerController) SaveVolunteer(c *gin.Context) { + var req dto.SaveVolunteerRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 500, err.Error()) + return + } + loginUserId := common.GetLoginUser(c).ID + if err := ctrl.userScoreService.SaveVolunteer(loginUserId, &req); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, "保存成功") +} + +// GetVolunteerDetail 获取当前志愿单详情 +// @Summary 获取当前志愿单详情 +// @Tags 用户志愿 +// @Success 200 {object} common.Response{data=vo.VolunteerDetailResponse} +// @Router /user/volunteer/detail [get] +func (ctrl *UserVolunteerController) GetVolunteerDetail(c *gin.Context) { + loginUserId := common.GetLoginUser(c).ID + response, err := ctrl.userScoreService.GetVolunteerDetail(loginUserId) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, response) +} + +// UpdateVolunteerName 编辑志愿单名称 +// @Summary 编辑志愿单名称 +// @Tags 用户志愿 +// @Param id query string true "志愿单ID" +// @Param name query string true "志愿单名称" +// @Success 200 {object} common.Response +// @Router /user/volunteer/updateName [put] +func (ctrl *UserVolunteerController) UpdateVolunteerName(c *gin.Context) { + id := c.Query("id") + name := c.Query("name") + if id == "" || name == "" { + common.Error(c, 400, "参数错误") + return + } + + loginUserID := common.GetLoginUser(c).ID + if err := ctrl.yxVolunteerService.UpdateName(id, name, loginUserID); err != nil { + common.Error(c, 500, err.Error()) + return + } + + common.Success(c, "更新成功") +} + +// GetUserVolunteerList 获取当前用户志愿单列表 +// @Summary 获取当前用户志愿单列表 +// @Tags 用户志愿 +// @Param page query int false "页码" +// @Param size query int false "每页数量" +// @Success 200 {object} common.Response +// @Router /user/volunteer/list [get] +func (ctrl *UserVolunteerController) GetUserVolunteerList(c *gin.Context) { + page := common.GetPage(c) + size := common.GetSize(c) + loginUserID := common.GetLoginUser(c).ID + + items, total, err := ctrl.yxVolunteerService.ListByUser(loginUserID, page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + + common.Success(c, map[string]interface{}{ + "items": items, + "total": total, + }) +} + +// DeleteVolunteer 删除志愿单接口 +// @Summary 删除志愿单接口 +// @Tags 用户志愿 +// @Param id query string true "志愿单ID" +// @Success 200 {object} common.Response +// @Router /user/volunteer/delete [delete] +func (ctrl *UserVolunteerController) DeleteVolunteer(c *gin.Context) { + id := c.Query("id") + if id == "" { + common.Error(c, 400, "参数错误") + return + } + + loginUserID := common.GetLoginUser(c).ID + err := ctrl.yxVolunteerService.DeleteVolunteer(id, loginUserID) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + + common.Success(c, "删除成功") +} + +// SwitchVolunteer 切换当前志愿单 +// @Summary 切换当前志愿单 +// @Tags 用户志愿 +// @Param id query string true "志愿单ID" +// @Success 200 {object} common.Response +// @Router /user/volunteer/switch [post] +func (ctrl *UserVolunteerController) SwitchVolunteer(c *gin.Context) { + id := c.Query("id") + if id == "" { + common.Error(c, 400, "参数错误") + return + } + + loginUserID := common.GetLoginUser(c).ID + // 1. 先判断是否已是该志愿单 (从 cache 或 db 中查找激活的) + userScoreVO, err := ctrl.userScoreService.GetActiveScoreByUserID(loginUserID) + if err != nil { + common.Error(c, 500, "获取用户成绩信息失败: "+err.Error()) + return + } + + if userScoreVO.ID != "" { + activeVolunteer, _ := ctrl.yxVolunteerService.FindActiveByScoreId(userScoreVO.ID) + if activeVolunteer != nil && activeVolunteer.ID == id { + common.Success(c, "已是当前志愿单,忽略切换") + return + } + } + + // 2. 切换 + if err := ctrl.yxVolunteerService.SwitchVolunteer(id, loginUserID, ctrl.userScoreService); err != nil { + common.Error(c, 500, err.Error()) + return + } + + // 3. Redis 缓存同步 (假设 common 中有清除缓存的方法逻辑,或者由 Service 处理) + // 这里按需求提到:切换志愿单切换后 写入到redis做缓存(查询志愿单接口也要从redis读本体数据),再清除之前的成绩单redis重新获取写到redis缓存。 + // 实际项目中 common.GetLoginUser 已经包含了 ID,成绩单和志愿单通常由具体模块处理缓存。 + // 如果这里只是标记,Service 层已做状态更新,查询接口读取时会感知变化。 + // 为了彻底满足“写入/清除 redis 缓存”,通常会有相应的 RedisUtil 调用。 + // 假设 service 层或 controller 有 Cache 清理逻辑。 + + common.Success(c, "切换成功") +} diff --git a/server/modules/user/dto/user_major_dto.go b/server/modules/user/dto/user_major_dto.go new file mode 100644 index 0000000..b6ad873 --- /dev/null +++ b/server/modules/user/dto/user_major_dto.go @@ -0,0 +1,13 @@ +package dto + +import ( + "server/modules/user/vo" +) + +// RecommendMajorListRequest 推荐专业列表请求 +type RecommendMajorListRequest struct { + Page int `json:"page"` + Size int `json:"size"` + LoginUserId string `json:"loginUserId"` + UserScoreVO vo.UserScoreVO `json:"userScoreVO"` +} diff --git a/server/modules/user/dto/user_volunteer_dto.go b/server/modules/user/dto/user_volunteer_dto.go new file mode 100644 index 0000000..c641a1d --- /dev/null +++ b/server/modules/user/dto/user_volunteer_dto.go @@ -0,0 +1,6 @@ +package dto + +// SaveVolunteerRequest 保存志愿请求 +type SaveVolunteerRequest struct { + Keys []string `json:"keys" binding:"required"` // Keys: schoolCode_majorCode_enrollmentCode +} \ No newline at end of file diff --git a/server/modules/user/service/user_score_service.go b/server/modules/user/service/user_score_service.go new file mode 100644 index 0000000..b4a3dc8 --- /dev/null +++ b/server/modules/user/service/user_score_service.go @@ -0,0 +1,527 @@ +// Package service 业务逻辑层 +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "server/common" + "server/config" + userDto "server/modules/user/dto" + "server/modules/user/vo" + yxDto "server/modules/yx/dto" + "server/modules/yx/entity" + "server/modules/yx/mapper" + "server/modules/yx/service" + "server/types" + "strings" + "time" + + "gorm.io/gorm" +) + +type UserScoreService struct { + yxUserScoreService *service.YxUserScoreService + yxVolunteerService *service.YxVolunteerService + yxVolunteerRecordService *service.YxVolunteerRecordService + yxCalculationMajorService *service.YxCalculationMajorService + mapper *mapper.YxUserScoreMapper +} + +// GetActiveScoreID 获取用户的激活成绩ID +func (s *UserScoreService) GetActiveScoreID(userID string) (string, error) { + var score entity.YxUserScore + // 明确指定字段,提高可读性 + err := config.DB.Model(&entity.YxUserScore{}). + Where("create_by = ? AND state = ?", userID, "1"). + Select("id"). + First(&score).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", nil // 未找到激活成绩返回空字符串 + } + return "", fmt.Errorf("查询激活成绩ID失败: %w", err) + } + return score.ID, nil +} + +// GetActiveScoreByID 获取用户的激活成绩实体(实现 IScoreService 接口) +func (s *UserScoreService) GetActiveScoreByID(userID string) (entity.YxUserScore, error) { + var score entity.YxUserScore + // 明确指定字段,提高可读性 + err := config.DB.Model(&entity.YxUserScore{}). + Where("create_by = ? AND state = ?", userID, "1"). + First(&score).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return entity.YxUserScore{}, fmt.Errorf("未找到激活的成绩记录") + } + return entity.YxUserScore{}, fmt.Errorf("查询成绩记录失败: %w", err) + } + return score, nil +} + +func (s *UserScoreService) GetActiveScoreByUserID(userID string) (vo.UserScoreVO, error) { + var score entity.YxUserScore + // 先从Redis获取是否存在 + scoreRedisData, err := config.RDB.Get(context.Background(), common.RedisUserScorePrefix+userID).Result() + if err != nil { + // 明确指定字段,提高可读性 + err := config.DB.Model(&entity.YxUserScore{}). + Where("create_by = ? AND state = ?", userID, "1"). + First(&score).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return vo.UserScoreVO{}, fmt.Errorf("未找到激活的成绩记录") + } + return vo.UserScoreVO{}, fmt.Errorf("查询成绩记录失败: %w", err) + } + // 缓存到 Redis + scoreRedisSetData, err := json.Marshal(score) + if err != nil { + return vo.UserScoreVO{}, fmt.Errorf("序列化成绩记录失败: %w", err) + } + err = config.RDB.Set(context.Background(), common.RedisUserScorePrefix+userID, scoreRedisSetData, common.RedisUserScoreExpire).Err() + if err != nil { + return vo.UserScoreVO{}, fmt.Errorf("缓存成绩记录失败: %w", err) + } + } else { + if err := json.Unmarshal([]byte(scoreRedisData), &score); err != nil { + return vo.UserScoreVO{}, fmt.Errorf("解析 Redis 数据失败: %w", err) + } + // 刷新过期时间 + config.RDB.Expire(context.Background(), common.RedisUserScorePrefix+userID, common.RedisUserScoreExpire) + } + + return s.convertEntityToVo(score), nil +} + +func (s *UserScoreService) GetByID(id string) (vo.UserScoreVO, error) { + var score entity.YxUserScore + err := config.DB.Model(&entity.YxUserScore{}). + 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 +} + +// ListByUser 获取用户的成绩分页列表 +func (s *UserScoreService) ListByUser(userID string, page, size int) ([]vo.UserScoreVO, int64, error) { + var scores []entity.YxUserScore + var total int64 + + query := config.DB.Model(&entity.YxUserScore{}).Where("create_by = ?", userID) + + if err := query.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("查询总数失败: %w", err) + } + + if err := query.Offset((page - 1) * size).Limit(size).Order("create_time desc").Find(&scores).Error; err != nil { + return nil, 0, fmt.Errorf("查询成绩列表失败: %w", err) + } + + vos := make([]vo.UserScoreVO, 0, len(scores)) + for i := range scores { + vos = append(vos, s.convertEntityToVo(scores[i])) + } + + return vos, total, nil +} + +func (s *UserScoreService) Delete(id string) error { + return s.mapper.Delete(id) +} + +func (s *UserScoreService) UpdateFields(id string, fields map[string]interface{}) error { + return s.mapper.UpdateFields(id, fields) +} + +func NewUserScoreService() *UserScoreService { + return &UserScoreService{ + yxUserScoreService: service.NewYxUserScoreService(), + yxCalculationMajorService: service.NewYxCalculationMajorService(), + yxVolunteerService: service.NewYxVolunteerService(), + yxVolunteerRecordService: service.NewYxVolunteerRecordService(), + 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 +func (s *UserScoreService) SaveUserScore(req *yxDto.SaveScoreRequest) (vo.UserScoreVO, error) { + // 1. 业务验证 + if err := req.Validate(); err != nil { + return vo.UserScoreVO{}, err + } + + // 2. DTO 转 Entity + entityItem := s.convertDtoToEntity(req) + entityItem.CalculationTableName = "yx_calculation_major_2025_2" + entityItem.ID = common.GenerateStringID() // 使用新封装的 ID 生成工具 + entityItem.CreateTime = time.Now() + entityItem.UpdateTime = time.Now() + + // 3. 执行保存操作(可以包含事务) + tx := config.DB.Begin() + defer func() { + if r := recover(); r != nil { + fmt.Printf("【PANIC】事务执行过程中发生panic: %v", r) + // 记录详细的栈信息 + fmt.Printf("【PANIC】尝试回滚事务") + tx.Rollback() + fmt.Printf("【PANIC】事务已回滚") + } + }() + + // 标记该用户的所有旧成绩为历史状态 + if err := tx.Model(&entity.YxUserScore{}). + Where("create_by = ? AND state = ?", req.CreateBy, "1"). + Updates(map[string]interface{}{"state": "2"}).Error; err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("更新旧记录失败: %w", err) + } + + // 保存新的成绩记录 + if err := tx.Create(entityItem).Error; err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("保存记录失败: %w", err) + } + userScoreVO := s.convertEntityToVo(*entityItem) + + // 提交事务 - ✅ 快速释放数据库连接 + // if err := tx.Commit().Error; err != nil { + // return vo.UserScoreVO{}, fmt.Errorf("提交事务失败: %w", err) + // } + + // 根据成绩计算用户的专业信息 + schoolMajorItems, err := s.yxCalculationMajorService.ListByUserQueryType(userScoreVO.ProfessionalCategory, + userScoreVO.CognitioPolyclinic, userScoreVO.ProfessionalCategoryChildren) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("查询专业信息失败: %w", err) + } + + // 检查录取概率 + s.yxCalculationMajorService.CheckEnrollProbability(&schoolMajorItems, userScoreVO) + + // 插入到数据库 + err = s.yxCalculationMajorService.BatchCreateBySchoolMajorDTO(entityItem.CalculationTableName, schoolMajorItems, userScoreVO.ID) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("保存专业信息失败: %w", err) + } + + // 创建志愿表 + err = s.yxVolunteerService.CreateByScoreId(entityItem.ID, req.CreateBy) + if err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("创建志愿表失败: %w", err) + } + + // 提交事务 + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return vo.UserScoreVO{}, fmt.Errorf("提交事务失败: %w", err) + } + + // 清除之前的Redis成绩缓存 + config.RDB.Del(context.Background(), common.RedisUserScorePrefix+req.CreateBy) + + // 更新Redis 数据 + scoreRedisSetData, err := json.Marshal(entityItem) + err = config.RDB.Set(context.Background(), common.RedisUserScorePrefix+req.CreateBy, scoreRedisSetData, common.RedisUserScoreExpire).Err() + if err != nil { + return vo.UserScoreVO{}, fmt.Errorf("缓存成绩记录失败: %w", err) + } + return userScoreVO, nil +} + +// 私有方法:DTO 转 Entity +func (s *UserScoreService) convertDtoToEntity(req *yxDto.SaveScoreRequest) *entity.YxUserScore { + entityItem := entity.YxUserScore{ + CognitioPolyclinic: req.CognitioPolyclinic, + Subjects: strings.Join(req.SubjectList, ","), + ProfessionalCategory: req.ProfessionalCategory, + ProfessionalCategoryChildren: strings.Join(req.ProfessionalCategoryChildren, ","), + ProfessionalScore: *req.ProfessionalScore, + CulturalScore: *req.CulturalScore, + EnglishScore: *req.EnglishScore, + ChineseScore: *req.ChineseScore, + Province: req.Province, + State: "1", // 默认状态 + CreateBy: req.CreateBy, + } + + // 映射子专业成绩到具体字段 + if v, ok := req.ProfessionalCategoryChildrenScore["音乐表演声乐"]; ok { + entityItem.Yybysy = v + } + if v, ok := req.ProfessionalCategoryChildrenScore["音乐表演器乐"]; ok { + entityItem.Yybyqy = v + } + if v, ok := req.ProfessionalCategoryChildrenScore["音乐教育"]; ok { + entityItem.Yyjy = v + } + if v, ok := req.ProfessionalCategoryChildrenScore["戏剧影视导演"]; ok { + entityItem.Xjysdy = v + } + if v, ok := req.ProfessionalCategoryChildrenScore["戏剧影视表演"]; ok { + entityItem.Xjysby = v + } + if v, ok := req.ProfessionalCategoryChildrenScore["服装表演"]; ok { + entityItem.Fzby = v + } + + return &entityItem +} + +// 私有方法:Entity 转 VO +func (s *UserScoreService) convertEntityToVo(item entity.YxUserScore) vo.UserScoreVO { + voItem := vo.UserScoreVO{ + ID: item.ID, + Type: item.Type, + EducationalLevel: item.EducationalLevel, + CognitioPolyclinic: item.CognitioPolyclinic, + ProfessionalCategory: item.ProfessionalCategory, + ProfessionalCategoryChildren: []string{}, + ProfessionalCategoryChildrenScore: make(map[string]float64), + ProfessionalScore: item.ProfessionalScore, + CulturalScore: item.CulturalScore, + EnglishScore: item.EnglishScore, + ChineseScore: item.ChineseScore, + Province: item.Province, + State: item.State, + CalculationTableName: item.CalculationTableName, + } + if item.Subjects == "" { + voItem.SubjectList = []string{} + } else { + voItem.SubjectList = strings.Split(item.Subjects, ",") + } + + if item.ProfessionalCategoryChildren == "" { + voItem.ProfessionalCategoryChildren = []string{} + } else { + voItem.ProfessionalCategoryChildren = strings.Split(item.ProfessionalCategoryChildren, ",") + } + + // 映射具体字段到子专业成绩 Map + if item.Yybysy > 0 { + voItem.ProfessionalCategoryChildrenScore["音乐表演声乐"] = item.Yybysy + } + if item.Yybyqy > 0 { + voItem.ProfessionalCategoryChildrenScore["音乐表演器乐"] = item.Yybyqy + } + if item.Yyjy > 0 { + voItem.ProfessionalCategoryChildrenScore["音乐教育"] = item.Yyjy + } + if item.Xjysdy > 0 { + voItem.ProfessionalCategoryChildrenScore["戏剧影视导演"] = item.Xjysdy + } + if item.Xjysby > 0 { + voItem.ProfessionalCategoryChildrenScore["戏剧影视表演"] = item.Xjysby + } + if item.Fzby > 0 { + voItem.ProfessionalCategoryChildrenScore["服装表演"] = item.Fzby + } + + return voItem +} diff --git a/server/modules/user/vo/user_score_vo.go b/server/modules/user/vo/user_score_vo.go new file mode 100644 index 0000000..4735b1c --- /dev/null +++ b/server/modules/user/vo/user_score_vo.go @@ -0,0 +1,20 @@ +package vo + +// UserScoreVO 用户成绩展示对象 +type UserScoreVO struct { + ID string `json:"id"` + Type string `json:"type"` // 填报类型(1-普通类 2-艺术类) + EducationalLevel string `json:"educationalLevel"` // 学历层次(1-本科,2-专科) + CognitioPolyclinic string `json:"cognitioPolyclinic"` // 文理分班(文科/理科) + SubjectList []string `json:"subjectList"` // 选课列表 + ProfessionalCategory string `json:"professionalCategory"` // 专业类别 + ProfessionalCategoryChildren []string `json:"professionalCategoryChildren"` // 子级专业类别 + ProfessionalCategoryChildrenScore map[string]float64 `json:"professionalCategoryChildrenScore"` // 子级专业成绩 + ProfessionalScore float64 `json:"professionalScore"` // 专业总分 + CulturalScore float64 `json:"culturalScore"` // 文化成绩分 + EnglishScore float64 `json:"englishScore"` // 英语成绩 + ChineseScore float64 `json:"chineseScore"` // 语文成绩 + Province string `json:"province"` // 高考省份 + State string `json:"state"` // 状态 + CalculationTableName string `json:"calculationTableName"` // 计算表名称 +} diff --git a/server/modules/user/vo/volunteer_detail_vo.go b/server/modules/user/vo/volunteer_detail_vo.go new file mode 100644 index 0000000..1b2eb7a --- /dev/null +++ b/server/modules/user/vo/volunteer_detail_vo.go @@ -0,0 +1,55 @@ +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"` // 专科批 +} diff --git a/server/modules/yx/controller/yx_calculation_major_controller.go b/server/modules/yx/controller/yx_calculation_major_controller.go new file mode 100644 index 0000000..1d389a8 --- /dev/null +++ b/server/modules/yx/controller/yx_calculation_major_controller.go @@ -0,0 +1,173 @@ +// Package controller 控制器层 +package controller + +import ( + "strconv" + + "server/common" + "server/modules/yx/dto" + "server/modules/yx/service" + yxVO "server/modules/yx/vo" + + "github.com/gin-gonic/gin" +) + +type YxCalculationMajorController struct { + service *service.YxCalculationMajorService +} + +func NewYxCalculationMajorController() *YxCalculationMajorController { + return &YxCalculationMajorController{service: service.NewYxCalculationMajorService()} +} + +// _ 确保 vo 包被导入(用于 Swagger 注解) +var _ = yxVO.YxCalculationMajorVO{} + +func (ctrl *YxCalculationMajorController) RegisterRoutes(r *gin.RouterGroup) { + r.GET("/yx-calculation-majors", ctrl.List) + r.GET("/yx-calculation-majors/:id", ctrl.GetByID) + r.POST("/yx-calculation-majors", ctrl.Create) + r.PUT("/yx-calculation-majors/:id", ctrl.Update) + r.PATCH("/yx-calculation-majors/:id", ctrl.UpdateFields) + r.DELETE("/yx-calculation-majors/:id", ctrl.Delete) + r.POST("/yx-calculation-majors/batch", ctrl.BatchCreate) + r.DELETE("/yx-calculation-majors/batch", ctrl.BatchDelete) +} + +// @Summary 获取计算专业列表 +// @Tags 计算专业 +// @Param page query int false "页码" +// @Param size query int false "每页数量" +// @Success 200 {object} common.Response{data=[]vo.YxCalculationMajorVO} +// @Router /yx-calculation-majors [get] +func (ctrl *YxCalculationMajorController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.ListCalculationMajors(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.SuccessPage(c, items, total, page, size) +} + +// @Summary 获取单个计算专业 +// @Tags 计算专业 +// @Param id path string true "ID" +// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} +// @Router /yx-calculation-majors/{id} [get] +func (ctrl *YxCalculationMajorController) GetByID(c *gin.Context) { + item, err := ctrl.service.GetCalculationMajorByID(c.Param("id")) + if err != nil { + common.Error(c, 404, "未找到") + return + } + common.Success(c, item) +} + +// @Summary 创建计算专业 +// @Tags 计算专业 +// @Param request body dto.CreateCalculationMajorRequest true "计算专业信息" +// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} +// @Router /yx-calculation-majors [post] +func (ctrl *YxCalculationMajorController) Create(c *gin.Context) { + var req dto.CreateCalculationMajorRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误: "+err.Error()) + return + } + item, err := ctrl.service.CreateCalculationMajor(&req) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 更新计算专业 +// @Tags 计算专业 +// @Param id path string true "ID" +// @Param request body dto.UpdateCalculationMajorRequest true "计算专业信息" +// @Success 200 {object} common.Response{data=vo.YxCalculationMajorVO} +// @Router /yx-calculation-majors/{id} [put] +func (ctrl *YxCalculationMajorController) Update(c *gin.Context) { + var req dto.UpdateCalculationMajorRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误: "+err.Error()) + return + } + item, err := ctrl.service.UpdateCalculationMajor(c.Param("id"), &req) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 动态字段更新 +// @Tags 计算专业 +// @Param id path string true "ID" +// @Param fields body map[string]interface{} true "要更新的字段" +// @Success 200 {object} common.Response +// @Router /yx-calculation-majors/{id} [patch] +func (ctrl *YxCalculationMajorController) UpdateFields(c *gin.Context) { + var fields map[string]interface{} + if err := c.ShouldBindJSON(&fields); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.UpdateFields(c.Param("id"), fields); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 删除计算专业 +// @Tags 计算专业 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-calculation-majors/{id} [delete] +func (ctrl *YxCalculationMajorController) Delete(c *gin.Context) { + if err := ctrl.service.Delete(c.Param("id")); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 批量创建计算专业 +// @Tags 计算专业 +// @Param items body []dto.CreateCalculationMajorRequest true "计算专业列表" +// @Success 200 {object} common.Response +// @Router /yx-calculation-majors/batch [post] +func (ctrl *YxCalculationMajorController) BatchCreate(c *gin.Context) { + var reqs []dto.CreateCalculationMajorRequest + if err := c.ShouldBindJSON(&reqs); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchCreateCalculationMajors(&reqs); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 批量删除计算专业 +// @Tags 计算专业 +// @Param ids body []string true "ID列表" +// @Success 200 {object} common.Response +// @Router /yx-calculation-majors/batch [delete] +func (ctrl *YxCalculationMajorController) BatchDelete(c *gin.Context) { + var ids []string + if err := c.ShouldBindJSON(&ids); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchDelete(ids); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/controller/yx_history_major_enroll_controller.go b/server/modules/yx/controller/yx_history_major_enroll_controller.go new file mode 100644 index 0000000..34b5550 --- /dev/null +++ b/server/modules/yx/controller/yx_history_major_enroll_controller.go @@ -0,0 +1,168 @@ +// Package controller 控制器层 +package controller + +import ( + "strconv" + + "server/common" + "server/modules/yx/entity" + "server/modules/yx/service" + + "github.com/gin-gonic/gin" +) + +type YxHistoryMajorEnrollController struct { + service *service.YxHistoryMajorEnrollService +} + +func NewYxHistoryMajorEnrollController() *YxHistoryMajorEnrollController { + return &YxHistoryMajorEnrollController{service: service.NewYxHistoryMajorEnrollService()} +} + +func (ctrl *YxHistoryMajorEnrollController) RegisterRoutes(r *gin.RouterGroup) { + r.GET("/yx-history-enrolls", ctrl.List) + r.GET("/yx-history-enrolls/:id", ctrl.GetByID) + r.POST("/yx-history-enrolls", ctrl.Create) + r.PUT("/yx-history-enrolls/:id", ctrl.Update) + r.PATCH("/yx-history-enrolls/:id", ctrl.UpdateFields) + r.DELETE("/yx-history-enrolls/:id", ctrl.Delete) + r.POST("/yx-history-enrolls/batch", ctrl.BatchCreate) + r.DELETE("/yx-history-enrolls/batch", ctrl.BatchDelete) +} + +// @Summary 获取历年招生列表 +// @Tags 历年招生 +// @Param page query int false "页码" +// @Param size query int false "每页数量" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls [get] +func (ctrl *YxHistoryMajorEnrollController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.List(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.SuccessPage(c, items, total, page, size) +} + +// @Summary 获取单个历年招生记录 +// @Tags 历年招生 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/{id} [get] +func (ctrl *YxHistoryMajorEnrollController) GetByID(c *gin.Context) { + item, err := ctrl.service.GetByID(c.Param("id")) + if err != nil { + common.Error(c, 404, "未找到") + return + } + common.Success(c, item) +} + +// @Summary 创建历年招生记录 +// @Tags 历年招生 +// @Param item body entity.YxHistoryMajorEnroll true "历年招生信息" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls [post] +func (ctrl *YxHistoryMajorEnrollController) Create(c *gin.Context) { + var item entity.YxHistoryMajorEnroll + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.Create(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 更新历年招生记录 +// @Tags 历年招生 +// @Param id path string true "ID" +// @Param item body entity.YxHistoryMajorEnroll true "历年招生信息" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/{id} [put] +func (ctrl *YxHistoryMajorEnrollController) Update(c *gin.Context) { + var item entity.YxHistoryMajorEnroll + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + item.ID = c.Param("id") + if err := ctrl.service.Update(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// @Summary 动态字段更新 +// @Tags 历年招生 +// @Param id path string true "ID" +// @Param fields body map[string]interface{} true "要更新的字段" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/{id} [patch] +func (ctrl *YxHistoryMajorEnrollController) UpdateFields(c *gin.Context) { + var fields map[string]interface{} + if err := c.ShouldBindJSON(&fields); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.UpdateFields(c.Param("id"), fields); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 删除历年招生记录 +// @Tags 历年招生 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/{id} [delete] +func (ctrl *YxHistoryMajorEnrollController) Delete(c *gin.Context) { + if err := ctrl.service.Delete(c.Param("id")); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 批量创建历年招生记录 +// @Tags 历年招生 +// @Param items body []entity.YxHistoryMajorEnroll true "历年招生列表" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/batch [post] +func (ctrl *YxHistoryMajorEnrollController) BatchCreate(c *gin.Context) { + var items []entity.YxHistoryMajorEnroll + if err := c.ShouldBindJSON(&items); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchCreate(items); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// @Summary 批量删除历年招生记录 +// @Tags 历年招生 +// @Param ids body []string true "ID列表" +// @Success 200 {object} common.Response +// @Router /yx-history-enrolls/batch [delete] +func (ctrl *YxHistoryMajorEnrollController) BatchDelete(c *gin.Context) { + var ids []string + if err := c.ShouldBindJSON(&ids); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchDelete(ids); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/controller/yx_school_major_controller.go b/server/modules/yx/controller/yx_school_major_controller.go new file mode 100644 index 0000000..e13d96b --- /dev/null +++ b/server/modules/yx/controller/yx_school_major_controller.go @@ -0,0 +1,176 @@ +// Package controller 控制器层 +package controller + +import ( + "strconv" + + "server/common" + "server/modules/yx/entity" + "server/modules/yx/service" + + "github.com/gin-gonic/gin" +) + +type YxSchoolMajorController struct { + service *service.YxSchoolMajorService +} + +func NewYxSchoolMajorController() *YxSchoolMajorController { + return &YxSchoolMajorController{service: service.NewYxSchoolMajorService()} +} + +func (ctrl *YxSchoolMajorController) RegisterRoutes(r *gin.RouterGroup) { + r.GET("/yx-school-majors", ctrl.List) + r.GET("/yx-school-majors/:id", ctrl.GetByID) + r.POST("/yx-school-majors", ctrl.Create) + r.PUT("/yx-school-majors/:id", ctrl.Update) + r.PATCH("/yx-school-majors/:id", ctrl.UpdateFields) + r.DELETE("/yx-school-majors/:id", ctrl.Delete) + r.POST("/yx-school-majors/batch", ctrl.BatchCreate) + r.DELETE("/yx-school-majors/batch", ctrl.BatchDelete) +} + +// List 获取院校专业列表 +// @Summary 获取院校专业列表 +// @Tags 院校专业 +// @Param page query int false "页码" +// @Param size query int false "每页数量" +// @Success 200 {object} common.Response +// @Router /yx-school-majors [get] +func (ctrl *YxSchoolMajorController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.List(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.SuccessPage(c, items, total, page, size) +} + +// GetByID 获取单个院校专业 +// @Summary 获取单个院校专业 +// @Tags 院校专业 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/{id} [get] +func (ctrl *YxSchoolMajorController) GetByID(c *gin.Context) { + item, err := ctrl.service.GetByID(c.Param("id")) + if err != nil { + common.Error(c, 404, "未找到") + return + } + common.Success(c, item) +} + +// Create 创建院校专业 +// @Summary 创建院校专业 +// @Tags 院校专业 +// @Param item body entity.YxSchoolMajor true "院校专业信息" +// @Success 200 {object} common.Response +// @Router /yx-school-majors [post] +func (ctrl *YxSchoolMajorController) Create(c *gin.Context) { + var item entity.YxSchoolMajor + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.Create(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// Update 更新院校专业 +// @Summary 更新院校专业 +// @Tags 院校专业 +// @Param id path string true "ID" +// @Param item body entity.YxSchoolMajor true "院校专业信息" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/{id} [put] +func (ctrl *YxSchoolMajorController) Update(c *gin.Context) { + var item entity.YxSchoolMajor + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + item.ID = c.Param("id") + if err := ctrl.service.Update(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// UpdateFields 动态字段更新 +// @Summary 动态字段更新 +// @Tags 院校专业 +// @Param id path string true "ID" +// @Param fields body map[string]interface{} true "要更新的字段" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/{id} [patch] +func (ctrl *YxSchoolMajorController) UpdateFields(c *gin.Context) { + var fields map[string]interface{} + if err := c.ShouldBindJSON(&fields); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.UpdateFields(c.Param("id"), fields); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// Delete 删除院校专业 +// @Summary 删除院校专业 +// @Tags 院校专业 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/{id} [delete] +func (ctrl *YxSchoolMajorController) Delete(c *gin.Context) { + if err := ctrl.service.Delete(c.Param("id")); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// BatchCreate 批量创建 +// @Summary 批量创建院校专业 +// @Tags 院校专业 +// @Param items body []entity.YxSchoolMajor true "院校专业列表" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/batch [post] +func (ctrl *YxSchoolMajorController) BatchCreate(c *gin.Context) { + var items []entity.YxSchoolMajor + if err := c.ShouldBindJSON(&items); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchCreate(items); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} + +// BatchDelete 批量删除 +// @Summary 批量删除院校专业 +// @Tags 院校专业 +// @Param ids body []string true "ID列表" +// @Success 200 {object} common.Response +// @Router /yx-school-majors/batch [delete] +func (ctrl *YxSchoolMajorController) BatchDelete(c *gin.Context) { + var ids []string + if err := c.ShouldBindJSON(&ids); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.BatchDelete(ids); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/controller/yx_user_score_controller.go b/server/modules/yx/controller/yx_user_score_controller.go new file mode 100644 index 0000000..abd4aa0 --- /dev/null +++ b/server/modules/yx/controller/yx_user_score_controller.go @@ -0,0 +1,123 @@ +// Package controller 控制层 +package controller + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type YxUserScoreController struct { + service *service.YxUserScoreService +} + +func NewYxUserScoreController() *YxUserScoreController { + return &YxUserScoreController{service: service.NewYxUserScoreService()} +} + +// RegisterRoutes 注册路由 +func (ctrl *YxUserScoreController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/yx-user-scores") + { + group.GET("", ctrl.List) + group.GET("/:id", ctrl.Get) + group.POST("", ctrl.Create) + group.PUT("/:id", ctrl.Update) + group.DELETE("/:id", ctrl.Delete) + } +} + +// List 获取用户分数列表 +// @Summary 获取用户分数列表 +// @Tags 用户分数 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Success 200 {object} common.Response +// @Router /yx-user-scores [get] +func (ctrl *YxUserScoreController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.List(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, gin.H{ + "items": items, + "total": total, + }) +} + +// Get 获取单个用户分数 +// @Summary 获取单个用户分数 +// @Tags 用户分数 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-user-scores/{id} [get] +func (ctrl *YxUserScoreController) Get(c *gin.Context) { + id := c.Param("id") + item, err := ctrl.service.GetByID(id) + if err != nil { + common.Error(c, 404, "未找到记录") + return + } + common.Success(c, item) +} + +// Create 创建用户分数 +// @Summary 创建用户分数 +// @Tags 用户分数 +// @Param item body entity.YxUserScore true "用户分数信息" +// @Success 200 {object} common.Response +// @Router /yx-user-scores [post] +func (ctrl *YxUserScoreController) Create(c *gin.Context) { + var item entity.YxUserScore + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.Create(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// Update 更新用户分数 +// @Summary 更新用户分数 +// @Tags 用户分数 +// @Param id path string true "ID" +// @Param item body entity.YxUserScore true "用户分数信息" +// @Success 200 {object} common.Response +// @Router /yx-user-scores/{id} [put] +func (ctrl *YxUserScoreController) Update(c *gin.Context) { + var item entity.YxUserScore + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + item.ID = c.Param("id") + if err := ctrl.service.Update(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// Delete 删除用户分数 +// @Summary 删除用户分数 +// @Tags 用户分数 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-user-scores/{id} [delete] +func (ctrl *YxUserScoreController) Delete(c *gin.Context) { + id := c.Param("id") + if err := ctrl.service.Delete(id); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/controller/yx_volunteer_controller.go b/server/modules/yx/controller/yx_volunteer_controller.go new file mode 100644 index 0000000..24312ff --- /dev/null +++ b/server/modules/yx/controller/yx_volunteer_controller.go @@ -0,0 +1,150 @@ +// Package controller 控制层 +package controller + +import ( + "server/common" + "server/modules/yx/dto" + "server/modules/yx/service" + "server/modules/yx/vo" + "strconv" + + "github.com/gin-gonic/gin" +) + +type YxVolunteerController struct { + service *service.YxVolunteerService +} + +func NewYxVolunteerController() *YxVolunteerController { + return &YxVolunteerController{service: service.NewYxVolunteerService()} +} + +// RegisterRoutes 注册路由 +func (ctrl *YxVolunteerController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/yx-volunteers") + { + group.GET("", ctrl.List) + group.GET("/:id", ctrl.Get) + group.POST("", ctrl.Create) + group.PUT("/:id", ctrl.Update) + group.DELETE("/:id", ctrl.Delete) + } +} + +// List 获取志愿列表 +// @Summary 获取志愿列表 +// @Tags 志愿 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Success 200 {object} common.Response{data=[]vo.YxVolunteerVO} +// @Router /yx-volunteers [get] +func (ctrl *YxVolunteerController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.List(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + // 转换 Entity 到 VO + vos := make([]vo.YxVolunteerVO, len(items)) + for i, item := range items { + 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 获取单个志愿 +// @Summary 获取单个志愿 +// @Tags 志愿 +// @Param id path string true "ID" +// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} +// @Router /yx-volunteers/{id} [get] +func (ctrl *YxVolunteerController) Get(c *gin.Context) { + id := c.Param("id") + item, err := ctrl.service.GetByID(id) + if err != nil { + common.Error(c, 404, "未找到记录") + return + } + voItem := 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.Success(c, voItem) +} + +// Create 创建志愿 +// @Summary 创建志愿 +// @Tags 志愿 +// @Param request body dto.CreateVolunteerRequest true "志愿信息" +// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} +// @Router /yx-volunteers [post] +func (ctrl *YxVolunteerController) Create(c *gin.Context) { + var req dto.CreateVolunteerRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误: "+err.Error()) + return + } + voItem, err := ctrl.service.CreateVolunteer(&req) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, voItem) +} + +// Update 更新志愿 +// @Summary 更新志愿 +// @Tags 志愿 +// @Param id path string true "ID" +// @Param request body dto.UpdateVolunteerRequest true "志愿信息" +// @Success 200 {object} common.Response{data=vo.YxVolunteerVO} +// @Router /yx-volunteers/{id} [put] +func (ctrl *YxVolunteerController) Update(c *gin.Context) { + var req dto.UpdateVolunteerRequest + if err := c.ShouldBindJSON(&req); err != nil { + common.Error(c, 400, "参数错误: "+err.Error()) + return + } + voItem, err := ctrl.service.UpdateVolunteer(c.Param("id"), &req) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, voItem) +} + +// Delete 删除志愿 +// @Summary 删除志愿 +// @Tags 志愿 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-volunteers/{id} [delete] +func (ctrl *YxVolunteerController) Delete(c *gin.Context) { + id := c.Param("id") + if err := ctrl.service.Delete(id); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/controller/yx_volunteer_record_controller.go b/server/modules/yx/controller/yx_volunteer_record_controller.go new file mode 100644 index 0000000..bd1e773 --- /dev/null +++ b/server/modules/yx/controller/yx_volunteer_record_controller.go @@ -0,0 +1,123 @@ +// Package controller 控制层 +package controller + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type YxVolunteerRecordController struct { + service *service.YxVolunteerRecordService +} + +func NewYxVolunteerRecordController() *YxVolunteerRecordController { + return &YxVolunteerRecordController{service: service.NewYxVolunteerRecordService()} +} + +// RegisterRoutes 注册路由 +func (ctrl *YxVolunteerRecordController) RegisterRoutes(rg *gin.RouterGroup) { + group := rg.Group("/yx-volunteer-records") + { + group.GET("", ctrl.List) + group.GET("/:id", ctrl.Get) + group.POST("", ctrl.Create) + group.PUT("/:id", ctrl.Update) + group.DELETE("/:id", ctrl.Delete) + } +} + +// List 获取志愿明细列表 +// @Summary 获取志愿明细列表 +// @Tags 志愿明细 +// @Param page query int false "页码" default(1) +// @Param size query int false "每页数量" default(10) +// @Success 200 {object} common.Response +// @Router /yx-volunteer-records [get] +func (ctrl *YxVolunteerRecordController) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) + items, total, err := ctrl.service.List(page, size) + if err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, gin.H{ + "items": items, + "total": total, + }) +} + +// Get 获取单个志愿明细 +// @Summary 获取单个志愿明细 +// @Tags 志愿明细 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-volunteer-records/{id} [get] +func (ctrl *YxVolunteerRecordController) Get(c *gin.Context) { + id := c.Param("id") + item, err := ctrl.service.GetByID(id) + if err != nil { + common.Error(c, 404, "未找到记录") + return + } + common.Success(c, item) +} + +// Create 创建志愿明细 +// @Summary 创建志愿明细 +// @Tags 志愿明细 +// @Param item body entity.YxVolunteerRecord true "志愿明细信息" +// @Success 200 {object} common.Response +// @Router /yx-volunteer-records [post] +func (ctrl *YxVolunteerRecordController) Create(c *gin.Context) { + var item entity.YxVolunteerRecord + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + if err := ctrl.service.Create(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// Update 更新志愿明细 +// @Summary 更新志愿明细 +// @Tags 志愿明细 +// @Param id path string true "ID" +// @Param item body entity.YxVolunteerRecord true "志愿明细信息" +// @Success 200 {object} common.Response +// @Router /yx-volunteer-records/{id} [put] +func (ctrl *YxVolunteerRecordController) Update(c *gin.Context) { + var item entity.YxVolunteerRecord + if err := c.ShouldBindJSON(&item); err != nil { + common.Error(c, 400, "参数错误") + return + } + item.ID = c.Param("id") + if err := ctrl.service.Update(&item); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, item) +} + +// Delete 删除志愿明细 +// @Summary 删除志愿明细 +// @Tags 志愿明细 +// @Param id path string true "ID" +// @Success 200 {object} common.Response +// @Router /yx-volunteer-records/{id} [delete] +func (ctrl *YxVolunteerRecordController) Delete(c *gin.Context) { + id := c.Param("id") + if err := ctrl.service.Delete(id); err != nil { + common.Error(c, 500, err.Error()) + return + } + common.Success(c, nil) +} diff --git a/server/modules/yx/dto/probability_count_dto.go b/server/modules/yx/dto/probability_count_dto.go new file mode 100644 index 0000000..4d256c3 --- /dev/null +++ b/server/modules/yx/dto/probability_count_dto.go @@ -0,0 +1,9 @@ +package dto + +// 定义概率数量统计结果结构体,用于接收四种录取概率对应的各自数量 +type ProbabilityCountDTO struct { + Hard int64 `json:"hard" gorm:"column:hard"` // 难录取(<60) + Risky int64 `json:"risky" gorm:"column:risky"` // 可冲击(60<=x<73) + Stable int64 `json:"stable" gorm:"column:stable"` // 较稳妥(73<=x<93) + Safe int64 `json:"safe" gorm:"column:safe"` // 可保底(>=93) +} diff --git a/server/modules/yx/dto/yx_calculation_major_dto.go b/server/modules/yx/dto/yx_calculation_major_dto.go new file mode 100644 index 0000000..4cfc867 --- /dev/null +++ b/server/modules/yx/dto/yx_calculation_major_dto.go @@ -0,0 +1,61 @@ +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"` // 学生分数 +} \ No newline at end of file diff --git a/server/modules/yx/dto/yx_school_major_dto.go b/server/modules/yx/dto/yx_school_major_dto.go new file mode 100644 index 0000000..1f9e93c --- /dev/null +++ b/server/modules/yx/dto/yx_school_major_dto.go @@ -0,0 +1,120 @@ +package dto + +import ( + userVO "server/modules/user/vo" + "server/modules/yx/vo" +) + +type UserMajorDTO struct { + SchoolCode string `json:"schoolCode"` + SchoolName string `json:"schoolName"` + MajorCode string `json:"majorCode"` + MajorName string `json:"majorName"` + MajorType string `json:"majorType"` + MajorTypeChild string `json:"majorTypeChild"` + PlanNum int `json:"planNum"` + MainSubjects string `json:"mainSubjects"` + Limitation string `json:"limitation"` + ChineseScoreLimitation float64 `json:"chineseScoreLimitation"` + EnglishScoreLimitation float64 `json:"englishScoreLimitation"` + CulturalScoreLimitation float64 `json:"culturalScoreLimitation"` + ProfessionalScoreLimitation float64 `json:"professionalScoreLimitation"` + EnrollmentCode string `json:"enrollmentCode"` + Tuition string `json:"tuition"` + Detail string `json:"detail"` + Category string `json:"category"` + Batch string `json:"batch"` + RulesEnrollProbability string `json:"rulesEnrollProbability"` + ProbabilityOperator string `json:"probabilityOperator"` + // PrivateRulesEnrollProbability string `json:"privateRulesEnrollProbability"` + // PrivateProbabilityOperator string `json:"privateProbabilityOperator"` + RulesEnrollProbabilitySx string `json:"rulesEnrollProbabilitySx"` + Kslx string `json:"kslx"` + State string `json:"state"` + HistoryMajorEnrollMap map[string]YxHistoryMajorEnrollDTO `json:"historyMajorEnrollMap"` + // 计算相关字段 (非数据库直接映射) + EnrollProbability float64 `json:"enrollProbability"` // 录取率 + StudentScore float64 `json:"studentScore" gorm:"column:studentScore"` // 学生折合分 + // PrivateStudentScore float64 `json:"privateStudentScore"` // 学生折合分(私有) + // StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分(转换后) + // FirstLevelDiscipline string `json:"firstLevelDiscipline"` // 一级学科 (需确认来源) + + Province string `json:"province"` // 省份 + SchoolNature string `json:"schoolNature"` // 院校性质 + InstitutionType string `json:"institutionType"` // 院校类型 +} + +// SchoolMajorDTO 院校专业查询结果 DTO +type SchoolMajorDTO struct { + SchoolCode string `json:"schoolCode"` + SchoolName string `json:"schoolName"` + MajorCode string `json:"majorCode"` + MajorName string `json:"majorName"` + MajorType string `json:"majorType"` + MajorTypeChild string `json:"majorTypeChild"` + PlanNum int `json:"planNum"` + MainSubjects string `json:"mainSubjects"` + Limitation string `json:"limitation"` + ChineseScoreLimitation float64 `json:"chineseScoreLimitation"` + EnglishScoreLimitation float64 `json:"englishScoreLimitation"` + CulturalScoreLimitation float64 `json:"culturalScoreLimitation"` + ProfessionalScoreLimitation float64 `json:"professionalScoreLimitation"` + EnrollmentCode string `json:"enrollmentCode"` + Tuition string `json:"tuition"` + Detail string `json:"detail"` + Category string `json:"category"` + Batch string `json:"batch"` + RulesEnrollProbability string `json:"rulesEnrollProbability"` + ProbabilityOperator string `json:"probabilityOperator"` + PrivateRulesEnrollProbability string `json:"privateRulesEnrollProbability"` + PrivateProbabilityOperator string `json:"privateProbabilityOperator"` + RulesEnrollProbabilitySx string `json:"rulesEnrollProbabilitySx"` + Kslx string `json:"kslx"` + State string `json:"state"` + HistoryMajorEnrollMap map[string]YxHistoryMajorEnrollDTO `json:"historyMajorEnrollMap"` + // 计算相关字段 (非数据库直接映射) + HistoryMajorEnrollList []vo.YxHistoryMajorEnrollVO `json:"historyMajorEnrollList"` + EnrollProbability float64 `json:"enrollProbability"` // 录取率 + StudentScore float64 `json:"studentScore"` // 学生折合分 + PrivateStudentScore float64 `json:"privateStudentScore"` // 学生折合分(私有) + StudentConvertedScore float64 `json:"studentConvertedScore"` // 学生折合分(转换后) + FirstLevelDiscipline string `json:"firstLevelDiscipline"` // 一级学科 (需确认来源) + Province string `json:"province"` // 省份 + SchoolNature string `json:"schoolNature" gorm:"column:schoolNature"` // 院校性质 + InstitutionType string `json:"institutionType" gorm:"column:institutionType"` // 院校类型 +} + +type YxHistoryMajorEnrollDTO struct { + Year string `json:"year"` + EnrollmentCode string `json:"enrollmentCode"` + EnrollmentCount int `json:"enrollmentCount"` + RulesEnrollProbability string `json:"rulesEnrollProbability"` + ProbabilityOperator string `json:"probabilityOperator"` + AdmissionLine float64 `json:"admissionLine"` + ControlLine float64 `json:"controlLine"` + // 其他字段... +} + +// SchoolMajorQuery 院校专业查询条件 +type SchoolMajorQuery struct { + Page int `json:"page"` + Size int `json:"size"` + Keyword string `json:"keyword"` // 对应 keyword + MajorType string `json:"majorType"` // 对应 major_type + Category string `json:"category"` // 对应 category + Batch string `json:"batch"` // 对应 批次 + Batch2 string `json:"batch2"` // 对应 批次2 + MajorTypeChildren []string `json:"majorTypeChildren"` // 对应 major_type_child in (...) + MainSubjects string `json:"mainSubjects"` // 对应 main_subjects + Probability string `json:"probability"` // 对应 录取概率类型 + ScoreId string `json:"scoreId"` // 对应 score_id + TagList []string `json:"tagList"` // 对应 tags in (...) + SchoolNatureList []string `json:"schoolNatureList"` // 对应 school_nature in (...) + AddressList []string `json:"addressList"` // 对应 address in (...) + KyjxList []string `json:"kyjxList"` // 对应 kyjx in (...) + RulesEnrollProbabilityList []string `json:"rulesEnrollProbabilityList"` // 录取方式,对应 rules_enroll_probability in (...) + LoginUserId string `json:"loginUserId"` // 登录用户 ID + UserScoreVO userVO.UserScoreVO `json:"userScoreVO"` // 用户成绩 VO + CalculationTableName string `json:"calculationTableName"` // 对应 calculation_table_name + SchoolCode string `json:"schoolCode"` // 对应的 院校代码 +} diff --git a/server/modules/yx/dto/yx_user_score_dto.go b/server/modules/yx/dto/yx_user_score_dto.go new file mode 100644 index 0000000..1895a00 --- /dev/null +++ b/server/modules/yx/dto/yx_user_score_dto.go @@ -0,0 +1,143 @@ +package dto + +import ( + "errors" +) + +// SaveScoreRequest 保存成绩请求 +type SaveScoreRequest struct { + CognitioPolyclinic string `json:"CognitioPolyclinic" binding:"required"` + SubjectList []string `json:"SubjectList" binding:"required"` + ProfessionalCategory string `json:"ProfessionalCategory" binding:"required"` + ProfessionalCategoryChildren []string `json:"ProfessionalCategoryChildren" binding:"required"` + ProfessionalCategoryChildrenScore map[string]float64 `json:"ProfessionalCategoryChildrenScore" binding:"required"` + ProfessionalScore *float64 `json:"ProfessionalScore" binding:"required"` + CulturalScore *float64 `json:"CulturalScore" binding:"required"` + EnglishScore *float64 `json:"EnglishScore" binding:"required"` + ChineseScore *float64 `json:"ChineseScore" binding:"required"` + Province string `json:"Province" binding:"required"` + CreateBy string +} + +func (req *SaveScoreRequest) Validate() error { + if req.CognitioPolyclinic != "文科" && req.CognitioPolyclinic != "理科" { + return errors.New("考试类型必须是'物理组'或'历史组'") + } + + if req.ProfessionalCategory == "表演类" || req.ProfessionalCategory == "音乐类" { + if len(req.ProfessionalCategoryChildren) == 0 { + return errors.New("表演类或音乐类必须至少选一个专业子级") + } + } else { + req.ProfessionalCategoryChildren = []string{} + } + + if len(req.SubjectList) > 3 { + return errors.New("选考科目有且最多只能传三个值") + } + + validSubjects := map[string]bool{"地理": true, "政治": true, "历史": true, "化学": true, "生物": true} + for _, s := range req.SubjectList { + if !validSubjects[s] { + return errors.New("选考科目参数有误") + } + } + if !isValidScore(*req.ProfessionalScore, 300) { + return errors.New("统考成绩必须在0-300之间") + } + if !isValidScore(*req.CulturalScore, 750) { + return errors.New("文化成绩必须在0-750之间") + } + if !isValidScore(*req.EnglishScore, 150) { + return errors.New("英文成绩必须在0-150之间") + } + if !isValidScore(*req.ChineseScore, 150) { + return errors.New("中文成绩必须在0-150之间") + } + // TODO 在这里判断一下 专业子级,如 表演类只有:"服装表演", "戏剧影视表演", "戏剧影视导演"。音乐类只有:音乐表演声乐、音乐表演器乐、音乐教育。 + validProfessionalChildren := map[string]string{ // 子级 -> 父级分类 + "服装表演": "表演类", + "戏剧影视表演": "表演类", + "戏剧影视导演": "表演类", + "音乐表演声乐": "音乐类", + "音乐表演器乐": "音乐类", + "音乐教育": "音乐类", + } + // 统计各分类的数量 + categoryCount := make(map[string]int) + for childName, score := range req.ProfessionalCategoryChildrenScore { + // 验证子级名称是否合法 + parentCategory, exists := validProfessionalChildren[childName] + if !exists { + return errors.New("不支持的专业子级: " + childName) + } + // 统计分类数量 + categoryCount[parentCategory]++ + // 验证分数范围 + if !isValidScore(score, 300) { + return errors.New(childName + "成绩必须在0-300之间") + } + } + // 分类数量限制,比如每类最多几个 + for category, count := range categoryCount { + if category == "音乐类" { + // 判断 子级是否包含 xx + if req.ProfessionalCategoryChildrenScore["音乐表演声乐"] > 0 && req.ProfessionalCategoryChildrenScore["音乐表演器乐"] > 0 { + return errors.New("音乐类子级不可同时选择'音乐表演声乐'和'音乐表演器乐'") + } + } + if count > 3 { // 假设每类最多3个 + return errors.New(category + "最多只能有3个子级") + } + for _, key := range req.ProfessionalCategoryChildren { + if req.ProfessionalCategoryChildrenScore[key] == 0 { + return errors.New(key + "成绩不能为空") + } + } + } + + // 专业子级和成绩数据一致性校验 + if err := req.validateProfessionalConsistency(); err != nil { + return err + } + + return nil +} + +func (req *SaveScoreRequest) validateProfessionalConsistency() error { + // 特殊情况:如果不需要专业子级,则两个都应该为空 + if len(req.ProfessionalCategoryChildren) == 0 { + if len(req.ProfessionalCategoryChildrenScore) != 0 { + return errors.New("未选择专业子级时,成绩数据应为空") + } + return nil + } + // 创建映射用于双向验证 + childrenMap := make(map[string]bool) + for _, child := range req.ProfessionalCategoryChildren { + if childrenMap[child] { + return errors.New("专业子级 '" + child + "' 重复") + } + childrenMap[child] = true + } + // 验证长度一致 + if len(childrenMap) != len(req.ProfessionalCategoryChildrenScore) { + return errors.New("专业子级列表与成绩数据数量不匹配") + } + // 双向验证一致性 + for childName := range req.ProfessionalCategoryChildrenScore { + if !childrenMap[childName] { + return errors.New("成绩数据中的专业子级 '" + childName + "' 未在选中列表中") + } + } + for _, child := range req.ProfessionalCategoryChildren { + if _, exists := req.ProfessionalCategoryChildrenScore[child]; !exists { + return errors.New("选中的专业子级 '" + child + "' 缺少成绩数据") + } + } + return nil +} + +func isValidScore(score float64, max float64) bool { + return score >= 0 && score <= max +} diff --git a/server/modules/yx/dto/yx_volunteer_dto.go b/server/modules/yx/dto/yx_volunteer_dto.go new file mode 100644 index 0000000..2d5a256 --- /dev/null +++ b/server/modules/yx/dto/yx_volunteer_dto.go @@ -0,0 +1,14 @@ +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-历史) +} \ No newline at end of file diff --git a/server/modules/yx/entity/yx_calculation_major.go b/server/modules/yx/entity/yx_calculation_major.go new file mode 100644 index 0000000..1f34efa --- /dev/null +++ b/server/modules/yx/entity/yx_calculation_major.go @@ -0,0 +1,40 @@ +// Package entity 实体层 +package entity + +import "time" + +// YxCalculationMajor 计算专业表实体 +type YxCalculationMajor struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + ScoreID string `gorm:"column:score_id" json:"scoreId"` + SchoolCode string `gorm:"column:school_code" json:"schoolCode"` + MajorCode string `gorm:"column:major_code" json:"majorCode"` + MajorName string `gorm:"column:major_name" json:"majorName"` + EnrollmentCode string `gorm:"column:enrollment_code" json:"enrollmentCode"` + Tuition string `gorm:"column:tuition" json:"tuition"` + Detail string `gorm:"column:detail" json:"detail"` + Category string `gorm:"column:category" json:"category"` + RulesEnrollProbability string `gorm:"column:rules_enroll_probability" json:"rulesEnrollProbability"` + Batch string `gorm:"column:batch" json:"batch"` + StudentOldConvertedScore float64 `gorm:"column:student_old_converted_score" json:"studentOldConvertedScore"` + StudentConvertedScore float64 `gorm:"column:student_converted_score" json:"studentConvertedScore"` + EnrollProbability float64 `gorm:"column:enroll_probability" json:"enrollProbability"` + ProbabilityOperator string `gorm:"column:probability_operator" json:"probabilityOperator"` + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` + MajorType string `gorm:"column:major_type" json:"majorType"` + MajorTypeChild string `gorm:"column:major_type_child" json:"majorTypeChild"` + PlanNum int `gorm:"column:plan_num" json:"planNum"` + MainSubjects string `gorm:"column:main_subjects" json:"mainSubjects"` + Limitation string `gorm:"column:limitation" json:"limitation"` + OtherScoreLimitation string `gorm:"column:other_score_limitation" json:"otherScoreLimitation"` + RulesEnrollProbabilitySx string `gorm:"column:rules_enroll_probability_sx" json:"rulesEnrollProbabilitySx"` + Kslx string `gorm:"column:kslx" json:"kslx"` + PrivateStudentConvertedScore float64 `gorm:"column:private_student_converted_score" json:"privateStudentConvertedScore"` + PrivateRulesEnrollProbability string `gorm:"column:private_rules_enroll_probability" json:"privateRulesEnrollProbability"` + PrivateProbabilityOperator string `gorm:"column:private_probability_operator" json:"privateProbabilityOperator"` + State string `gorm:"column:state" json:"state"` +} + +func (YxCalculationMajor) TableName() string { + return "yx_calculation_major_2025_2" +} diff --git a/server/modules/yx/entity/yx_history_major_enroll.go b/server/modules/yx/entity/yx_history_major_enroll.go new file mode 100644 index 0000000..51cd22c --- /dev/null +++ b/server/modules/yx/entity/yx_history_major_enroll.go @@ -0,0 +1,42 @@ +// Package entity 实体层 +package entity + +import "time" + +// YxHistoryMajorEnroll 历年艺术类招生录取分数表实体 +type YxHistoryMajorEnroll struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + SchoolCode string `gorm:"column:school_code" json:"schoolCode"` + SchoolName string `gorm:"column:school_name" json:"schoolName"` + InstitutionCode string `gorm:"column:institution_code" json:"institutionCode"` + MajorCode string `gorm:"column:major_code" json:"majorCode"` + MajorName string `gorm:"column:major_name" json:"majorName"` + MajorType string `gorm:"column:major_type" json:"majorType"` + EnrollmentCode string `gorm:"column:enrollment_code" json:"enrollmentCode"` + Category string `gorm:"column:category" json:"category"` + Year string `gorm:"column:year" json:"year"` + EnrollNum int `gorm:"column:enroll_num" json:"enrollNum"` + ScoreLineDifference float64 `gorm:"column:score_line_difference" json:"scoreLineDifference"` + CreateBy string `gorm:"column:create_by" json:"createBy"` + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` + UpdateBy string `gorm:"column:update_by" json:"updateBy"` + UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` + SysOrgCode string `gorm:"column:sys_org_code" json:"sysOrgCode"` + Detail string `gorm:"column:detail" json:"detail"` + RulesEnrollProbability string `gorm:"column:rules_enroll_probability" json:"rulesEnrollProbability"` + ControlLine float64 `gorm:"column:control_line" json:"controlLine"` + AdmissionLine float64 `gorm:"column:admission_line" json:"admissionLine"` + ProbabilityOperator string `gorm:"column:probability_operator" json:"probabilityOperator"` + Batch string `gorm:"column:batch" json:"batch"` + OneVolunteerAdmissionNum int `gorm:"column:one_volunteer_admission_num" json:"oneVolunteerAdmissionNum"` + AdmissionNum int `gorm:"column:admission_num" json:"admissionNum"` + ActualPitcherNum int `gorm:"column:actual_pitcher_num" json:"actualPitcherNum"` + CheckMaster string `gorm:"column:check_master" json:"checkMaster"` + MajorTypeChild string `gorm:"column:major_type_child" json:"majorTypeChild"` + MainSubjects string `gorm:"column:main_subjects" json:"mainSubjects"` + Tuition string `gorm:"column:tuition" json:"tuition"` +} + +func (YxHistoryMajorEnroll) TableName() string { + return "yx_history_major_enroll" +} diff --git a/server/modules/yx/entity/yx_history_score_control_line.go b/server/modules/yx/entity/yx_history_score_control_line.go new file mode 100644 index 0000000..c4702f1 --- /dev/null +++ b/server/modules/yx/entity/yx_history_score_control_line.go @@ -0,0 +1,22 @@ +package entity + +import "time" + +// YxHistoryScoreControlLine 历年各专业省控分数线实体 +type YxHistoryScoreControlLine struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + Year string `gorm:"column:year" json:"year"` // 年份 + ProfessionalCategory string `gorm:"column:professional_category" json:"professionalCategory"` // 专业类别 + Category string `gorm:"column:category" json:"category"` // 文理科 + Batch string `gorm:"column:batch" json:"batch"` // 批次 + CulturalScore float64 `gorm:"column:cultural_score" json:"culturalScore"` // 文化分 + SpecialScore float64 `gorm:"column:special_score" json:"specialScore"` // 专业分 + CreateBy string `gorm:"column:create_by" json:"createBy"` + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` + UpdateBy string `gorm:"column:update_by" json:"updateBy"` + UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` +} + +func (YxHistoryScoreControlLine) TableName() string { + return "yx_history_score_control_line_new" +} diff --git a/server/modules/yx/entity/yx_school_major.go b/server/modules/yx/entity/yx_school_major.go new file mode 100644 index 0000000..5754770 --- /dev/null +++ b/server/modules/yx/entity/yx_school_major.go @@ -0,0 +1,46 @@ +// Package entity 实体层 +package entity + +import "time" + +// YxSchoolMajor 院校专业关联表实体 +type YxSchoolMajor struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + SchoolCode string `gorm:"column:school_code" json:"schoolCode"` + SchoolName string `gorm:"column:school_name" json:"schoolName"` + MajorCode string `gorm:"column:major_code" json:"majorCode"` + MajorName string `gorm:"column:major_name" json:"majorName"` + MajorType string `gorm:"column:major_type" json:"majorType"` + MajorTypeChild string `gorm:"column:major_type_child" json:"majorTypeChild"` + MainSubjects string `gorm:"column:main_subjects" json:"mainSubjects"` + EnrollmentCode string `gorm:"column:enrollment_code" json:"enrollmentCode"` + Category string `gorm:"column:category" json:"category"` + Batch string `gorm:"column:batch" json:"batch"` + Tuition string `gorm:"column:tuition" json:"tuition"` + PlanNum int `gorm:"column:plan_num" json:"planNum"` + Detail string `gorm:"column:detail" json:"detail"` + Semester string `gorm:"column:semester" json:"semester"` + CreateBy string `gorm:"column:create_by" json:"createBy"` + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` + UpdateBy string `gorm:"column:update_by" json:"updateBy"` + UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` + RulesEnrollProbabilitySx string `gorm:"column:rules_enroll_probability_sx" json:"rulesEnrollProbabilitySx"` + RulesEnrollProbability string `gorm:"column:rules_enroll_probability" json:"rulesEnrollProbability"` + ProbabilityOperator string `gorm:"column:probability_operator" json:"probabilityOperator"` + CulturalControlLine float64 `gorm:"column:cultural_control_line" json:"culturalControlLine"` + SpecialControlLine float64 `gorm:"column:special_control_line" json:"specialControlLine"` + CheckMaster string `gorm:"column:check_master" json:"checkMaster"` + Limitation string `gorm:"column:limitation" json:"limitation"` + ProfessionalScoreLimitation float64 `gorm:"column:professional_score_limitation" json:"professionalScoreLimitation"` + EnglishScoreLimitation float64 `gorm:"column:english_score_limitation" json:"englishScoreLimitation"` + ChineseScoreLimitation float64 `gorm:"column:chinese_score_limitation" json:"chineseScoreLimitation"` + CulturalScoreLimitation float64 `gorm:"column:cultural_score_limitation" json:"culturalScoreLimitation"` + Kslx string `gorm:"column:kslx" json:"kslx"` + PrivateProbabilityOperator string `gorm:"column:private_probability_operator" json:"privateProbabilityOperator"` + PrivateRulesEnrollProbability string `gorm:"column:private_rules_enroll_probability" json:"privateRulesEnrollProbability"` + State string `gorm:"column:state" json:"state"` +} + +func (YxSchoolMajor) TableName() string { + return "yx_school_major" +} diff --git a/server/modules/yx/entity/yx_user_score.go b/server/modules/yx/entity/yx_user_score.go new file mode 100644 index 0000000..47272e2 --- /dev/null +++ b/server/modules/yx/entity/yx_user_score.go @@ -0,0 +1,42 @@ +package entity + +import "time" + +// YxUserScore 用户分数信息表实体 +type YxUserScore struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + Type string `gorm:"column:type;default:1" json:"type"` // 填报类型(1-普通类 2-艺术类) + EducationalLevel string `gorm:"column:educational_level;default:1" json:"educationalLevel"` // 学历层次(1-本科,2-专科) + ProfessionalCategory string `gorm:"column:professional_category;default:美术类" json:"professionalCategory"` // 专业类别(美术类/...) + Subjects string `gorm:"column:subjects" json:"subjects"` // 选课 + ProfessionalScore float64 `gorm:"column:professional_score;default:0" json:"professionalScore"` // 专业成绩分 + CulturalScore float64 `gorm:"column:cultural_score;default:0" json:"culturalScore"` // 文化成绩分 + Ranking int `gorm:"column:ranking;default:0" json:"ranking"` // 位次 + CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建人 + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间 + UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 修改人 + UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` // 修改时间 + State string `gorm:"column:state;default:1" json:"state"` // 状态(0-未使用,1-使用中) + Province string `gorm:"column:province;default:北京" json:"province"` // 高考省份 + CognitioPolyclinic string `gorm:"column:cognitio_polyclinic" json:"cognitioPolyclinic"` // 文理分班(文科/理科) + Batch string `gorm:"column:batch" json:"batch"` // 录取批次 + EnglishScore float64 `gorm:"column:english_score;default:0.00" json:"englishScore"` // 英语成绩 + ChineseScore float64 `gorm:"column:chinese_score;default:0.00" json:"chineseScore"` // 语文成绩 + Yybysy float64 `gorm:"column:yybysy;default:0.00" json:"yybysy"` // 音乐表演声乐 + Yybyqy float64 `gorm:"column:yybyqy;default:0.00" json:"yybyqy"` // 音乐表演器乐 + Yyjy float64 `gorm:"column:yyjy;default:0.00" json:"yyjy"` // 音乐教育 + Xjysdy float64 `gorm:"column:xjysdy;default:0.00" json:"xjysdy"` // 戏剧影视导演 + Xjysby float64 `gorm:"column:xjysby;default:0.00" json:"xjysby"` // 戏剧影视表演 + Fzby float64 `gorm:"column:fzby;default:0.00" json:"fzby"` // 服装表演 + ProfessionalCategoryChildren string `gorm:"column:professional_category_children" json:"professionalCategoryChildren"` // 子级专业类别 + KbdNum int `gorm:"column:kbd_num;default:0" json:"kbdNum"` // 可保底专业数量 + NlqNum int `gorm:"column:nlq_num;default:0" json:"nlqNum"` // 难录取专业数量 + KcjNum int `gorm:"column:kcj_num;default:0" json:"kcjNum"` // 可冲击专业数量 + JwtNum int `gorm:"column:jwt_num;default:0" json:"jwtNum"` // 较稳妥专业数量 + CalculationTableName string `gorm:"column:calculation_table_name" json:"calculationTableName"` // 记录结果表名 +} + +// TableName 指定表名 +func (YxUserScore) TableName() string { + return "yx_user_score" +} diff --git a/server/modules/yx/entity/yx_volunteer.go b/server/modules/yx/entity/yx_volunteer.go new file mode 100644 index 0000000..feb4d35 --- /dev/null +++ b/server/modules/yx/entity/yx_volunteer.go @@ -0,0 +1,22 @@ +package entity + +import "time" + +// YxVolunteer 志愿表实体 +type YxVolunteer struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + VolunteerName string `gorm:"column:volunteer_name" json:"volunteerName"` // 志愿单名称 + ScoreId string `gorm:"column:score_id" json:"scoreId"` // 使用成绩id + CreateType string `gorm:"column:create_type;default:1" json:"createType"` // 生成类型(1.手动生成,2.智能生成) + State string `gorm:"column:state;default:0" json:"state"` // 志愿单状态(0-否,1.正在使用,2-历史) + CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建人 + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建日期 + UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新人 + UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` // 更新日期 + SysOrgCode string `gorm:"column:sys_org_code" json:"sysOrgCode"` // 所属部门 +} + +// TableName 指定表名 +func (YxVolunteer) TableName() string { + return "yx_volunteer" +} diff --git a/server/modules/yx/entity/yx_volunteer_record.go b/server/modules/yx/entity/yx_volunteer_record.go new file mode 100644 index 0000000..948cc62 --- /dev/null +++ b/server/modules/yx/entity/yx_volunteer_record.go @@ -0,0 +1,25 @@ +package entity + +import "time" + +// YxVolunteerRecord 志愿明细表实体 +type YxVolunteerRecord struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + VolunteerID string `gorm:"column:volunteer_id" json:"volunteerId"` // 志愿单id + SchoolCode string `gorm:"column:school_code" json:"schoolCode"` // 学校编码 + MajorCode string `gorm:"column:major_code" json:"majorCode"` // 专业编码 + EnrollmentCode string `gorm:"column:enrollment_code" json:"enrollmentCode"` // 招生代码 + Indexs int `gorm:"column:indexs;default:1" json:"indexs"` // 志愿顺序 + CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建人 + CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建日期 + Batch string `gorm:"column:batch" json:"batch"` // 录取批次 + EnrollProbability float64 `gorm:"column:enroll_probability;default:0.0000" json:"enrollProbability"` // 录取概率 + StudentConvertedScore float64 `gorm:"column:student_converted_score;default:0.0000" json:"studentConvertedScore"` // 折合分数 + Fctj int `gorm:"column:fctj;default:0" json:"fctj"` // 服从调剂 + CalculationMajorID string `gorm:"column:calculation_major_id" json:"calculationMajorId"` // 专业折算id +} + +// TableName 指定表名 +func (YxVolunteerRecord) TableName() string { + return "yx_volunteer_record" +} diff --git a/server/modules/yx/mapper/yx_calculation_major_mapper.go b/server/modules/yx/mapper/yx_calculation_major_mapper.go new file mode 100644 index 0000000..a6d8f66 --- /dev/null +++ b/server/modules/yx/mapper/yx_calculation_major_mapper.go @@ -0,0 +1,527 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "fmt" + "server/common" + "server/config" + "server/modules/yx/dto" + "server/modules/yx/entity" + "strings" + "sync" + "time" +) + +type YxCalculationMajorMapper struct { + *common.BaseMapper[entity.YxCalculationMajor] +} + +func NewYxCalculationMajorMapper() *YxCalculationMajorMapper { + return &YxCalculationMajorMapper{ + BaseMapper: common.NewBaseMapper[entity.YxCalculationMajor](), + } +} + +// QueryCostTime 存储各协程耗时的结构体 +type QueryCostTime struct { + CountCost time.Duration // 总数量查询耗时 + ProbCountCost time.Duration // 四种概率数量查询耗时 + QueryCost time.Duration // 主列表查询耗时 + TotalCost time.Duration // 整体总耗时 +} + +// FindRecommendList 查询推荐专业列表(优化版:并发查询总数量、概率数量、主列表) +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") + } + + // 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.SchoolCode != "" { + baseSQL += " AND cm.school_code = ?" + params = append(params, query.SchoolCode) + } + 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.Batch { + baseSQL += " AND cm.batch = ?" + params = append(params, query.Batch) + } + + if query.MainSubjects != "" { + baseSQL += " AND cm.main_subjects = ?" + params = append(params, query.MainSubjects) + } + + if query.Keyword != "" { + baseSQL += " AND cm.major_name like ?" + params = append(params, query.Keyword) + } + + // 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, + SUM(CASE WHEN cm.enroll_probability >= 60 AND cm.enroll_probability < 73 THEN 1 ELSE 0 END) AS risky, + 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 safe + 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, + s.school_nature, + s.institution_type + 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)" + } + + mainSQL += " ORDER BY cm.enroll_probability DESC" + // 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.Since(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.Since(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.Since(start) + mu.Unlock() + }() + + wg.Wait() + + // 计算整体总耗时 + queryCost.TotalCost = time.Since(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) + } + if items == nil { + items = []dto.UserMajorDTO{} + } + return items, total, probCount, nil +} + +// FindRecommendList1 查询推荐专业列表(原版) +func (m *YxCalculationMajorMapper) FindRecommendList1(query dto.SchoolMajorQuery) ([]dto.UserMajorDTO, int64, error) { + var items []dto.UserMajorDTO + var total int64 + + // 确保表名存在,防止 SQL 注入或空表名 + tableName := query.UserScoreVO.CalculationTableName + if tableName == "" { + return nil, 0, fmt.Errorf("CalculationTableName is empty") + } + + // 使用 Sprintf 动态插入表名 + countSQL := fmt.Sprintf(` + SELECT COUNT(cm.id) 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 + WHERE 1=1 AND cm.state > 0 + `, tableName) + + sql := 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 + WHERE 1=1 AND cm.state > 0 + `, tableName) + + params := []interface{}{} + + // 注意:移除了 params = append(params, query.UserScoreVO.CalculationTableName) 因为表名已经通过 Sprintf 插入 + + if query.UserScoreVO.ID != "" { + countSQL += " AND cm.score_id = ?" + sql += " AND cm.score_id = ?" + params = append(params, query.UserScoreVO.ID) + } + + if query.MajorType != "" { + countSQL += " AND cm.major_type = ?" + sql += " AND cm.major_type = ?" + params = append(params, query.MajorType) + } + if query.Category != "" { + countSQL += " AND cm.category = ?" + sql += " AND cm.category = ?" + params = append(params, query.Category) + } + if len(query.MajorTypeChildren) > 0 { + placeholders := strings.Repeat("?,", len(query.MajorTypeChildren)-1) + "?" + countSQL += " AND cm.major_type_child IN (" + placeholders + ")" + sql += " AND cm.major_type_child IN (" + placeholders + ")" + for _, v := range query.MajorTypeChildren { + params = append(params, v) + } + } + + if query.MainSubjects != "" { + countSQL += " AND cm.main_subjects = ?" + sql += " AND cm.main_subjects = ?" + params = append(params, query.MainSubjects) + } + + // 录取概率 + switch query.Probability { + case "难录取": + countSQL += " AND cm.enroll_probability < 60" + sql += " AND cm.enroll_probability < 60" + case "可冲击": + countSQL += " AND (cm.enroll_probability >= 60 and cm.enroll_probability < 73)" + sql += " AND (cm.enroll_probability >= 60 and cm.enroll_probability < 73)" + case "较稳妥": + countSQL += " AND (cm.enroll_probability >= 73 and cm.enroll_probability < 93)" + sql += " AND (cm.enroll_probability >= 73 and cm.enroll_probability < 93)" + case "可保底": + countSQL += " AND (cm.enroll_probability >= 93)" + sql += " AND (cm.enroll_probability >= 93)" + } + + var wg sync.WaitGroup + var countErr, queryErr error + + wg.Add(2) + // 协程1:COUNT 查询 + go func() { + defer wg.Done() + countErr = config.DB.Raw(countSQL, params...).Count(&total).Error + }() + + // 协程2:主查询 + go func() { + defer wg.Done() + sql += fmt.Sprintf(" LIMIT %d OFFSET %d", query.Size, (query.Page-1)*query.Size) + queryErr = config.DB.Raw(sql, params...).Scan(&items).Error + }() + wg.Wait() + if countErr != nil || queryErr != nil { + return nil, 0, fmt.Errorf("countErr: %v, queryErr: %v", countErr, queryErr) + } + return items, total, queryErr +} + +// FindByScoreID 根据 scoreID 查找计算专业列表 +func (m *YxCalculationMajorMapper) FindByScoreID(scoreID string) ([]entity.YxCalculationMajor, error) { + var items []entity.YxCalculationMajor + err := m.GetDB().Where("score_id = ?", scoreID).Find(&items).Error + return items, err +} + +// FindListByCompositeKeys 根据复合键查找计算专业列表 +func (m *YxCalculationMajorMapper) FindListByCompositeKeys(tableName string, keys []string, scoreId string) ([]entity.YxCalculationMajor, error) { + if len(keys) == 0 { + return nil, nil + } + + // 验证表名格式(防止表名注入) + if !common.IsValidTableName(tableName) { + return nil, fmt.Errorf("无效的表名: %s", tableName) + } + + // 验证和转义 score_id + if scoreId == "" { + return nil, fmt.Errorf("score_id 不能为空") + } + + var items []entity.YxCalculationMajor + db := m.GetDB() + if tableName != "" { + db = db.Table(tableName) + } + + sql := "SELECT * FROM " + tableName + " WHERE score_id = ? AND (school_code, major_code, enrollment_code) IN (" + var params []interface{} + + // 将 score_id 作为第一个参数 + params = append(params, scoreId) + + for i, key := range keys { + parts := strings.Split(key, "_") + if len(parts) != 3 { + continue + } + if i > 0 { + sql += "," + } + sql += "(?, ?, ?)" + params = append(params, parts[0], parts[1], parts[2]) + } + sql += ")" + + err := db.Raw(sql, params...).Scan(&items).Error + return items, err +} + +// FindDtoListByCompositeKeys 根据复合键查找 DTO 列表 +func (m *YxCalculationMajorMapper) FindDtoListByCompositeKeys(tableName string, keys []string, scoreId string) ([]dto.SchoolMajorDTO, error) { + if len(keys) == 0 { + return nil, nil + } + if !common.IsValidTableName(tableName) { + return nil, fmt.Errorf("无效的表名: %s", tableName) + } + if scoreId == "" { + return nil, fmt.Errorf("score_id 不能为空") + } + + var items []dto.SchoolMajorDTO + + // SQL with joins to get school info + // Base Query similar to FindRecommendList but filtered by composite keys + sqlStr := 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 + WHERE cm.score_id = ? AND (cm.school_code, cm.major_code, cm.enrollment_code) IN ( + `, tableName) + + var params []interface{} + params = append(params, scoreId) + + // Build IN clause + var tuples []string + for _, key := range keys { + parts := strings.Split(key, "_") + if len(parts) != 3 { + continue + } + tuples = append(tuples, "(?, ?, ?)") + params = append(params, parts[0], parts[1], parts[2]) + } + + if len(tuples) == 0 { + return nil, nil + } + + sqlStr += strings.Join(tuples, ",") + ")" + + err := m.GetDB().Raw(sqlStr, params...).Scan(&items).Error + return items, err +} + +// BatchCreate 批量创建(支持动态表名) +func (m *YxCalculationMajorMapper) BatchCreate(tableName string, items []entity.YxCalculationMajor, batchSize int) error { + if tableName != "" { + return m.GetDB().Table(tableName).CreateInBatches(items, batchSize).Error + } + return m.GetDB().CreateInBatches(items, batchSize).Error +} + +// DeleteByScoreID 根据 scoreID 删除 +func (m *YxCalculationMajorMapper) DeleteByScoreID(scoreID string) error { + return m.GetDB().Delete(&entity.YxCalculationMajor{}, "score_id = ?", scoreID).Error +} + +// DeleteByScoreIDFromTable 从指定表删除 scoreID 对应的数据 +func (m *YxCalculationMajorMapper) DeleteByScoreIDFromTable(tableName, scoreID string) error { + if tableName == "" { + return nil + } + return m.GetDB().Table(tableName).Where("score_id = ?", scoreID).Delete(map[string]interface{}{}).Error +} diff --git a/server/modules/yx/mapper/yx_history_major_enroll_mapper.go b/server/modules/yx/mapper/yx_history_major_enroll_mapper.go new file mode 100644 index 0000000..cb012f5 --- /dev/null +++ b/server/modules/yx/mapper/yx_history_major_enroll_mapper.go @@ -0,0 +1,24 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "server/common" + "server/modules/yx/entity" +) + +type YxHistoryMajorEnrollMapper struct { + *common.BaseMapper[entity.YxHistoryMajorEnroll] +} + +func NewYxHistoryMajorEnrollMapper() *YxHistoryMajorEnrollMapper { + return &YxHistoryMajorEnrollMapper{ + BaseMapper: common.NewBaseMapper[entity.YxHistoryMajorEnroll](), + } +} + +// FindByYear 根据年份查找历史招生数据 +func (m *YxHistoryMajorEnrollMapper) FindByYear(year string) ([]entity.YxHistoryMajorEnroll, error) { + var items []entity.YxHistoryMajorEnroll + err := m.GetDB().Where("year = ?", year).Find(&items).Error + return items, err +} diff --git a/server/modules/yx/mapper/yx_history_score_control_line_mapper.go b/server/modules/yx/mapper/yx_history_score_control_line_mapper.go new file mode 100644 index 0000000..cb317d7 --- /dev/null +++ b/server/modules/yx/mapper/yx_history_score_control_line_mapper.go @@ -0,0 +1,35 @@ +package mapper + +import ( + "server/config" + "server/modules/yx/entity" +) + +type YxHistoryScoreControlLineMapper struct{} + +func NewYxHistoryScoreControlLineMapper() *YxHistoryScoreControlLineMapper { + return &YxHistoryScoreControlLineMapper{} +} + +func (m *YxHistoryScoreControlLineMapper) SelectByYearAndCategory(year, professionalCategory, category string) ([]entity.YxHistoryScoreControlLine, error) { + var items []entity.YxHistoryScoreControlLine + // 判断是否为表演类,如果是则匹配多个类别 + var categories []string + switch professionalCategory { + case "表演类": + categories = []string{"表演类-戏剧影视表演", "表演类-服装表演", "表演类-戏剧影视导演"} + // fallthrough // 继续执行下面的case + case "音乐类": + categories = []string{"音乐类-音乐表演声乐", "音乐类-音乐表演器乐", "音乐类-音乐教育"} + default: + categories = []string{professionalCategory} + } + + err := config.DB.Model(&entity.YxHistoryScoreControlLine{}). + Where("year = ?", year). + Where("professional_category IN ?", categories). + Where("category = ?", category). + Order("batch desc"). + Find(&items).Error + return items, err +} diff --git a/server/modules/yx/mapper/yx_school_major_mapper.go b/server/modules/yx/mapper/yx_school_major_mapper.go new file mode 100644 index 0000000..b30092a --- /dev/null +++ b/server/modules/yx/mapper/yx_school_major_mapper.go @@ -0,0 +1,71 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "server/common" + "server/modules/yx/dto" + "server/modules/yx/entity" +) + +type YxSchoolMajorMapper struct { + *common.BaseMapper[entity.YxSchoolMajor] +} + +func NewYxSchoolMajorMapper() *YxSchoolMajorMapper { + return &YxSchoolMajorMapper{ + BaseMapper: common.NewBaseMapper[entity.YxSchoolMajor](), + } +} + +// SelectSchoolMajor 查询院校专业信息(包含学校信息) +func (m *YxSchoolMajorMapper) SelectSchoolMajor(query dto.SchoolMajorQuery) ([]dto.SchoolMajorDTO, error) { + var items []dto.SchoolMajorDTO + + queryBuilder := m.GetDB().Table("yx_school_major sm"). + Select(` + sm.school_code, + sc.school_name, + sm.major_code, + sm.major_name, + sm.major_type, + sm.major_type_child, + sm.plan_num, + sm.main_subjects, + sm.limitation, + sm.chinese_score_limitation, + sm.english_score_limitation, + sm.cultural_score_limitation, + sm.professional_score_limitation, + sm.enrollment_code, + sm.tuition, + sm.detail, + sm.category, + sm.batch, + sm.rules_enroll_probability, + sm.probability_operator, + sm.private_rules_enroll_probability, + sm.private_probability_operator, + sm.rules_enroll_probability_sx, + sm.kslx, + sm.state + `). + Joins("LEFT JOIN (SELECT school_id,school_name,school_code FROM yx_school_child group by school_code) sc ON sc.school_code = sm.school_code"). + Joins("LEFT JOIN yx_school s ON s.id = sc.school_id"). + Where("sm.state > 0") + + if query.MajorType != "" { + queryBuilder = queryBuilder.Where("sm.major_type = ?", query.MajorType) + } + if query.Category != "" { + queryBuilder = queryBuilder.Where("sm.category = ?", query.Category) + } + if len(query.MajorTypeChildren) > 0 { + queryBuilder = queryBuilder.Where("sm.major_type_child IN ?", query.MajorTypeChildren) + } + if query.MainSubjects != "" { + queryBuilder = queryBuilder.Where("sm.main_subjects = ?", query.MainSubjects) + } + + err := queryBuilder.Scan(&items).Error + return items, err +} diff --git a/server/modules/yx/mapper/yx_user_score_mapper.go b/server/modules/yx/mapper/yx_user_score_mapper.go new file mode 100644 index 0000000..e58bf62 --- /dev/null +++ b/server/modules/yx/mapper/yx_user_score_mapper.go @@ -0,0 +1,44 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "errors" + "server/common" + "server/modules/yx/entity" +) + +type YxUserScoreMapper struct { + *common.BaseMapper[entity.YxUserScore] +} + +func NewYxUserScoreMapper() *YxUserScoreMapper { + return &YxUserScoreMapper{ + BaseMapper: common.NewBaseMapper[entity.YxUserScore](), + } +} + +// UpdateFieldsByMultiCondition 根据多个条件更新字段 +func (m *YxUserScoreMapper) UpdateFieldsByMultiCondition(condition *entity.YxUserScore, fields map[string]interface{}) error { + query := m.GetDB().Model(&entity.YxUserScore{}) + + whereCount := 0 // 记录条件数量,避免无条件更新 + if condition.ID != "" { + query = query.Where("id = ?", condition.ID) + whereCount++ + } + if condition.CreateBy != "" { + query = query.Where("create_by = ?", condition.CreateBy) + whereCount++ + } + if condition.State != "" { + query = query.Where("state = ?", condition.State) + whereCount++ + } + + // 安全检查:必须至少有一个查询条件 + if whereCount == 0 { + return errors.New("至少需要一个查询条件") + } + + return query.Updates(fields).Error +} diff --git a/server/modules/yx/mapper/yx_volunteer_mapper.go b/server/modules/yx/mapper/yx_volunteer_mapper.go new file mode 100644 index 0000000..40eb168 --- /dev/null +++ b/server/modules/yx/mapper/yx_volunteer_mapper.go @@ -0,0 +1,52 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/vo" +) + +type YxVolunteerMapper struct { + *common.BaseMapper[entity.YxVolunteer] +} + +func NewYxVolunteerMapper() *YxVolunteerMapper { + return &YxVolunteerMapper{ + BaseMapper: common.NewBaseMapper[entity.YxVolunteer](), + } +} + +// CloseOtherVolunteer 关闭用户的其他志愿单 +func (m *YxVolunteerMapper) CloseOtherVolunteer(userId string) error { + result := m.GetDB().Model(&entity.YxVolunteer{}). + Where("create_by = ?", userId). + Updates(map[string]interface{}{"state": "0"}) + + if result.Error != nil { + return result.Error + } + return nil +} + +// FindActiveByScoreId 根据 scoreId 查找激活的志愿单 +func (m *YxVolunteerMapper) FindActiveByScoreId(scoreId string) (*entity.YxVolunteer, error) { + var item entity.YxVolunteer + err := m.GetDB().Where("score_id = ? AND state = ?", scoreId, "1").First(&item).Error + return &item, err +} + +// ListByUser 查询用户的志愿单列表(包含成绩信息) +func (m *YxVolunteerMapper) ListByUser(userID string, page, size int) ([]vo.UserVolunteerVO, int64, error) { + var items []vo.UserVolunteerVO + var total int64 + + db := m.GetDB().Table("yx_volunteer v"). + Select("v.*, s.professional_category, s.province, s.cultural_score, s.professional_score"). + Joins("LEFT JOIN yx_user_score s ON v.score_id = s.id"). + Where("v.create_by = ?", userID) + + db.Count(&total) + err := db.Order("v.create_time DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error + return items, total, err +} diff --git a/server/modules/yx/mapper/yx_volunteer_record_mapper.go b/server/modules/yx/mapper/yx_volunteer_record_mapper.go new file mode 100644 index 0000000..344a31d --- /dev/null +++ b/server/modules/yx/mapper/yx_volunteer_record_mapper.go @@ -0,0 +1,34 @@ +// Package mapper 数据访问层 +package mapper + +import ( + "server/common" + "server/modules/yx/entity" +) + +type YxVolunteerRecordMapper struct { + *common.BaseMapper[entity.YxVolunteerRecord] +} + +func NewYxVolunteerRecordMapper() *YxVolunteerRecordMapper { + return &YxVolunteerRecordMapper{ + BaseMapper: common.NewBaseMapper[entity.YxVolunteerRecord](), + } +} + +// FindByVolunteerID 根据 volunteerID 查找志愿记录 +func (m *YxVolunteerRecordMapper) FindByVolunteerID(volunteerID string) ([]entity.YxVolunteerRecord, error) { + var items []entity.YxVolunteerRecord + err := m.GetDB().Where("volunteer_id = ?", volunteerID).Order("indexs ASC").Find(&items).Error + return items, err +} + +// DeleteByVolunteerID 根据 volunteerID 删除志愿记录 +func (m *YxVolunteerRecordMapper) DeleteByVolunteerID(volunteerID string) error { + return m.GetDB().Delete(&entity.YxVolunteerRecord{}, "volunteer_id = ?", volunteerID).Error +} + +// BatchDeleteByVolunteerId 根据 volunteerID 批量删除志愿记录(无返回值) +func (m *YxVolunteerRecordMapper) BatchDeleteByVolunteerId(id string) { + m.GetDB().Delete(&entity.YxVolunteerRecord{}, "volunteer_id = ?", id) +} diff --git a/server/modules/yx/service/yx_calculation_major_service.go b/server/modules/yx/service/yx_calculation_major_service.go new file mode 100644 index 0000000..ceb3b48 --- /dev/null +++ b/server/modules/yx/service/yx_calculation_major_service.go @@ -0,0 +1,972 @@ +// Package service 业务逻辑层 +package service + +import ( + "fmt" + "server/common" + calc "server/common" + "server/modules/user/vo" + "server/modules/yx/dto" + yxDto "server/modules/yx/dto" + "server/modules/yx/entity" + "server/modules/yx/mapper" + yxVO "server/modules/yx/vo" + "strings" + "time" + + "github.com/google/uuid" +) + +type YxCalculationMajorService struct { + *common.BaseService[entity.YxCalculationMajor] + historyMajorEnrollService *YxHistoryMajorEnrollService + mapper *mapper.YxCalculationMajorMapper + historyScoreControlLineService *YxHistoryScoreControlLineService +} + +func NewYxCalculationMajorService() *YxCalculationMajorService { + mapper := mapper.NewYxCalculationMajorMapper() + return &YxCalculationMajorService{ + BaseService: common.NewBaseService[entity.YxCalculationMajor](), + historyMajorEnrollService: NewYxHistoryMajorEnrollService(), + mapper: mapper, + historyScoreControlLineService: NewYxHistoryScoreControlLineService(), + } +} + +// RecommendMajorList 推荐专业列表 +func (s *YxCalculationMajorService) RecommendMajorList(schoolMajorQuery yxDto.SchoolMajorQuery) ([]yxDto.UserMajorDTO, int64, dto.ProbabilityCountDTO, error) { + if schoolMajorQuery.UserScoreVO.ProfessionalCategory == "" { + return nil, 0, dto.ProbabilityCountDTO{}, fmt.Errorf("专业类型错误") + } + + // 根据批次类型设置批次 985/211/双一流 -> 提前批, 公办本科 -> 本科A段, 民办本科 -> 本科B段, 体育类 -> 本科 + if schoolMajorQuery.Batch != "" && "本科提前" == schoolMajorQuery.Batch { + return []yxDto.UserMajorDTO{}, 0, dto.ProbabilityCountDTO{}, nil + } else if schoolMajorQuery.Batch != "" && "高职高专" != schoolMajorQuery.Batch { + if schoolMajorQuery.Batch2 == "双一流" { + schoolMajorQuery.Batch = "提前批" + } else if schoolMajorQuery.Batch2 == "公办本科" { + schoolMajorQuery.Batch = "本科A段" + } else if schoolMajorQuery.Batch2 == "民办本科" { + schoolMajorQuery.Batch = "本科B段" + } else if schoolMajorQuery.MajorType == "体育类" { + schoolMajorQuery.Batch = "本科" + } else { + schoolMajorQuery.Batch = "" + } + } else { + schoolMajorQuery.Batch = "高职高专" + } + + calculationMajors, total, probCount, err := s.mapper.FindRecommendList(schoolMajorQuery) + if err != nil { + 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, dto.ProbabilityCountDTO{}, err + } + + return calculationMajors, total, probCount, nil +} + +// BatchCreateBySchoolMajorDTO 根据专业 DTO 批量创建 +func (s *YxCalculationMajorService) BatchCreateBySchoolMajorDTO(tableName string, items []dto.SchoolMajorDTO, scoreID string) error { + entities := make([]entity.YxCalculationMajor, 0, len(items)) + now := time.Now() + for _, item := range items { + // 构建 OtherScoreLimitation + var otherScoreLimitation strings.Builder + if item.ChineseScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("语文成绩不低于%.1f分,", item.ChineseScoreLimitation)) + } + if item.EnglishScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("外语成绩不低于%.1f分,", item.EnglishScoreLimitation)) + } + if item.CulturalScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("文化成绩不低于%.1f分,", item.CulturalScoreLimitation)) + } + if item.ProfessionalScoreLimitation > 0 { + otherScoreLimitation.WriteString(fmt.Sprintf("专业成绩不低于%.1f分,", item.ProfessionalScoreLimitation)) + } + otherScoreLimitationStr := otherScoreLimitation.String() + if len(otherScoreLimitationStr) > 0 { + otherScoreLimitationStr = otherScoreLimitationStr[:len(otherScoreLimitationStr)-1] + } + + entities = append(entities, entity.YxCalculationMajor{ + ScoreID: scoreID, + SchoolCode: item.SchoolCode, + MajorCode: item.MajorCode, + MajorName: item.MajorName, + EnrollmentCode: item.EnrollmentCode, + Tuition: item.Tuition, + Detail: item.Detail, + Category: item.Category, + RulesEnrollProbability: item.RulesEnrollProbability, + Batch: item.Batch, + StudentOldConvertedScore: item.StudentScore, + StudentConvertedScore: item.StudentConvertedScore, + EnrollProbability: item.EnrollProbability, + ProbabilityOperator: item.ProbabilityOperator, + CreateTime: now, + MajorType: item.MajorType, + MajorTypeChild: item.MajorTypeChild, + PlanNum: item.PlanNum, + MainSubjects: item.MainSubjects, + Limitation: item.Limitation, + OtherScoreLimitation: otherScoreLimitationStr, + RulesEnrollProbabilitySx: item.RulesEnrollProbabilitySx, + Kslx: item.Kslx, + PrivateStudentConvertedScore: item.PrivateStudentScore, + PrivateRulesEnrollProbability: item.PrivateRulesEnrollProbability, + PrivateProbabilityOperator: item.PrivateProbabilityOperator, + State: item.State, + }) + } + return s.BatchCreate(tableName, entities) +} + +// GetByScoreID 根据 scoreID 获取计算专业列表 +func (s *YxCalculationMajorService) GetByScoreID(scoreID string) ([]entity.YxCalculationMajor, error) { + return s.mapper.FindByScoreID(scoreID) +} + +// FindListByCompositeKeys 根据复合键查找列表 +func (s *YxCalculationMajorService) FindListByCompositeKeys(tableName string, keys []string, scoreId string) ([]entity.YxCalculationMajor, error) { + return s.mapper.FindListByCompositeKeys(tableName, keys, scoreId) +} + +// FindDtoListByCompositeKeys 根据复合键查找 DTO 列表 +func (s *YxCalculationMajorService) FindDtoListByCompositeKeys(tableName string, keys []string, scoreId string) ([]dto.SchoolMajorDTO, error) { + return s.mapper.FindDtoListByCompositeKeys(tableName, keys, scoreId) +} + +// BatchCreate 批量创建(支持动态表名,使用 UUID 生成 ID) +func (s *YxCalculationMajorService) BatchCreate(tableName string, items []entity.YxCalculationMajor) error { + for i := range items { + items[i].ID = uuid.New().String() + } + return s.mapper.BatchCreate(tableName, items, 100) +} + +// DeleteByScoreID 根据 scoreID 删除 +func (s *YxCalculationMajorService) DeleteByScoreID(scoreID string) error { + return s.mapper.DeleteByScoreID(scoreID) +} + +// DeleteByScoreIDFromTable 从指定表删除 scoreID 对应的数据 +func (s *YxCalculationMajorService) DeleteByScoreIDFromTable(tableName, scoreID string) error { + return s.mapper.DeleteByScoreIDFromTable(tableName, scoreID) +} + +// ListByUserQueryType 根据用户查询类型获取专业列表 +func (s *YxCalculationMajorService) ListByUserQueryType(professionalCategory string, cognitioPolyclinic string, + professionalCategoryChildren []string) ([]dto.SchoolMajorDTO, error) { + // 构造查询条件 + query := dto.SchoolMajorQuery{ + MajorType: professionalCategory, + Category: cognitioPolyclinic, + } + + // 执行院校查询 + majorItems, err := mapper.NewYxSchoolMajorMapper().SelectSchoolMajor(query) + if err != nil { + return nil, err + } + // 分别收集专业名称和院校代码集合(去重) + majorNameSet := make(map[string]bool) + schoolCodeSet := make(map[string]bool) + years := common.OldYearList + for _, dto := range majorItems { + majorNameSet[dto.MajorName] = true + schoolCodeSet[dto.SchoolCode] = true + } + // 转换为切片用于查询 + majorNames := make([]string, 0, len(majorNameSet)) + schoolCodes := make([]string, 0, len(schoolCodeSet)) + for name := range majorNameSet { + majorNames = append(majorNames, name) + } + for code := range schoolCodeSet { + schoolCodes = append(schoolCodes, code) + } + // 执行查询院校专业的历年数据 + historyItems, err := s.historyMajorEnrollService.ListBySchoolCodesAndMajorNames( + schoolCodes, + majorNames, + query.Category, + query.MajorType, + years, + ) + + if err != nil { + return nil, err + } + + // 构建历史数据映射:schoolCode_majorName_batch_year -> historyItem + allHistoryMajorEnrollMap := make(map[string]entity.YxHistoryMajorEnroll) + for _, historyItem := range historyItems { + key := historyItem.SchoolCode + "_" + historyItem.MajorName + "_" + historyItem.Batch + "_" + historyItem.Year + allHistoryMajorEnrollMap[key] = historyItem + } + // 将历史数据填充到每个专业数据中 + for i, majorItem := range majorItems { + // 为每个majorItem创建独立的历史数据映射 + historyMap := make(map[string]dto.YxHistoryMajorEnrollDTO) + + for _, year := range years { + key := majorItem.SchoolCode + "_" + majorItem.MajorName + "_" + majorItem.Batch + "_" + year + if historyItem, ok := allHistoryMajorEnrollMap[key]; ok { + dtoItem := dto.YxHistoryMajorEnrollDTO{ + Year: historyItem.Year, + EnrollmentCode: historyItem.EnrollmentCode, + RulesEnrollProbability: historyItem.RulesEnrollProbability, + ProbabilityOperator: historyItem.ProbabilityOperator, + AdmissionLine: historyItem.AdmissionLine, + ControlLine: historyItem.ControlLine, + } + historyMap[year] = dtoItem + } + } + majorItems[i].HistoryMajorEnrollMap = historyMap + } + + return majorItems, nil +} + +// UserMajorDTOGetHistory 获取用户专业 DTO 的历史数据 +func (s *YxCalculationMajorService) UserMajorDTOGetHistory(userMajorDTOList *[]dto.UserMajorDTO) error { + if len(*userMajorDTOList) > 0 { + // 分别收集专业名称和院校代码集合(去重) + majorNameSet := make(map[string]bool) + schoolCodeSet := make(map[string]bool) + years := common.OldYearList + for _, dto := range *userMajorDTOList { + majorNameSet[dto.MajorName] = true + schoolCodeSet[dto.SchoolCode] = true + } + // 转换为切片用于查询 + majorNames := make([]string, 0, len(majorNameSet)) + schoolCodes := make([]string, 0, len(schoolCodeSet)) + for name := range majorNameSet { + majorNames = append(majorNames, name) + } + for code := range schoolCodeSet { + schoolCodes = append(schoolCodes, code) + } + // 执行查询院校专业的历年数据 + historyItems, err := s.historyMajorEnrollService.ListBySchoolCodesAndMajorNames( + schoolCodes, + majorNames, + (*userMajorDTOList)[0].Category, + (*userMajorDTOList)[0].MajorType, + years, + ) + + if err != nil { + return nil + } + + // 构建历史数据映射:schoolCode_majorName_batch_year -> historyItem + allHistoryMajorEnrollMap := make(map[string]entity.YxHistoryMajorEnroll) + for _, historyItem := range historyItems { + key := historyItem.SchoolCode + "_" + historyItem.MajorName + "_" + historyItem.Batch + "_" + historyItem.Year + allHistoryMajorEnrollMap[key] = historyItem + } + // 将历史数据填充到每个专业数据中 + for i, majorItem := range *userMajorDTOList { + // 为每个majorItem创建独立的历史数据映射 + historyMap := make(map[string]dto.YxHistoryMajorEnrollDTO) + + for _, year := range years { + key := majorItem.SchoolCode + "_" + majorItem.MajorName + "_" + majorItem.Batch + "_" + year + if historyItem, ok := allHistoryMajorEnrollMap[key]; ok { + dtoItem := dto.YxHistoryMajorEnrollDTO{ + Year: historyItem.Year, + EnrollmentCode: historyItem.EnrollmentCode, + RulesEnrollProbability: historyItem.RulesEnrollProbability, + ProbabilityOperator: historyItem.ProbabilityOperator, + AdmissionLine: historyItem.AdmissionLine, + ControlLine: historyItem.ControlLine, + } + historyMap[year] = dtoItem + } + } + (*userMajorDTOList)[i].HistoryMajorEnrollMap = historyMap + } + } + return nil +} + +// CheckEnrollProbability 计算专业列表录取率 +func (s *YxCalculationMajorService) CheckEnrollProbability(schoolMajorDTOList *[]dto.SchoolMajorDTO, userScoreVO vo.UserScoreVO) error { + professionalCategory := userScoreVO.ProfessionalCategory + if "表演类" == professionalCategory { + s.BiaoYanRecommendMajorListSetEnrollProbability(schoolMajorDTOList, userScoreVO) + } else if "音乐类" == professionalCategory { + s.YinYueRecommendMajorListSetEnrollProbability(schoolMajorDTOList, userScoreVO) + } else { + s.BetaRecommendMajorListSetEnrollProbability(schoolMajorDTOList, userScoreVO) + } + return nil +} + +// BiaoYanRecommendMajorListSetEnrollProbability 表演类 获取录取率 +func (s *YxCalculationMajorService) BiaoYanRecommendMajorListSetEnrollProbability(recommendMajorList *[]dto.SchoolMajorDTO, userScoreVO vo.UserScoreVO) { + if recommendMajorList == nil || len(*recommendMajorList) == 0 { + return + } + professionalCategory := userScoreVO.ProfessionalCategory + nowBatch := "本科" + + // 获取省控线 Map + historyScoreControlLineMap, err := s.historyScoreControlLineService.MapsBatchByProfessionalCategoryOfYear(common.NowYear, professionalCategory, userScoreVO.CognitioPolyclinic) + if err != nil { + return + } + + culturalScore := userScoreVO.CulturalScore + professionalScore := userScoreVO.ProfessionalScore + + var nowMajorTypeChild string + var batchKey string + for i := range *recommendMajorList { + item := &(*recommendMajorList)[i] + + rulesEnrollProbability := item.PrivateRulesEnrollProbability + probabilityOperator := item.PrivateProbabilityOperator + + // 当前子类型 + nowMajorTypeChild = item.MajorTypeChild + // 整合为 表演类-XXX子类型 + batchKey = professionalCategory + "-" + nowMajorTypeChild + "_" + item.Batch + + // 获取对应批次的省控线 + controlLineData, ok := historyScoreControlLineMap[batchKey] + if !ok { + if val, okDefault := historyScoreControlLineMap["本科"]; okDefault { + controlLineData = val + } else { + continue + } + } + + // 判断子类型,使用对应的成绩进行计算 + professionalScore = userScoreVO.ProfessionalCategoryChildrenScore[item.MajorTypeChild] + + culturalControlLine := controlLineData.CulturalScore + specialControlLine := controlLineData.SpecialScore + + if rulesEnrollProbability == "" { + continue + } + + // 补全 probabilityOperator 逻辑 + if rulesEnrollProbability == "文过专排" && probabilityOperator == "" { + probabilityOperator = "文*0+专*1" + } else if rulesEnrollProbability == "专过文排" && probabilityOperator == "" { + probabilityOperator = "文*1+专*0" + } + + if probabilityOperator == "" { + item.EnrollProbability = common.Number5 + continue + } + + // 判断其他录取要求 + if !calc.OtherScoreJudge(professionalScore, userScoreVO, *item) { + item.EnrollProbability = common.Number0 + continue + } + + // 判断是否过省控线 + if !calc.CrossingControlLine(rulesEnrollProbability, culturalScore, professionalScore, culturalControlLine, specialControlLine) { + item.EnrollProbability = common.Number0 + continue + } + + // 计算学生折合分 + studentScore := calc.ConvertIntoScore(rulesEnrollProbability, culturalScore, professionalScore, probabilityOperator) + item.PrivateStudentScore = studentScore + item.StudentScore = studentScore + + // 权限检查 + if !calc.HasComputeEnrollProbabilityPermissions(nowBatch, item.Batch) { + item.EnrollProbability = common.Number0 + continue + } + + // 录取方式计算 + if common.CulturalControlLineGuoMain == rulesEnrollProbability { + if len(item.HistoryMajorEnrollList) == 0 { + item.EnrollProbability = common.Number0 + continue + } + item.EnrollProbability = (studentScore * item.HistoryMajorEnrollList[0].AdmissionLine) * common.Number0p75 + if studentScore >= item.HistoryMajorEnrollList[0].AdmissionLine { + item.EnrollProbability *= common.Number0p5 + } + continue + } else { + nowYearProvincialControlLine := calc.ConvertIntoScore(rulesEnrollProbability, culturalControlLine, specialControlLine, probabilityOperator) + if nowYearProvincialControlLine <= 0 { + item.EnrollProbability = common.Number0 + continue + } + + diffMap := calc.ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability(item.MajorType, rulesEnrollProbability, probabilityOperator, item.HistoryMajorEnrollMap) + historyThreeYearDiff := diffMap["scoreDifference"].(float64) + + if historyThreeYearDiff == 0 { + item.EnrollProbability = common.Number0 + continue + } + + nowYearDiff := studentScore - nowYearProvincialControlLine + + enrollProbability := calc.CommonCheckEnrollProbability(nowYearDiff, historyThreeYearDiff) + item.EnrollProbability = calc.CommonCheckEnrollProbabilityBeilv(enrollProbability) + } + } +} + +// YinYueRecommendMajorListSetEnrollProbability 音乐类 获取录取率 +func (s *YxCalculationMajorService) YinYueRecommendMajorListSetEnrollProbability(recommendMajorList *[]dto.SchoolMajorDTO, userScoreVO vo.UserScoreVO) { + if recommendMajorList == nil || len(*recommendMajorList) == 0 { + return + } + professionalCategory := userScoreVO.ProfessionalCategory + nowBatch := "本科" + + // 获取省控线 Map + historyScoreControlLineMap, err := s.historyScoreControlLineService.MapsBatchByProfessionalCategoryOfYear(common.NowYear, professionalCategory, userScoreVO.CognitioPolyclinic) + if err != nil { + return + } + + culturalScore := userScoreVO.CulturalScore + professionalScore := userScoreVO.ProfessionalScore + + var rulesEnrollProbability string + var probabilityOperator string + var nowMajorTypeChild string + var batchKey string + for i := range *recommendMajorList { + item := &(*recommendMajorList)[i] + + rulesEnrollProbability = item.PrivateRulesEnrollProbability + probabilityOperator = item.PrivateProbabilityOperator + + // 当前子类型 + nowMajorTypeChild = item.MajorTypeChild + + // 主考科目 + if "音乐表演" == nowMajorTypeChild { + nowMajorTypeChild += item.MainSubjects + } + + // 整合为 表演类-XXX子类型 + if "高职高专" == item.Batch { + batchKey = professionalCategory + "-" + nowMajorTypeChild + "_" + item.Batch + } else { + batchKey = professionalCategory + "-" + nowMajorTypeChild + "_本科" + } + + // 获取对应批次的省控线 + controlLineData, ok := historyScoreControlLineMap[batchKey] + if !ok { + continue + // if val, okDefault := historyScoreControlLineMap["本科"]; okDefault { + // controlLineData = val + // } else { + // continue + // } + } + + // 判断子类型,使用对应的成绩进行计算 + professionalScore = userScoreVO.ProfessionalCategoryChildrenScore[nowMajorTypeChild] + + culturalControlLine := controlLineData.CulturalScore + specialControlLine := controlLineData.SpecialScore + + if rulesEnrollProbability == "" { + continue + } + + // 补全 probabilityOperator 逻辑 + if rulesEnrollProbability == "文过专排" && probabilityOperator == "" { + probabilityOperator = "文*0+专*1" + } else if rulesEnrollProbability == "专过文排" && probabilityOperator == "" { + probabilityOperator = "文*1+专*0" + } + + if probabilityOperator == "" { + item.EnrollProbability = common.Number5 + continue + } + + // 判断其他录取要求 + if !calc.OtherScoreJudge(professionalScore, userScoreVO, *item) { + item.EnrollProbability = common.Number0 + continue + } + + // 判断是否过省控线 + if !calc.CrossingControlLine(rulesEnrollProbability, culturalScore, professionalScore, culturalControlLine, specialControlLine) { + item.EnrollProbability = common.Number0 + continue + } + + // 计算学生折合分 + studentScore := calc.ConvertIntoScore(rulesEnrollProbability, culturalScore, professionalScore, probabilityOperator) + item.PrivateStudentScore = studentScore + item.StudentScore = studentScore + + // 权限检查 + if !calc.HasComputeEnrollProbabilityPermissions(nowBatch, item.Batch) { + item.EnrollProbability = common.Number0 + continue + } + + // 录取方式计算 + if common.CulturalControlLineGuoMain == rulesEnrollProbability { + if len(item.HistoryMajorEnrollList) == 0 { + item.EnrollProbability = common.Number0 + continue + } + item.EnrollProbability = (studentScore * item.HistoryMajorEnrollList[0].AdmissionLine) * common.Number0p75 + if studentScore >= item.HistoryMajorEnrollList[0].AdmissionLine { + item.EnrollProbability *= common.Number0p5 + } + continue + } else { + nowYearProvincialControlLine := calc.ConvertIntoScore(rulesEnrollProbability, culturalControlLine, specialControlLine, probabilityOperator) + if nowYearProvincialControlLine <= 0 { + item.EnrollProbability = common.Number0 + continue + } + + diffMap := calc.ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability(item.MajorType, rulesEnrollProbability, probabilityOperator, item.HistoryMajorEnrollMap) + historyThreeYearDiff := diffMap["scoreDifference"].(float64) + + if historyThreeYearDiff == 0 { + item.EnrollProbability = common.Number0 + continue + } + + nowYearDiff := studentScore - nowYearProvincialControlLine + + enrollProbability := calc.CommonCheckEnrollProbability(nowYearDiff, historyThreeYearDiff) + item.EnrollProbability = calc.CommonCheckEnrollProbabilityBeilv(enrollProbability) + } + } +} + +// BetaRecommendMajorListSetEnrollProbability 美术与设计类,书法类,体育类 获取录取率 +func (s *YxCalculationMajorService) BetaRecommendMajorListSetEnrollProbability(recommendMajorList *[]dto.SchoolMajorDTO, userScoreVO vo.UserScoreVO) { + if recommendMajorList == nil || len(*recommendMajorList) == 0 { + return + } + professionalCategory := userScoreVO.ProfessionalCategory + nowBatch := "本科" + + // 获取省控线 Map + historyScoreControlLineMap, err := s.historyScoreControlLineService.MapsBatchByProfessionalCategoryOfYear(common.NowYear, professionalCategory, userScoreVO.CognitioPolyclinic) + if err != nil { + return + } + + culturalScore := userScoreVO.CulturalScore + professionalScore := userScoreVO.ProfessionalScore + + for i := range *recommendMajorList { + item := &(*recommendMajorList)[i] + + rulesEnrollProbability := item.PrivateRulesEnrollProbability + probabilityOperator := item.PrivateProbabilityOperator + + // 获取对应批次的省控线 + controlLineData, ok := historyScoreControlLineMap[item.Batch] + if !ok { + if val, okDefault := historyScoreControlLineMap["本科"]; okDefault { + controlLineData = val + } else { + continue + } + } + + culturalControlLine := controlLineData.CulturalScore + specialControlLine := controlLineData.SpecialScore + + if rulesEnrollProbability == "" { + continue + } + + // 补全 probabilityOperator 逻辑 + if rulesEnrollProbability == "文过专排" && probabilityOperator == "" { + probabilityOperator = "文*0+专*1" + } else if rulesEnrollProbability == "专过文排" && probabilityOperator == "" { + probabilityOperator = "文*1+专*0" + } + + if probabilityOperator == "" { + item.EnrollProbability = common.Number5 + continue + } + + // 判断其他录取要求 + if !calc.OtherScoreJudge(professionalScore, userScoreVO, *item) { + item.EnrollProbability = common.Number0 + continue + } + + // 25年专业录取原则变动 (体育类特殊逻辑) + if item.MajorType == "体育类" { + specialSchoolCodes := []string{"6530", "6085", "6110", "6065", "6050"} + isSpecial := false + for _, code := range specialSchoolCodes { + if item.SchoolCode == code { + isSpecial = true + break + } + } + if isSpecial { + item.EnrollProbability = common.Number0 + continue + } + } + + // 判断是否过省控线 + if !calc.CrossingControlLine(rulesEnrollProbability, culturalScore, professionalScore, culturalControlLine, specialControlLine) { + item.EnrollProbability = common.Number0 + continue + } + + // 计算学生折合分 + studentScore := calc.ConvertIntoScore(rulesEnrollProbability, culturalScore, professionalScore, probabilityOperator) + item.PrivateStudentScore = studentScore + item.StudentScore = studentScore + + // 权限检查 + if !calc.HasComputeEnrollProbabilityPermissions(nowBatch, item.Batch) { + item.EnrollProbability = common.Number0 + continue + } + + // 录取方式计算 + if common.CulturalControlLineGuoMain == rulesEnrollProbability { + if len(item.HistoryMajorEnrollList) == 0 { + item.EnrollProbability = common.Number0 + continue + } + item.EnrollProbability = (studentScore * item.HistoryMajorEnrollList[0].AdmissionLine) * common.Number0p75 + if studentScore >= item.HistoryMajorEnrollList[0].AdmissionLine { + item.EnrollProbability *= common.Number0p5 + } + continue + } else { + nowYearProvincialControlLine := calc.ConvertIntoScore(rulesEnrollProbability, culturalControlLine, specialControlLine, probabilityOperator) + if nowYearProvincialControlLine <= 0 { + item.EnrollProbability = common.Number0 + continue + } + + diffMap := calc.ComputeHistoryMajorEnrollScoreLineDifferenceWithRulesEnrollProbability(item.MajorType, rulesEnrollProbability, probabilityOperator, item.HistoryMajorEnrollMap) + historyThreeYearDiff := diffMap["scoreDifference"].(float64) + + if historyThreeYearDiff == 0 { + item.EnrollProbability = common.Number0 + continue + } + + nowYearDiff := studentScore - nowYearProvincialControlLine + + enrollProbability := calc.CommonCheckEnrollProbability(nowYearDiff, historyThreeYearDiff) + item.EnrollProbability = calc.CommonCheckEnrollProbabilityBeilv(enrollProbability) + } + } +} + +// 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 +} diff --git a/server/modules/yx/service/yx_history_major_enroll_service.go b/server/modules/yx/service/yx_history_major_enroll_service.go new file mode 100644 index 0000000..8861fac --- /dev/null +++ b/server/modules/yx/service/yx_history_major_enroll_service.go @@ -0,0 +1,136 @@ +// Package service 业务逻辑层 +package service + +import ( + "server/common" + "server/config" + "server/modules/yx/dto" + "server/modules/yx/entity" + "server/modules/yx/mapper" + "server/modules/yx/vo" + + "github.com/google/uuid" +) + +type YxHistoryMajorEnrollService struct { + *common.BaseService[entity.YxHistoryMajorEnroll] + mapper *mapper.YxHistoryMajorEnrollMapper +} + +func NewYxHistoryMajorEnrollService() *YxHistoryMajorEnrollService { + mapper := mapper.NewYxHistoryMajorEnrollMapper() + return &YxHistoryMajorEnrollService{ + BaseService: common.NewBaseService[entity.YxHistoryMajorEnroll](), + mapper: mapper, + } +} + +// RecommendMajorDTOListSetHistoryInfo 填充历史录取信息 +func (s *YxHistoryMajorEnrollService) RecommendMajorDTOListSetHistoryInfo(dtoList *[]dto.SchoolMajorDTO, years []string) { + if dtoList == nil || len(*dtoList) == 0 { + return + } + + schoolCodes := make([]string, 0) + majorNames := make([]string, 0) + category := (*dtoList)[0].Category // 假设同一批次查询类别相同 + majorType := (*dtoList)[0].MajorType + + for _, item := range *dtoList { + schoolCodes = append(schoolCodes, item.SchoolCode) + majorNames = append(majorNames, item.MajorName) + } + + historyList, err := s.ListBySchoolCodesAndMajorNames(schoolCodes, majorNames, category, majorType, years) + if err != nil { + return // Log error + } + + // Group by SchoolCode + MajorName 并转换为 VO + historyMap := make(map[string][]vo.YxHistoryMajorEnrollVO) + for _, h := range historyList { + key := h.SchoolCode + "_" + h.MajorName + vo := vo.YxHistoryMajorEnrollVO{ + 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 { + item := &(*dtoList)[i] + key := item.SchoolCode + "_" + item.MajorName + if list, ok := historyMap[key]; ok { + item.HistoryMajorEnrollList = list + } + } +} + +// ListBySchoolCodesAndMajorNames 根据学校代码和专业名称查询历史招生数据 +func (s *YxHistoryMajorEnrollService) ListBySchoolCodesAndMajorNames( + schoolCodes []string, + majorNames []string, + category string, + majorType string, + years []string, +) ([]entity.YxHistoryMajorEnroll, error) { + var items []entity.YxHistoryMajorEnroll + sql := ` + SELECT hme.* + FROM yx_history_major_enroll hme + WHERE 1=1 AND hme.rules_enroll_probability is not null AND hme.admission_line > 0 + ` + params := []interface{}{} + if majorType != "" { + sql += " AND hme.major_type = ?" + params = append(params, majorType) + } + if category != "" { + sql += " AND hme.category = ?" + params = append(params, category) + } + if len(schoolCodes) > 0 { + sql += " AND hme.school_code IN ?" + params = append(params, schoolCodes) + } + if len(majorNames) > 0 { + sql += " AND hme.major_name IN ?" + params = append(params, majorNames) + } + if len(years) > 0 { + sql += " AND hme.year IN ?" + params = append(params, years) + } + sql += " ORDER BY hme.year DESC" + err := config.DB.Raw(sql, params...).Scan(&items).Error + return items, err +} + +// GetByYear 根据年份查询历史招生数据 +func (s *YxHistoryMajorEnrollService) GetByYear(year string) ([]entity.YxHistoryMajorEnroll, error) { + return s.mapper.FindByYear(year) +} + +// Create 创建历史招生数据(使用 UUID 生成 ID) +func (s *YxHistoryMajorEnrollService) Create(item *entity.YxHistoryMajorEnroll) error { + item.ID = uuid.New().String() + return s.mapper.Create(item) +} + +// BatchUpsert 批量插入或更新(使用 UUID 生成 ID) +func (s *YxHistoryMajorEnrollService) BatchUpsert(items []entity.YxHistoryMajorEnroll, updateColumns []string) error { + for i := range items { + if items[i].ID == "" { + items[i].ID = uuid.New().String() + } + } + return s.mapper.BatchUpsert(items, updateColumns) +} diff --git a/server/modules/yx/service/yx_history_score_control_line_service.go b/server/modules/yx/service/yx_history_score_control_line_service.go new file mode 100644 index 0000000..321053c --- /dev/null +++ b/server/modules/yx/service/yx_history_score_control_line_service.go @@ -0,0 +1,47 @@ +package service + +import ( + "server/modules/yx/entity" + "server/modules/yx/mapper" +) + +type YxHistoryScoreControlLineService struct { + mapper *mapper.YxHistoryScoreControlLineMapper +} + +func NewYxHistoryScoreControlLineService() *YxHistoryScoreControlLineService { + return &YxHistoryScoreControlLineService{ + mapper: mapper.NewYxHistoryScoreControlLineMapper(), + } +} + +// MapsBatchByProfessionalCategoryOfYear 获取指定年份、专业类别、文理科的省控线,并以批次为 Key 返回 Map +func (s *YxHistoryScoreControlLineService) MapsBatchByProfessionalCategoryOfYear(year, professionalCategory, category string) (map[string]entity.YxHistoryScoreControlLine, error) { + list, err := s.mapper.SelectByYearAndCategory(year, professionalCategory, category) + if err != nil { + return nil, err + } + + result := make(map[string]entity.YxHistoryScoreControlLine) + + switch professionalCategory { + case "表演类": + fallthrough + case "音乐类": + for _, item := range list { + result[item.ProfessionalCategory+"_"+item.Batch] = item + } + default: + for _, item := range list { + // 复制对象逻辑,处理"本科A段"映射为"提前批" + if item.Batch == "本科A段" { + newItem := item + newItem.Batch = "提前批" + result[newItem.Batch] = newItem + } + result[item.Batch] = item + } + } + + return result, nil +} diff --git a/server/modules/yx/service/yx_school_major_service.go b/server/modules/yx/service/yx_school_major_service.go new file mode 100644 index 0000000..cc51a09 --- /dev/null +++ b/server/modules/yx/service/yx_school_major_service.go @@ -0,0 +1,39 @@ +// Package service 业务逻辑层 +package service + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/mapper" + + "github.com/google/uuid" +) + +type YxSchoolMajorService struct { + *common.BaseService[entity.YxSchoolMajor] + mapper *mapper.YxSchoolMajorMapper +} + +func NewYxSchoolMajorService() *YxSchoolMajorService { + mapper := mapper.NewYxSchoolMajorMapper() + return &YxSchoolMajorService{ + BaseService: common.NewBaseService[entity.YxSchoolMajor](), + mapper: mapper, + } +} + +// Create 创建院校专业(使用 UUID 生成 ID) +func (s *YxSchoolMajorService) Create(item *entity.YxSchoolMajor) error { + item.ID = uuid.New().String() + return s.mapper.Create(item) +} + +// BatchUpsert 批量插入或更新(使用 UUID 生成 ID) +func (s *YxSchoolMajorService) BatchUpsert(items []entity.YxSchoolMajor, updateColumns []string) error { + for i := range items { + if items[i].ID == "" { + items[i].ID = uuid.New().String() + } + } + return s.mapper.BatchUpsert(items, updateColumns) +} diff --git a/server/modules/yx/service/yx_user_score_service.go b/server/modules/yx/service/yx_user_score_service.go new file mode 100644 index 0000000..419828e --- /dev/null +++ b/server/modules/yx/service/yx_user_score_service.go @@ -0,0 +1,44 @@ +// Package service 业务逻辑层 +package service + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/mapper" + + "github.com/google/uuid" +) + +type YxUserScoreService struct { + *common.BaseService[entity.YxUserScore] + mapper *mapper.YxUserScoreMapper +} + +func NewYxUserScoreService() *YxUserScoreService { + mapper := mapper.NewYxUserScoreMapper() + return &YxUserScoreService{ + BaseService: common.NewBaseService[entity.YxUserScore](), + mapper: mapper, + } +} + +// UpdateFieldsByEntity 根据实体条件更新字段 +func (s *YxUserScoreService) UpdateFieldsByEntity(item *entity.YxUserScore, fields map[string]interface{}) error { + return s.mapper.UpdateFieldsByMultiCondition(item, fields) +} + +// Create 创建用户成绩(使用 UUID 生成 ID) +func (s *YxUserScoreService) Create(item *entity.YxUserScore) error { + item.ID = uuid.New().String() + return s.mapper.Create(item) +} + +// BatchUpsert 批量插入或更新(使用 UUID 生成 ID) +func (s *YxUserScoreService) BatchUpsert(items []entity.YxUserScore, updateColumns []string) error { + for i := range items { + if items[i].ID == "" { + items[i].ID = uuid.New().String() + } + } + return s.mapper.BatchUpsert(items, updateColumns) +} diff --git a/server/modules/yx/service/yx_volunteer_record_service.go b/server/modules/yx/service/yx_volunteer_record_service.go new file mode 100644 index 0000000..b8631af --- /dev/null +++ b/server/modules/yx/service/yx_volunteer_record_service.go @@ -0,0 +1,49 @@ +// Package service 业务逻辑层 +package service + +import ( + "server/common" + "server/modules/yx/entity" + "server/modules/yx/mapper" + + "github.com/google/uuid" +) + +type YxVolunteerRecordService struct { + *common.BaseService[entity.YxVolunteerRecord] + mapper *mapper.YxVolunteerRecordMapper +} + +func NewYxVolunteerRecordService() *YxVolunteerRecordService { + mapper := mapper.NewYxVolunteerRecordMapper() + return &YxVolunteerRecordService{ + BaseService: common.NewBaseService[entity.YxVolunteerRecord](), + mapper: mapper, + } +} + +// FindByVolunteerID 根据 volunteerID 查找志愿记录 +func (s *YxVolunteerRecordService) FindByVolunteerID(volunteerID string) ([]entity.YxVolunteerRecord, error) { + return s.mapper.FindByVolunteerID(volunteerID) +} + +// DeleteByVolunteerID 根据 volunteerID 删除志愿记录 +func (s *YxVolunteerRecordService) DeleteByVolunteerID(volunteerID string) error { + return s.mapper.DeleteByVolunteerID(volunteerID) +} + +// Create 创建志愿记录(使用 UUID 生成 ID) +func (s *YxVolunteerRecordService) Create(item *entity.YxVolunteerRecord) error { + item.ID = uuid.New().String() + return s.mapper.Create(item) +} + +// BatchUpsert 批量插入或更新(使用 UUID 生成 ID) +func (s *YxVolunteerRecordService) BatchUpsert(items []entity.YxVolunteerRecord, updateColumns []string) error { + for i := range items { + if items[i].ID == "" { + items[i].ID = uuid.New().String() + } + } + return s.mapper.BatchUpsert(items, updateColumns) +} diff --git a/server/modules/yx/service/yx_volunteer_service.go b/server/modules/yx/service/yx_volunteer_service.go new file mode 100644 index 0000000..d9375ac --- /dev/null +++ b/server/modules/yx/service/yx_volunteer_service.go @@ -0,0 +1,205 @@ +// Package service 业务逻辑层 +package service + +import ( + "fmt" + "server/common" + userVO "server/modules/user/vo" + "server/modules/yx/dto" + "server/modules/yx/entity" + "server/modules/yx/mapper" + yxVO "server/modules/yx/vo" + "time" +) + +// ScoreService 定义成绩服务的接口,用于解耦 +type ScoreService interface { + GetByID(id string) (userVO.UserScoreVO, error) + GetActiveScoreByID(userID string) (entity.YxUserScore, error) + GetActiveScoreID(userID string) (string, error) + UpdateFields(id string, fields map[string]interface{}) error + Delete(id string) error +} + +type YxVolunteerService struct { + *common.BaseService[entity.YxVolunteer] + mapper *mapper.YxVolunteerMapper + volunteerRecordMapper *mapper.YxVolunteerRecordMapper +} + +func NewYxVolunteerService() *YxVolunteerService { + return &YxVolunteerService{ + BaseService: common.NewBaseService[entity.YxVolunteer](), + mapper: mapper.NewYxVolunteerMapper(), + volunteerRecordMapper: mapper.NewYxVolunteerRecordMapper(), + } +} + +// FindActiveByScoreId 根据 scoreId 查找激活的志愿单 +func (s *YxVolunteerService) FindActiveByScoreId(scoreId string) (*entity.YxVolunteer, error) { + return s.mapper.FindActiveByScoreId(scoreId) +} + +// CreateByScoreId 根据 ScoreId 创建新志愿表 +func (s *YxVolunteerService) CreateByScoreId(scoreId string, userId string) error { + volunteer := entity.YxVolunteer{} + volunteer.ID = common.GenerateStringID() + // 志愿表名称格式 时间戳 20260101134501志愿表 + volunteer.VolunteerName = time.Now().Format("20060102150405") + "志愿表" + + volunteer.ScoreId = scoreId + volunteer.CreateType = "1" + volunteer.State = "1" + volunteer.CreateBy = userId + volunteer.CreateTime = time.Now() + volunteer.UpdateTime = time.Now() + + // 先关闭当前用户其他志愿单 + if err := s.mapper.CloseOtherVolunteer(userId); err != nil { + return fmt.Errorf("关闭其他志愿表失败: %w", err) + } + // 创建志愿表 + 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 更新志愿表名称 +func (s *YxVolunteerService) UpdateName(id, name, userID string) error { + volunteer, err := s.GetByID(id) + if err != nil { + return err + } + if volunteer.CreateBy != userID { + return fmt.Errorf("无权修改该志愿单") + } + return s.mapper.UpdateFields(id, map[string]interface{}{"volunteer_name": name, "update_by": userID, "update_time": time.Now()}) +} + +// ListByUser 查询用户的志愿单列表(包含成绩信息) +func (s *YxVolunteerService) ListByUser(userID string, page, size int) ([]yxVO.UserVolunteerVO, int64, error) { + return s.mapper.ListByUser(userID, page, size) +} + +// DeleteVolunteer 删除志愿单 +func (s *YxVolunteerService) DeleteVolunteer(id, userID string) error { + volunteer, err := s.GetByID(id) + if err != nil { + return err + } + if volunteer.CreateBy != userID { + return fmt.Errorf("无权删除该志愿单") + } + if volunteer.State == "1" { + return fmt.Errorf("激活状态的志愿单不可删除") + } + s.volunteerRecordMapper.BatchDeleteByVolunteerId(id) + s.mapper.Delete(id) + // 1. 获取成绩单信息 + //scoreObj, err := scoreService.GetByID(volunteer.ScoreId) + // if err != nil { + // return fmt.Errorf("获取成绩单失败: %w", err) + // } + // _ = scoreObj // temporarily ignore until cascade logic is refined + + // 这里需要从 scoreObj 中获取 CalculationTableName 和 ID + // 由于是 interface{},可以通过反射或简单断言(如果能在 yx 定义共用结构最好) + // 或者直接在 scoreService 中增加一个获取这些字段的方法 + + // 简化:这里我们假设 scoreObj 其实包含了这些信息。 + // 为了真正的解耦,我们可以在 scoreService 中增加专门的方法。 + return nil // 暂时回退复杂逻辑 +} + +// SwitchVolunteer 切换激活的志愿单 +func (s *YxVolunteerService) SwitchVolunteer(id, userID string, scoreService ScoreService) error { + if err := s.mapper.CloseOtherVolunteer(userID); err != nil { + return err + } + if err := s.mapper.UpdateFields(id, map[string]interface{}{"state": "1", "update_by": userID, "update_time": time.Now()}); err != nil { + return err + } + + volunteer, err := s.GetByID(id) + if err != nil { + return err + } + + // 获取之前的激活成绩并关闭 + activeScoreID, err := scoreService.GetActiveScoreID(userID) + if err != nil { + return fmt.Errorf("获取激活成绩失败: %w", err) + } + if activeScoreID != "" && activeScoreID != volunteer.ScoreId { + scoreService.UpdateFields(activeScoreID, map[string]interface{}{"state": "0"}) + } + 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, + } +} diff --git a/server/modules/yx/vo/user_volunteer_vo.go b/server/modules/yx/vo/user_volunteer_vo.go new file mode 100644 index 0000000..e5b0b53 --- /dev/null +++ b/server/modules/yx/vo/user_volunteer_vo.go @@ -0,0 +1,19 @@ +package vo + +import ( + "server/types" +) + +// UserVolunteerVO 志愿单视图对象,关联成绩信息 +type UserVolunteerVO struct { + ID string `gorm:"column:id;primaryKey" json:"id"` + VolunteerName string `gorm:"column:volunteer_name" json:"volunteerName"` // 志愿单名称 + ScoreId string `gorm:"column:score_id" json:"scoreId"` // 使用成绩id + CreateType string `gorm:"column:create_type;default:1" json:"createType"` // 生成类型(1.手动生成,2.智能生成) + State string `gorm:"column:state;default:0" json:"state"` // 志愿单状态(0-否,1.正在使用,2-历史) + 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"` // 专业成绩分 +} diff --git a/server/modules/yx/vo/yx_calculation_major_vo.go b/server/modules/yx/vo/yx_calculation_major_vo.go new file mode 100644 index 0000000..16cb845 --- /dev/null +++ b/server/modules/yx/vo/yx_calculation_major_vo.go @@ -0,0 +1,40 @@ +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"` // 更新时间 +} \ No newline at end of file diff --git a/server/modules/yx/vo/yx_history_major_enroll_vo.go b/server/modules/yx/vo/yx_history_major_enroll_vo.go new file mode 100644 index 0000000..aa4bb47 --- /dev/null +++ b/server/modules/yx/vo/yx_history_major_enroll_vo.go @@ -0,0 +1,17 @@ +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"` // 录取规则概率(山东) + // 其他字段... +} \ No newline at end of file diff --git a/server/modules/yx/vo/yx_volunteer_vo.go b/server/modules/yx/vo/yx_volunteer_vo.go new file mode 100644 index 0000000..9783746 --- /dev/null +++ b/server/modules/yx/vo/yx_volunteer_vo.go @@ -0,0 +1,17 @@ +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"` // 所属部门 +} \ No newline at end of file diff --git a/server/tests/help.md b/server/tests/help.md new file mode 100644 index 0000000..b83867d --- /dev/null +++ b/server/tests/help.md @@ -0,0 +1,6 @@ +``` +cd server/tests +go test -v -run TestRedisOperation # 运行 Redis 测试 +go test -v -run TestSchoolMajorQuery # 运行 SQL 查询测试 +go test -v # 运行所有测试 +``` \ No newline at end of file diff --git a/server/tests/init_test.go b/server/tests/init_test.go new file mode 100644 index 0000000..d1f278d --- /dev/null +++ b/server/tests/init_test.go @@ -0,0 +1,30 @@ +package tests + +import ( + "log" + "os" + "path/filepath" + "runtime" + "server/config" +) + +func init() { + // 切换工作目录到 server 根目录,以便读取配置文件(如果有) + // 这里直接初始化配置,不依赖文件,但保留目录切换逻辑以防万一 + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Join(filepath.Dir(filename), "..") + err := os.Chdir(dir) + if err != nil { + log.Fatal("切换工作目录失败:", err) + } + + // 设置测试环境 + os.Setenv("GO_ENV", "test") + // 加载配置 + config.LoadConfig() + + // 初始化数据库 + config.InitDB() + // 初始化Redis + config.InitRedis() +} diff --git a/server/tests/service_test.go b/server/tests/service_test.go new file mode 100644 index 0000000..021ff01 --- /dev/null +++ b/server/tests/service_test.go @@ -0,0 +1,89 @@ +package tests + +import ( + "context" + "server/common" + "server/config" + "server/modules/user/vo" + "server/modules/yx/service" + "testing" + "time" +) + +// TestRedisOperation 测试 Redis 读写 +func TestRedisOperation(t *testing.T) { + ctx := context.Background() + key := "test:hello" + value := "world_" + time.Now().Format("2006-01-02 15:04:05") + + // 写 Redis + err := config.RDB.Set(ctx, key, value, time.Minute).Err() + if err != nil { + t.Fatalf("Redis 写入失败: %v", err) + } + t.Logf("Redis 写入成功: %s -> %s", key, value) + + // 读 Redis + val, err := config.RDB.Get(ctx, key).Result() + if err != nil { + t.Fatalf("Redis 读取失败: %v", err) + } + t.Logf("Redis 读取成功: %s", val) + + if val != value { + t.Errorf("Redis 值不匹配: 期望 %s, 实际 %s", value, val) + } +} + +// TestSchoolMajorQuery 测试院校专业复杂查询 Mapper +func TestSchoolMajorQuery(t *testing.T) { + cs := service.NewYxCalculationMajorService() + userScoreVO := vo.UserScoreVO{ + ProfessionalCategory: "美术与设计类", + CognitioPolyclinic: "文科", + CulturalScore: 500, + ProfessionalScore: 250, + } + start := time.Now() + items, err := cs.ListByUserQueryType("美术与设计类", "文科", []string{}) + if err != nil { + t.Fatalf("查询失败: %v", err) + } + + duration := time.Now().UnixMilli() - start.UnixMilli() + common.LogInfo("CheckEnrollProbability elapsed: %v", duration) + t.Logf("查询成功,共找到 %d 条记录", len(items)) + + startTime := time.Now() + cs.CheckEnrollProbability(&items, userScoreVO) + elapsed := time.Now().UnixMilli() - startTime.UnixMilli() + common.LogInfo("CheckEnrollProbability elapsed: %v", elapsed) + + // 插入到数据库 + // err = cs.BatchCreateBySchoolMajorDTO(items) + if err != nil { + t.Fatalf("插入数据库失败: %v", err) + } + t.Logf("成功插入 %d 条记录到数据库", len(items)) + + // 打印前几条结果 + // for i, item := range items { + // if i >= 3 { + // break + // } + // jsonBytes, _ := json.Marshal(item) + // t.Logf("记录 #%d: %s", i+1, string(jsonBytes)) + // } +} + +// TestIDGenerator 测试 ID 生成器 +func TestIDGenerator(t *testing.T) { + idLong := common.GenerateLongID() + t.Logf("生成的 Long ID: %d", idLong) + + idStr := common.GenerateStringID() + t.Logf("生成的 String ID: %s", idStr) + + // idTimestamp := common.GetIDGenerator().GenerateTimestampLongID() + // t.Logf("生成的 Timestamp Long ID: %d", idTimestamp) +} diff --git a/server/types/date_time.go b/server/types/date_time.go new file mode 100644 index 0000000..97042b3 --- /dev/null +++ b/server/types/date_time.go @@ -0,0 +1,71 @@ +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()) +} \ No newline at end of file diff --git a/task_detail_2026_03_14.md b/task_detail_2026_03_14.md new file mode 100644 index 0000000..354b362 --- /dev/null +++ b/task_detail_2026_03_14.md @@ -0,0 +1,32 @@ +# 任务执行摘要 + +## 会话 ID: local +- [2026-03-14 19:36:24] +- **执行原因**: 说明如何使用 Zed 调试运行 server 内的 Go 项目 +- **执行过程**: + 1. 确认项目结构与 server 目录位置。 + 2. 给出在 Zed 中配置调试与运行的步骤与注意点。 +- **执行结果**: 已提供 Zed 调试运行指引 + + +# 任务执行摘要 + +## 会话 ID: local +- [2026-03-14 19:40:29] +- **执行原因**: 说明 Zed 是否支持类似 VSCode 的直接调试与扩展 +- **执行过程**: + 1. 查证 Zed 调试器与 Go 调试适配器的官方文档。 + 2. 给出在 Zed 中直接调试的入口与配置要点。 +- **执行结果**: 已给出可直接调试的说明与扩展结论 + + +# 任务执行摘要 + +## 会话 ID: local +- [2026-03-14 19:53:40] +- **执行原因**: 说明 Zed 中 Cmd+点击的跳转能力与路径跳转方式 +- **执行过程**: + 1. 查阅 Zed 官方文档与官方博客关于跳转与快捷键说明。 + 2. 汇总可用的跳转方式与路径打开替代方案。 +- **执行结果**: 已给出 Cmd+点击跳转与路径打开建议 +