From c2c9cc4e50214fcb33f6cb92a1ce5a5b77e15714 Mon Sep 17 00:00:00 2001 From: awinx Date: Sun, 14 Dec 2025 19:38:02 +0800 Subject: [PATCH] =?UTF-8?q?stu01:=E5=88=9B=E5=BB=BA=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=88=B0=E5=AD=A6=E7=94=9F=E6=8E=A5=E5=8F=A3=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stu01/HELP.md | 32 ++++ stu01/db.sql | 89 +++++++++++ stu01/pom.xml | 147 ++++++++++++++++++ stu01/prompt.txt | 41 +++++ .../top/awinx/stu01/Stu01Application.java | 16 ++ .../java/top/awinx/stu01/common/Result.java | 49 ++++++ .../top/awinx/stu01/config/CorsConfig.java | 34 ++++ .../top/awinx/stu01/config/JwtConfig.java | 54 +++++++ .../top/awinx/stu01/config/WebMvcConfig.java | 32 ++++ .../stu01/controller/LoginController.java | 89 +++++++++++ .../stu01/controller/StudentController.java | 56 +++++++ .../stu01/controller/TestController.java | 16 ++ .../top/awinx/stu01/dto/LoginRequest.java | 15 ++ .../top/awinx/stu01/dto/StudentAddDTO.java | 39 +++++ .../top/awinx/stu01/dto/StudentQueryDTO.java | 24 +++ .../java/top/awinx/stu01/entity/Student.java | 53 +++++++ .../java/top/awinx/stu01/entity/SysUser.java | 20 +++ .../stu01/exception/BusinessException.java | 12 ++ .../exception/GlobalExceptionHandler.java | 80 ++++++++++ .../stu01/interceptor/JwtInterceptor.java | 54 +++++++ .../top/awinx/stu01/mapper/StudentMapper.java | 12 ++ .../top/awinx/stu01/mapper/SysUserMapper.java | 14 ++ .../awinx/stu01/service/StudentService.java | 28 ++++ .../awinx/stu01/service/SysUserService.java | 12 ++ .../service/impl/StudentServiceImpl.java | 73 +++++++++ .../service/impl/SysUserServiceImpl.java | 47 ++++++ .../java/top/awinx/stu01/utils/JwtUtil.java | 86 ++++++++++ stu01/src/main/resources/application.yml | 46 ++++++ .../main/resources/mapper/SysUserMapper.xml | 7 + .../awinx/stu01/Stu01ApplicationTests.java | 13 ++ stu01/target/classes/application.yml | 46 ++++++ stu01/target/classes/mapper/SysUserMapper.xml | 7 + .../compile/default-compile/createdFiles.lst | 11 ++ .../compile/default-compile/inputFiles.lst | 23 +++ .../default-testCompile/createdFiles.lst | 1 + .../default-testCompile/inputFiles.lst | 1 + 36 files changed, 1379 insertions(+) create mode 100644 stu01/HELP.md create mode 100644 stu01/db.sql create mode 100644 stu01/pom.xml create mode 100644 stu01/prompt.txt create mode 100644 stu01/src/main/java/top/awinx/stu01/Stu01Application.java create mode 100644 stu01/src/main/java/top/awinx/stu01/common/Result.java create mode 100644 stu01/src/main/java/top/awinx/stu01/config/CorsConfig.java create mode 100644 stu01/src/main/java/top/awinx/stu01/config/JwtConfig.java create mode 100644 stu01/src/main/java/top/awinx/stu01/config/WebMvcConfig.java create mode 100644 stu01/src/main/java/top/awinx/stu01/controller/LoginController.java create mode 100644 stu01/src/main/java/top/awinx/stu01/controller/StudentController.java create mode 100644 stu01/src/main/java/top/awinx/stu01/controller/TestController.java create mode 100644 stu01/src/main/java/top/awinx/stu01/dto/LoginRequest.java create mode 100644 stu01/src/main/java/top/awinx/stu01/dto/StudentAddDTO.java create mode 100644 stu01/src/main/java/top/awinx/stu01/dto/StudentQueryDTO.java create mode 100644 stu01/src/main/java/top/awinx/stu01/entity/Student.java create mode 100644 stu01/src/main/java/top/awinx/stu01/entity/SysUser.java create mode 100644 stu01/src/main/java/top/awinx/stu01/exception/BusinessException.java create mode 100644 stu01/src/main/java/top/awinx/stu01/exception/GlobalExceptionHandler.java create mode 100644 stu01/src/main/java/top/awinx/stu01/interceptor/JwtInterceptor.java create mode 100644 stu01/src/main/java/top/awinx/stu01/mapper/StudentMapper.java create mode 100644 stu01/src/main/java/top/awinx/stu01/mapper/SysUserMapper.java create mode 100644 stu01/src/main/java/top/awinx/stu01/service/StudentService.java create mode 100644 stu01/src/main/java/top/awinx/stu01/service/SysUserService.java create mode 100644 stu01/src/main/java/top/awinx/stu01/service/impl/StudentServiceImpl.java create mode 100644 stu01/src/main/java/top/awinx/stu01/service/impl/SysUserServiceImpl.java create mode 100644 stu01/src/main/java/top/awinx/stu01/utils/JwtUtil.java create mode 100644 stu01/src/main/resources/application.yml create mode 100644 stu01/src/main/resources/mapper/SysUserMapper.xml create mode 100644 stu01/src/test/java/top/awinx/stu01/Stu01ApplicationTests.java create mode 100644 stu01/target/classes/application.yml create mode 100644 stu01/target/classes/mapper/SysUserMapper.xml create mode 100644 stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst diff --git a/stu01/HELP.md b/stu01/HELP.md new file mode 100644 index 0000000..794823e --- /dev/null +++ b/stu01/HELP.md @@ -0,0 +1,32 @@ +# Read Me First +The following was discovered as part of building this project: + +* The JVM level was changed from '25' to '24', review the [JDK Version Range](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions#jdk-version-range) on the wiki for more details. + +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.12/maven-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.4.12/maven-plugin/build-image.html) +* [Spring Web](https://docs.spring.io/spring-boot/3.4.12/reference/web/servlet.html) +* [MyBatis Framework](https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) +* [MyBatis Quick Start](https://github.com/mybatis/spring-boot-starter/wiki/Quick-Start) +* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/) + +### Maven Parent overrides + +Due to Maven's design, elements are inherited from the parent POM to the project POM. +While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. +To prevent this, the project POM contains empty overrides for these elements. +If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. + diff --git a/stu01/db.sql b/stu01/db.sql new file mode 100644 index 0000000..178201d --- /dev/null +++ b/stu01/db.sql @@ -0,0 +1,89 @@ +-- 切换到test01_db数据库 +USE stu01_db; + +-- 1. 用户表(sys_user):存储登录账号、密码、权限类型 +DROP TABLE IF EXISTS sys_user; +CREATE TABLE sys_user ( + id BIGINT AUTO_INCREMENT COMMENT '用户ID(主键)' PRIMARY KEY, + username VARCHAR(50) NOT NULL COMMENT '登录账号(唯一)', + password VARCHAR(50) NOT NULL COMMENT '登录密码(无需加密,测试用)', + user_type TINYINT NOT NULL COMMENT '权限类型:1-学生 2-教师 3-管理员', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + real_name VARCHAR(50) NOT NULL COMMENT '真实姓名' +) COMMENT '用户表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 添加账号唯一索引 +CREATE UNIQUE INDEX idx_sys_user_username ON sys_user(username); + +-- 插入测试数据(管理员账号:admin/123456;学生账号:stu001/123456;教师账号:tea001/123456) +INSERT INTO sys_user (username, password, user_type) VALUES +('admin', '123456', 3), +('stu001', '123456', 1), +('tea001', '123456', 2); + +-- 2. 学生表(student):存储学生基础信息 +DROP TABLE IF EXISTS student; +CREATE TABLE student ( + id BIGINT AUTO_INCREMENT COMMENT '学生ID(主键)' PRIMARY KEY, + stu_no VARCHAR(8) NOT NULL COMMENT '学号(8位纯数字,唯一)', + name VARCHAR(20) NOT NULL COMMENT '学生姓名', + major VARCHAR(30) NOT NULL COMMENT '专业(仅允许:计算机/数学/英语)', + user_id BIGINT NOT NULL COMMENT '关联用户ID', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + -- 外键关联用户表 + CONSTRAINT fk_student_user_id FOREIGN KEY (user_id) REFERENCES sys_user(id) +) COMMENT '学生表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 添加学号唯一索引 +CREATE UNIQUE INDEX idx_student_stu_no ON student(stu_no); + +-- 插入测试数据(关联学生账号stu001的user_id,需先查询sys_user中stu001的id再替换,示例中假设id=2) +INSERT INTO student (stu_no, name, major, user_id) VALUES ('20250001', '张三', '计算机', 2); + +-- 3. 教师表(teacher):存储教师基础信息 +DROP TABLE IF EXISTS teacher; +CREATE TABLE teacher ( + id BIGINT AUTO_INCREMENT COMMENT '教师ID(主键)' PRIMARY KEY, + tea_no VARCHAR(8) NOT NULL COMMENT '教师编号(8位纯数字,唯一)', + name VARCHAR(20) NOT NULL COMMENT '教师姓名', + user_id BIGINT NOT NULL COMMENT '关联用户ID', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + -- 外键关联用户表 + CONSTRAINT fk_teacher_user_id FOREIGN KEY (user_id) REFERENCES sys_user(id) +) COMMENT '教师表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 添加教师编号唯一索引 +CREATE UNIQUE INDEX idx_teacher_tea_no ON teacher(tea_no); + +-- 插入测试数据(关联教师账号tea001的user_id,示例中假设id=3) +INSERT INTO teacher (tea_no, name, user_id) VALUES ('20250001', '李老师', 3); + +-- 4. 课程表(course):存储课程信息,关联授课教师 +DROP TABLE IF EXISTS course; +CREATE TABLE course ( + id BIGINT AUTO_INCREMENT COMMENT '课程ID(主键)' PRIMARY KEY, + course_name VARCHAR(50) NOT NULL COMMENT '课程名称(仅允许中文/数字/字母,长度2-20)', + tea_id BIGINT NOT NULL COMMENT '关联教师ID', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + -- 外键关联教师表(用于教师删除时的关联拦截) + CONSTRAINT fk_course_tea_id FOREIGN KEY (tea_id) REFERENCES teacher(id) +) COMMENT '课程表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 插入测试数据(关联李老师的teacher_id,示例中假设id=1) +INSERT INTO course (course_name, tea_id) VALUES ('Java编程', 1); + +-- 5. 学分表(score):存储学生课程成绩,关联学生和课程 +DROP TABLE IF EXISTS score; +CREATE TABLE score ( + id BIGINT AUTO_INCREMENT COMMENT '学分ID(主键)' PRIMARY KEY, + stu_id BIGINT NOT NULL COMMENT '关联学生ID', + course_id BIGINT NOT NULL COMMENT '关联课程ID', + score INT NOT NULL COMMENT '分数(0-100)', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + -- 外键关联学生表和课程表 + CONSTRAINT fk_score_stu_id FOREIGN KEY (stu_id) REFERENCES student(id), + CONSTRAINT fk_score_course_id FOREIGN KEY (course_id) REFERENCES course(id) +) COMMENT '学分表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 插入测试数据(关联张三的student_id=1,Java编程的course_id=1) +INSERT INTO score (stu_id, course_id, score) VALUES (1, 1, 90); diff --git a/stu01/pom.xml b/stu01/pom.xml new file mode 100644 index 0000000..0ca0ffb --- /dev/null +++ b/stu01/pom.xml @@ -0,0 +1,147 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.12 + + + top.awinx + stu01 + 0.0.1-SNAPSHOT + stu01 + Demo project for Spring Boot + + + + + + + + + + + + + + + 24 + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.5 + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mybatis.spring.boot + mybatis-spring-boot-starter-test + 3.0.5 + test + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.14 + + + io.jsonwebtoken + jjwt-api + 0.13.0 + + + io.jsonwebtoken + jjwt-impl + 0.13.0 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.13.0 + runtime + + + + + jakarta.validation + jakarta.validation-api + 3.1.0 + + + + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + + + + + org.hibernate.validator + hibernate-validator-annotation-processor + 8.0.1.Final + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/stu01/prompt.txt b/stu01/prompt.txt new file mode 100644 index 0000000..7b711bc --- /dev/null +++ b/stu01/prompt.txt @@ -0,0 +1,41 @@ +一、核心协作规则重申 + + + 任务拆分:严格按照《学生管理系统(测试用例适配)开发任务计划表》执行,每个会话仅完成 1 个最小粒度的任务(如 1.1、1.2),确保任务足够简单、无遗漏; + 计划表更新:每完成 1 个任务后,在输出末尾更新计划表,标注对应任务 “完成状态” 为 “已完成”,并在 “备注” 栏记录执行细节、遇到的问题、后续注意点(规避遗忘 / 幻觉); + 代码要求:所有开发代码仅满足测试用例覆盖需求,尽可能简洁,无冗余逻辑、无过度设计 / 优化(如密码无需加密、并发仅保证接收请求即可); + 协作模式:由助手主导任务拆解与代码编写,用户负责实际操作(创建项目、粘贴代码、运行调试),助手需提供 “复制即用” 的代码及清晰的操作指引; + 输出结构:每个任务的输出需包含「操作指引」「核心代码」「验证方式」「更新后的计划表」四部分,确保用户可直接落地。 + + +二、任务计划表引用 +所有操作均基于下方《学生管理系统(测试用例适配)开发任务计划表》,每次会话需携带该计划表的最新版本(含已完成任务标注): +阶段 任务 ID 任务名称 核心目标 关键提示词 完成状态 备注(仅保留执行结果和后续影响) +1. 项目初始化与环境搭建 1.1 创建 SpringBoot 后端基础项目 搭建后端工程骨架,引入核心依赖 SpringBoot、MySQL 驱动、MyBatis、JWT、lombok 已完成 Spring Boot 3.4.12,包名 top.awinx.stu01,Java 25;依赖:MyBatis-Plus 3.5.15、JJWT 0.13.0;启动方式:./mvnw spring-boot:run +1. 项目初始化与环境搭建 1.3 设计 MySQL 数据库表结构 设计核心实体表 用户表、学生表、教师表、课程表、学分表 已完成 数据库 stu01_db(MariaDB),用户 stu01/123456;核心表含 sys_user(预置 admin/stu001/tea001,密码 123456);字段约束:学号 / 教师编号 8 位纯数字,专业限 3 类,课程名 2-20 字符,分数 0-100 +1. 项目初始化与环境搭建 1.4 后端基础配置 配置数据库、跨域、JWT 参数 application.yml、跨域、JWT 密钥 已完成 数据库连接正常;跨域允许 5173 端口(独立 CorsConfig);JWT:32 位密钥、1 小时过期;context-path=/api,端口 8080;MyBatis-Plus 开启下划线转驼峰、SQL 日志 +2. 用户登录模块开发 2.1 后端用户登录核心逻辑 实现账号密码校验 合法账号、缺少账号 / 密码、错误账号密码 已完成 接口:POST /api/login;校验逻辑:空值 + 存在性 + 密码匹配;返回格式:Result(code/message/data) +2. 用户登录模块开发 2.2 JWT 令牌生成与校验 生成 token、校验 token、令牌刷新 合法 token、token 拦截、令牌刷新 已完成 接口:① POST /api/login(返回 token+user);② GET /api/login/refresh(刷新 token);JWT 拦截器放行登录 / 刷新接口,拦截其他 /api/**;token 载荷含 user_type(1 - 学生 / 2 - 教师 / 3 - 管理员) +2. 用户登录模块开发 2.4 登录异常处理 统一异常返回格式,明确异常信息 缺少账号 / 密码、错误账号密码、token 无效 / 过期 已完成 全局异常处理器捕获业务 / 参数 / 系统异常;返回统一 Result 格式:业务异常 code=400、系统异常 code=500;支持空参数、账号密码错误、token 异常等场景,提示信息简洁 +2. 用户登录模块开发 2.5 多用户登录性能基础支持 支持高并发请求 多用户登陆性能测试 已完成 依赖 Spring Boot 3.x 默认线程池 +3. 学生信息模块开发 3.1 后端学生基础 CRUD 实现学生添加 / 查询接口(支持模糊查询) 学生添加、学生查询(学号 / 姓名 / 专业筛选) 已完成 1. 接口:① POST /api/student/add(添加,校验学号唯一);② GET/POST/api/student/query(查询,学号精确、姓名 / 专业模糊);2. 依赖 JWT 拦截器,需携带合法 token;3. 返回格式统一为 Result,兼容全局异常处理;4. 支持组合条件筛选 +3. 学生信息模块开发 3.2 学生添加参数校验 校验学号、专业合法性 缺少学号、不合法学号、非法专业 已完成 学号规则:8 位纯数字、非空、唯一;专业限计算机 / 数学 / 英语;用 3.x 参数校验注解 +3. 学生信息模块开发 3.3 学生查询参数校验 校验 ID 合法性、防 SQL 注入 合法 ID、非法 ID、SQL 注入拦截 未完成 防注入:MyBatis-Plus 参数绑定;ID 校验:非负整数 + 存在性 +3. 学生信息模块开发 3.5 学生并发测试基础支持 支持添加 / 查询接口并发请求 学生查询 / 添加并发测试 未完成 依赖 3.x 默认配置 +4. 教师信息模块开发 4.1 后端教师基础 CRUD 实现教师添加 / 更新 / 删除接口 教师添加、更新、删除 未完成 接口路径:POST /api/teacher/add、PUT /api/teacher/update、DELETE /api/teacher/delete +4. 教师信息模块开发 4.2 教师删除关联拦截 教师关联课程 / 学分时拦截删除 删除关联拦截 未完成 校验逻辑:查询 course 表关联关系,存在则拦截;可选 @Transactional 事务处理 +4. 教师信息模块开发 4.3 教师查询防 SQL 注入 校验查询参数、防 SQL 注入 教师查询、SQL 注入拦截 未完成 接口:/api/teacher/query;防注入:MyBatis-Plus 参数绑定 +4. 教师信息模块开发 4.5 教师查询并发基础支持 确保教师查询接口支持并发请求 老师查询接口并发测试 未完成 依赖 3.x 默认配置 +5. 课程与学分模块开发 5.1 后端课程 / 学分基础 CRUD 编写课程 / 学分实体 / DAO/Service/Controller 课程添加、学分添加 未完成 接口路径:POST /api/course/add、POST /api/score/add +5. 课程与学分模块开发 5.2 课程参数校验 校验课程名称合法性、实现课程查询 课程查询、不合法名称 未完成 课程名规则:2-20 字符(中文 / 数字 / 字母);接口路径:/api/course/query +5. 课程与学分模块开发 5.3 学分参数校验 校验分数合法性、实现学分更新 学分更新、错误分数 未完成 分数规则:0-100,超范围返回校验失败;接口路径:PUT /api/score/update +5. 课程与学分模块开发 5.5 课程查询并发基础支持 确保课程查询接口支持并发请求 课程查询并发测试 未完成 依赖 3.x 默认配置 +6. 权限控制模块开发 6.1 JWT 权限标识 令牌中添加用户类型 访问权限 - 未登录、权限标识 已完成 用户类型:1 - 学生、2 - 教师、3 - 管理员;JWT 载荷已包含 user_type +6. 权限控制模块开发 6.2 后端接口权限拦截 拦截未登录请求、越权访问请求 越权访问 - 学生 - 老师、学生 - 管理员、老师 - 管理员 未完成 基于 JWT 的 user_type 判断权限;拦截器扩展:学生禁访 /api/teacher/、/api/admin/,教师禁访 /api/admin/** +7. 测试适配与代码精简 7.1 测试用例全覆盖核对 逐一核对测试用例,确保功能点无遗漏 用例全覆盖、功能校验 未完成 核对类别:登录 / 学生 / 教师 / 课程学分 / 权限 / 并发 / 注入;适配 3.x、MariaDB +7. 测试适配与代码精简 7.2 代码精简 移除冗余逻辑,仅保留测试必需功能 代码简洁、无冗余 未完成 删除冗余代码,简化逻辑;移除无用配置依赖;统一异常处理逻辑 +7. 测试适配与代码精简 7.3 基础联调 确保后端接口通信正常,核心功能可运行 联调、接口通联 未完成 测试核心接口(登录、增删改查、权限、刷新);前后端跨域正常;数据库读写正常 + +三、执行反馈模块(每次会话必填) +问题解决:核心是添加了一个springboot的关于validation的starter依赖。 +确认3.2完成,开始3.3 \ No newline at end of file diff --git a/stu01/src/main/java/top/awinx/stu01/Stu01Application.java b/stu01/src/main/java/top/awinx/stu01/Stu01Application.java new file mode 100644 index 0000000..455b16f --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/Stu01Application.java @@ -0,0 +1,16 @@ +package top.awinx.stu01; + + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("top.awinx.stu01.mapper") +public class Stu01Application { + + public static void main(String[] args) { + SpringApplication.run(Stu01Application.class, args); + } + +} diff --git a/stu01/src/main/java/top/awinx/stu01/common/Result.java b/stu01/src/main/java/top/awinx/stu01/common/Result.java new file mode 100644 index 0000000..01e10b1 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/common/Result.java @@ -0,0 +1,49 @@ +package top.awinx.stu01.common; + +import lombok.Data; + +/** + * 全局统一返回结果 + * code:200成功,400失败 + * message:提示信息 + * data:返回数据 + */ +@Data +public class Result { + private Integer code; + private String message; + private T data; + + // 成功返回(无数据) + public static Result success(String message) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage(message); + return result; + } + + // 成功返回(带数据) + public static Result success(String message, T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage(message); + result.setData(data); + return result; + } + + // 失败返回 + public static Result error(String message) { + Result result = new Result<>(); + result.setCode(400); + result.setMessage(message); + return result; + } + + // 失败返回 + public static Result error(Integer code, String message) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + return result; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/config/CorsConfig.java b/stu01/src/main/java/top/awinx/stu01/config/CorsConfig.java new file mode 100644 index 0000000..0bc19c0 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/config/CorsConfig.java @@ -0,0 +1,34 @@ +package top.awinx.stu01.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * Spring Boot 3.x跨域配置类(适配前端5173端口) + */ +@Configuration +public class CorsConfig { + + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + // 允许前端5173端口访问 + config.addAllowedOrigin("http://localhost:5174"); + // 允许携带Cookie + config.setAllowCredentials(true); + // 允许所有请求方法 + config.addAllowedMethod("*"); + // 允许所有请求头 + config.addAllowedHeader("*"); + // 暴露响应头(方便前端获取JWT令牌) + config.addExposedHeader("Authorization"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 所有接口都允许跨域 + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/config/JwtConfig.java b/stu01/src/main/java/top/awinx/stu01/config/JwtConfig.java new file mode 100644 index 0000000..fdeb6cd --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/config/JwtConfig.java @@ -0,0 +1,54 @@ +package top.awinx.stu01.config; + +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; + +/** + * JWT配置类(适配jjwt 0.13.0) + */ +@Configuration +public class JwtConfig { + + // 从yml配置中读取密钥 + @Value("${jwt.secret}") + private String secret; + + // 从yml配置中读取过期时间 + @Value("${jwt.expire}") + private long expire; + + // 从yml配置中读取请求头key + @Value("${jwt.header}") + private String header; + + // 从yml配置中读取令牌前缀 + @Value("${jwt.prefix}") + private String prefix; + + /** + * 创建JWT签名密钥(jjwt 0.13.0要求密钥长度≥256位) + */ + @Bean + public SecretKey jwtSecretKey() { + // 将字符串密钥转换为SecretKey对象 + return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + // 提供getter方法,方便其他类获取配置 + public long getExpire() { + return expire; + } + + public String getHeader() { + return header; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/config/WebMvcConfig.java b/stu01/src/main/java/top/awinx/stu01/config/WebMvcConfig.java new file mode 100644 index 0000000..16e4732 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/config/WebMvcConfig.java @@ -0,0 +1,32 @@ +package top.awinx.stu01.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import top.awinx.stu01.interceptor.JwtInterceptor; + +/** + * SpringMVC配置:注册JWT拦截器+跨域配置(适配Spring Boot 3.x) + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + /** + * 注册JWT拦截器Bean + */ + @Bean + public JwtInterceptor jwtInterceptor() { + return new JwtInterceptor(); + } + + /** + * 配置拦截规则:放行登录接口,拦截其他/api/**接口 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor()) + .addPathPatterns("/**") // 拦截所有/api开头的接口 + .excludePathPatterns("/login"); // 放行登录接口 + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/controller/LoginController.java b/stu01/src/main/java/top/awinx/stu01/controller/LoginController.java new file mode 100644 index 0000000..4777196 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/controller/LoginController.java @@ -0,0 +1,89 @@ +package top.awinx.stu01.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import top.awinx.stu01.common.Result; +import top.awinx.stu01.config.JwtConfig; +import top.awinx.stu01.dto.LoginRequest; +import top.awinx.stu01.entity.SysUser; +import top.awinx.stu01.service.SysUserService; +import top.awinx.stu01.utils.JwtUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import top.awinx.stu01.exception.BusinessException; + +/** + * 登录接口Controller + */ +@RestController +@RequestMapping("/login") +public class LoginController { + + @Resource + private SysUserService sysUserService; + + @Resource + private JwtUtil jwtUtil; + + @Resource + private JwtConfig jwtConfig; + + /** + * 登录接口(POST请求,接收JSON参数) + * @param loginRequest JSON格式的登录参数 + * @return 统一返回结果 + */ + @PostMapping("") + public Result login(@Valid @RequestBody LoginRequest loginRequest) { + // 提取账号密码 + String username = loginRequest.getUsername(); + String password = loginRequest.getPassword(); + + // 调用Service校验账号密码 + SysUser sysUser = sysUserService.loginCheck(username, password); + + // 校验失败 + if (sysUser == null) { + // throw new BusinessException("账号或密码错误"); + return Result.error("账号或密码错误"); + } + + // 生成JWT令牌 + String token = jwtUtil.generateToken(sysUser); + + // 组装返回数据(token+基础用户信息) + Map resultMap = new HashMap<>(); + resultMap.put("token", token); + resultMap.put("user", sysUser); + + // 校验成功 + return Result.success("登录成功", resultMap); + } + + /** + * 登陆令牌刷新接口(GET请求,无参数) + * @return 统一返回结果 + */ + @GetMapping("/refresh") + public Result refresh(HttpServletRequest request) { + String oldToken = request.getHeader(jwtConfig.getHeader()); + if (oldToken != null && oldToken.startsWith(jwtConfig.getPrefix())){ + oldToken = oldToken.substring(jwtConfig.getPrefix().length()); + oldToken = oldToken.trim(); + } + if (oldToken == null || !jwtUtil.validateToken(oldToken)) { + throw new BusinessException("请先登录或token已过期"); + } + SysUser sysUser = jwtUtil.getUserFromToken(oldToken); + String newToken = jwtUtil.generateToken(sysUser); + return Result.success("刷新成功", newToken); + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/controller/StudentController.java b/stu01/src/main/java/top/awinx/stu01/controller/StudentController.java new file mode 100644 index 0000000..58f377a --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/controller/StudentController.java @@ -0,0 +1,56 @@ +package top.awinx.stu01.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.awinx.stu01.common.Result; +import top.awinx.stu01.dto.StudentAddDTO; +import top.awinx.stu01.dto.StudentQueryDTO; +import top.awinx.stu01.entity.Student; +import top.awinx.stu01.service.StudentService; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 学生CRUD控制器 + */ +@RestController +@RequestMapping("/student") +public class StudentController { + + @Resource + private StudentService studentService; + + /** + * 添加学生接口 + * 请求方式:POST + * 接口路径:/api/student/add + */ + @PostMapping("/add") + public Result addStudent(@Valid @RequestBody StudentAddDTO addDTO) { + Student student = studentService.addStudent(addDTO); + return Result.success("添加学生成功", student); + } + + /** + * 查询学生接口(兼容GET/POST,支持模糊查询) + * 请求方式:GET/POST + * 接口路径:/api/student/query + */ + @GetMapping("/query") + public Result> queryStudentByGet(StudentQueryDTO queryDTO) { + List studentList = studentService.queryStudent(queryDTO); + return Result.success("查询成功", studentList); + } + + @PostMapping("/query") + public Result> queryStudentByPost(@RequestBody StudentQueryDTO queryDTO) { + List studentList = studentService.queryStudent(queryDTO); + return Result.success("查询成功", studentList); + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/controller/TestController.java b/stu01/src/main/java/top/awinx/stu01/controller/TestController.java new file mode 100644 index 0000000..90e61b8 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/controller/TestController.java @@ -0,0 +1,16 @@ +package top.awinx.stu01.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 测试接口,验证数据库和跨域配置 + */ +@RestController +public class TestController { + + @GetMapping("/test") + public String test() { + return "后端配置生效,前端可跨域访问!"; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/dto/LoginRequest.java b/stu01/src/main/java/top/awinx/stu01/dto/LoginRequest.java new file mode 100644 index 0000000..cf448dd --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/dto/LoginRequest.java @@ -0,0 +1,15 @@ +package top.awinx.stu01.dto; + +import lombok.Data; +import jakarta.validation.constraints.NotBlank; + +/** + * 登录请求参数DTO(接收JSON格式参数) + */ +@Data +public class LoginRequest { + @NotBlank(message = "用户名不能为空") + private String username; // 登录账号 + @NotBlank(message = "密码不能为空") + private String password; // 登录密码 +} diff --git a/stu01/src/main/java/top/awinx/stu01/dto/StudentAddDTO.java b/stu01/src/main/java/top/awinx/stu01/dto/StudentAddDTO.java new file mode 100644 index 0000000..912a8e5 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/dto/StudentAddDTO.java @@ -0,0 +1,39 @@ +package top.awinx.stu01.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +/** + * 添加学生请求参数DTO + */ +@Data +public class StudentAddDTO { + + /** + * 学号(8位纯数字) + */ + @NotBlank(message = "学号不能为空") + @Pattern(regexp = "^\\d{8}$", message = "学号必须为8位纯数字,不能包含特殊字符") + private String stuNo; + + /** + * 学生姓名 + */ + @NotBlank(message = "学生姓名不能为空") + private String name; + + /** + * 专业(计算机/数学/英语) + */ + @NotBlank(message = "专业不能为空") + @Pattern(regexp = "^(计算机|数学|英语)$", message = "专业仅支持:计算机、数学、英语") + private String major; + + /** + * 关联用户ID + */ + @NotNull(message = "关联用户ID不能为空") + private Long userId; +} diff --git a/stu01/src/main/java/top/awinx/stu01/dto/StudentQueryDTO.java b/stu01/src/main/java/top/awinx/stu01/dto/StudentQueryDTO.java new file mode 100644 index 0000000..ae8297d --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/dto/StudentQueryDTO.java @@ -0,0 +1,24 @@ +package top.awinx.stu01.dto; + +import jakarta.validation.constraints.Min; +import lombok.Data; + +@Data +public class StudentQueryDTO { + + /** + * 学号(精确匹配) + */ + private String stuNo; + + /** + * 姓名(模糊匹配) + */ + private String name; + + /** + * 专业(模糊匹配) + */ + private String major; + +} diff --git a/stu01/src/main/java/top/awinx/stu01/entity/Student.java b/stu01/src/main/java/top/awinx/stu01/entity/Student.java new file mode 100644 index 0000000..682fc80 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/entity/Student.java @@ -0,0 +1,53 @@ +package top.awinx.stu01.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生实体类:对应student表 + */ +@Data +@TableName("student") // 关联数据库表名 +public class Student { + + /** + * 主键ID(自增) + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 学号(8位纯数字,唯一) + */ + @TableField("stu_no") + private String stuNo; + + /** + * 学生姓名 + */ + @TableField("name") + private String name; + + /** + * 专业(计算机/数学/英语) + */ + @TableField("major") + private String major; + + /** + * 关联用户ID(外键) + */ + @TableField("user_id") + private Long userId; + + /** + * 创建时间(默认当前时间) + */ + @TableField("create_time") + private LocalDateTime createTime; +} diff --git a/stu01/src/main/java/top/awinx/stu01/entity/SysUser.java b/stu01/src/main/java/top/awinx/stu01/entity/SysUser.java new file mode 100644 index 0000000..de783b4 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/entity/SysUser.java @@ -0,0 +1,20 @@ +package top.awinx.stu01.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统用户实体(对应sys_user表) + */ +@Data +@TableName("sys_user") // 关联数据库表名 +public class SysUser { + @TableId(type = IdType.AUTO) // 主键自增 + private Integer id; // 用户ID + private String username; // 登录账号 + private String password; // 登录密码(测试用明文,无需加密) + private Integer userType; // 用户类型:1-学生 2-教师 3-管理员 + private String realName; // 真实姓名 +} diff --git a/stu01/src/main/java/top/awinx/stu01/exception/BusinessException.java b/stu01/src/main/java/top/awinx/stu01/exception/BusinessException.java new file mode 100644 index 0000000..bbb3a73 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/exception/BusinessException.java @@ -0,0 +1,12 @@ +package top.awinx.stu01.exception; + +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + private final int code = 400; + private final String message; + public BusinessException(String message) { + this.message = message; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/exception/GlobalExceptionHandler.java b/stu01/src/main/java/top/awinx/stu01/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..710c53f --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/exception/GlobalExceptionHandler.java @@ -0,0 +1,80 @@ +package top.awinx.stu01.exception; + +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.fasterxml.jackson.core.JsonParseException; + +import top.awinx.stu01.common.Result; + +/** + * 全局异常处理器:统一捕获所有接口异常,返回标准Result格式 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 捕获业务异常(如账号密码错误、token无效等) + */ + @ExceptionHandler(BusinessException.class) + public Result handleBusinessException(BusinessException e) { + return Result.error(e.getCode(), e.getMessage()); + } + + /** + * 捕获参数校验异常(如@NotBlank、@Pattern触发的异常) + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleValidException(MethodArgumentNotValidException e) { + // 获取第一个校验失败的字段提示信息 + String message = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(); + return Result.error(400, message); + } + + + /** + * 捕获JSON解析异常(如JSON字段名称不匹配、JSON语法错误等) + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + // 判断是否是JSON解析错误 + Throwable rootCause = e.getRootCause(); + if (rootCause instanceof JsonParseException) { + return Result.error(400, "JSON格式错误,请检查:字段名需用双引号、括号匹配、无多余逗号/特殊字符"); + } else { + return Result.error(400, "请求体格式错误,请检查JSON语法"); + } + } + + /** + * 捕获数据库约束异常(如非空字段为空、字段长度超出限制等) + */ + @ExceptionHandler(DataIntegrityViolationException.class) + public Result handleDataIntegrityException(DataIntegrityViolationException e) { + String msg = e.getMessage(); + if (msg.contains("constraint")) { + if (msg.contains("primary")){ + return Result.error(400, "主键冲突,请检查"); + } else if (msg.contains("unique")) { + return Result.error(400, "唯一约束冲突,请检查"); + } else { + return Result.error(400, "数据库约束冲突,请检查"); + } + } else { + return Result.error(400, "数据库操作失败,请检查"); + } + } + + /** + * 捕获系统异常(兜底处理) + */ + @ExceptionHandler(Exception.class) + public Result handleSystemException(Exception e) { + // 系统异常返回通用提示(避免暴露敏感信息) + e.printStackTrace(); + return Result.error(500, "服务器内部错误,请稍后重试"); + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/interceptor/JwtInterceptor.java b/stu01/src/main/java/top/awinx/stu01/interceptor/JwtInterceptor.java new file mode 100644 index 0000000..d9a19e7 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/interceptor/JwtInterceptor.java @@ -0,0 +1,54 @@ +package top.awinx.stu01.interceptor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.HandlerInterceptor; +import top.awinx.stu01.common.Result; +import top.awinx.stu01.config.JwtConfig; +import top.awinx.stu01.utils.JwtUtil; + +import java.io.PrintWriter; + +/** + * JWT拦截器:校验请求头中的token合法性 + */ +public class JwtInterceptor implements HandlerInterceptor { + + @Resource + private JwtUtil jwtUtil; + @Resource + private JwtConfig jwtConfig; + + /** + * 预处理:请求到达Controller前校验token + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 1. 获取请求头中的token + String token = request.getHeader(jwtConfig.getHeader()); + String prefix = jwtConfig.getPrefix(); + if (token != null && token.startsWith(prefix)) { + token = token.substring(prefix.length()); + token = token.trim(); + } + + // 2. 校验token是否存在且合法 + if (token == null || !jwtUtil.validateToken(token)) { + // 3. 非法token/无token,返回400错误 + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + Result result = Result.error("请先登录或token已过期"); + PrintWriter writer = response.getWriter(); + writer.write(new ObjectMapper().writeValueAsString(result)); + writer.flush(); + writer.close(); + return false; // 拦截请求 + } + + // 4. token合法,放行请求 + return true; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/mapper/StudentMapper.java b/stu01/src/main/java/top/awinx/stu01/mapper/StudentMapper.java new file mode 100644 index 0000000..fea6b32 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/mapper/StudentMapper.java @@ -0,0 +1,12 @@ +package top.awinx.stu01.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import top.awinx.stu01.entity.Student; + +/** + * 学生Mapper:继承MyBatis-Plus BaseMapper,无需自定义SQL + */ +@Mapper +public interface StudentMapper extends BaseMapper { +} diff --git a/stu01/src/main/java/top/awinx/stu01/mapper/SysUserMapper.java b/stu01/src/main/java/top/awinx/stu01/mapper/SysUserMapper.java new file mode 100644 index 0000000..06b30a7 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/mapper/SysUserMapper.java @@ -0,0 +1,14 @@ +package top.awinx.stu01.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import top.awinx.stu01.entity.SysUser; +import org.apache.ibatis.annotations.Mapper; + +/** + * MyBatis-Plus Mapper(DAO层) + * BaseMapper已封装CRUD,无需手动写SQL + */ +@Mapper // 标识为MyBatis Mapper,Spring扫描 +public interface SysUserMapper extends BaseMapper { + // 无需新增方法,BaseMapper已包含:selectOne、selectList等 +} diff --git a/stu01/src/main/java/top/awinx/stu01/service/StudentService.java b/stu01/src/main/java/top/awinx/stu01/service/StudentService.java new file mode 100644 index 0000000..14ab44e --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/service/StudentService.java @@ -0,0 +1,28 @@ +package top.awinx.stu01.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import top.awinx.stu01.dto.StudentAddDTO; +import top.awinx.stu01.dto.StudentQueryDTO; +import top.awinx.stu01.entity.Student; + +import java.util.List; + +/** + * 学生Service接口 + */ +public interface StudentService extends IService { + + /** + * 添加学生 + * @param addDTO 添加参数 + * @return 添加后的学生信息 + */ + Student addStudent(StudentAddDTO addDTO); + + /** + * 模糊查询学生(支持学号精确、姓名/专业模糊) + * @param queryDTO 查询参数 + * @return 学生列表 + */ + List queryStudent(StudentQueryDTO queryDTO); +} diff --git a/stu01/src/main/java/top/awinx/stu01/service/SysUserService.java b/stu01/src/main/java/top/awinx/stu01/service/SysUserService.java new file mode 100644 index 0000000..e97283b --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/service/SysUserService.java @@ -0,0 +1,12 @@ +package top.awinx.stu01.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import top.awinx.stu01.entity.SysUser; + +/** + * 用户服务接口 + */ +public interface SysUserService extends IService { + // 登录校验方法:返回用户信息(null表示校验失败) + SysUser loginCheck(String username, String password); +} diff --git a/stu01/src/main/java/top/awinx/stu01/service/impl/StudentServiceImpl.java b/stu01/src/main/java/top/awinx/stu01/service/impl/StudentServiceImpl.java new file mode 100644 index 0000000..e192ba4 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/service/impl/StudentServiceImpl.java @@ -0,0 +1,73 @@ +package top.awinx.stu01.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import top.awinx.stu01.dto.StudentAddDTO; +import top.awinx.stu01.dto.StudentQueryDTO; +import top.awinx.stu01.entity.Student; +import top.awinx.stu01.exception.BusinessException; +import top.awinx.stu01.mapper.StudentMapper; +import top.awinx.stu01.service.StudentService; + +import java.util.List; + +/** + * 学生Service实现类 + */ +@Service +public class StudentServiceImpl extends ServiceImpl implements StudentService { + + /** + * 添加学生:校验学号唯一性,转换DTO为实体并保存 + */ + @Override + public Student addStudent(StudentAddDTO addDTO) { + // 1. 校验学号是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Student::getStuNo, addDTO.getStuNo()); + if (this.count(wrapper) > 0) { + throw new BusinessException("学号" + addDTO.getStuNo() + "已存在"); + } + + // 2. DTO转换为实体 + Student student = new Student(); + student.setStuNo(addDTO.getStuNo()); + student.setName(addDTO.getName()); + student.setMajor(addDTO.getMajor()); + student.setUserId(addDTO.getUserId()); + + // 3. 保存学生信息 + this.save(student); + return student; + } + + /** + * 模糊查询学生:学号精确匹配,姓名/专业模糊匹配 + */ + @Override + public List queryStudent(StudentQueryDTO queryDTO) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 学号:精确匹配 + if (queryDTO.getStuNo() != null && !queryDTO.getStuNo().isEmpty()) { + wrapper.eq(Student::getStuNo, queryDTO.getStuNo()); + } + + // 姓名:模糊匹配(包含) + if (queryDTO.getName() != null && !queryDTO.getName().isEmpty()) { + wrapper.like(Student::getName, queryDTO.getName()); + } + + // 专业:模糊匹配(包含) + if (queryDTO.getMajor() != null && !queryDTO.getMajor().isEmpty()) { + wrapper.like(Student::getMajor, queryDTO.getMajor()); + } + + // 按创建时间降序排列 + wrapper.orderByDesc(Student::getCreateTime); + + // 查询并返回结果 + return this.list(wrapper); + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/service/impl/SysUserServiceImpl.java b/stu01/src/main/java/top/awinx/stu01/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..e3964ac --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/service/impl/SysUserServiceImpl.java @@ -0,0 +1,47 @@ +package top.awinx.stu01.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import org.springframework.stereotype.Service; +import top.awinx.stu01.entity.SysUser; +import top.awinx.stu01.mapper.SysUserMapper; +import top.awinx.stu01.service.SysUserService; + +/** + * 用户服务实现类(核心业务逻辑) + */ +@Service // 标识为Spring服务Bean +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + /** + * 登录校验逻辑: + * 1. 校验账号非空 + * 2. 校验密码非空 + * 3. 查询数据库是否存在该账号 + * 4. 校验密码是否匹配 + */ + @Override + public SysUser loginCheck(String username, String password) { + // 1. 空值校验 + if (username == null || username.isEmpty()) { + return null; + } + if (password == null || password.isEmpty()) { + return null; + } + + // 2. 根据账号查询用户(LambdaQueryWrapper避免SQL注入) + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getUsername, username); // 等值查询username + SysUser sysUser = this.baseMapper.selectOne(queryWrapper); + + // 3. 校验用户存在且密码匹配(测试用明文对比) + if (sysUser == null || !password.equals(sysUser.getPassword())) { + return null; + } + + // 4. 校验通过,返回用户信息(后续生成JWT用) + return sysUser; + } +} diff --git a/stu01/src/main/java/top/awinx/stu01/utils/JwtUtil.java b/stu01/src/main/java/top/awinx/stu01/utils/JwtUtil.java new file mode 100644 index 0000000..78063e7 --- /dev/null +++ b/stu01/src/main/java/top/awinx/stu01/utils/JwtUtil.java @@ -0,0 +1,86 @@ +package top.awinx.stu01.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import top.awinx.stu01.config.JwtConfig; +import top.awinx.stu01.entity.SysUser; + +import javax.crypto.SecretKey; +import java.util.Date; + +/** + * JWT工具类(适配jjwt 0.13.0) + * 功能:生成token、解析token、校验token + */ +@Component +public class JwtUtil { + + @Resource + private JwtConfig jwtConfig; + + /** + * 生成JWT令牌 + * @param sysUser 登录成功的用户信息 + * @return token字符串 + */ + public String generateToken(SysUser sysUser) { + // 生成密钥(适配jjwt 0.13.0,需32位密钥) + SecretKey secretKey = jwtConfig.jwtSecretKey(); + + // 构建token,载荷包含user_id、user_type、username + return Jwts.builder() + .claim("user_id", sysUser.getId()) // 用户ID + .claim("user_type", sysUser.getUserType()) // 用户类型(1-学生/2-教师/3-管理员) + .claim("username", sysUser.getUsername()) // 用户名 + .issuedAt(new Date()) // 签发时间 + .expiration(new Date(System.currentTimeMillis() + jwtConfig.getExpire() * 1000)) // 过期时间 + .signWith(secretKey) // 签名 + .compact(); + } + + /** + * 解析token,获取载荷信息 + * @param token JWT令牌 + * @return 载荷Claims + */ + public Claims parseToken(String token) { + SecretKey secretKey = jwtConfig.jwtSecretKey(); + return Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + /** + * 获取token中的用户信息 + * @param token + * @return 用户信息 + */ + public SysUser getUserFromToken(String token) { + Claims claims = parseToken(token); + SysUser sysUser = new SysUser(); + sysUser.setId(claims.get("user_id", Integer.class)); + sysUser.setUserType(claims.get("user_type", Integer.class)); + sysUser.setUsername(claims.get("username", String.class)); + return sysUser; + } + + /** + * 校验token是否合法(未过期+签名正确) + * @param token JWT令牌 + * @return true-合法,false-非法 + */ + public boolean validateToken(String token) { + try { + Claims claims = parseToken(token); + // 校验是否过期 + return !claims.getExpiration().before(new Date()); + } catch (Exception e) { + // 签名错误、过期、格式错误等均返回false + return false; + } + } +} diff --git a/stu01/src/main/resources/application.yml b/stu01/src/main/resources/application.yml new file mode 100644 index 0000000..9be6930 --- /dev/null +++ b/stu01/src/main/resources/application.yml @@ -0,0 +1,46 @@ +# 服务器配置 +server: + port: 8080 # 后端端口,与前端代理配置一致 + servlet: + context-path: /api # 接口前缀,适配前端跨域代理 + +# 数据源配置(适配MariaDB,兼容MySQL驱动) +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver # Spring Boot 3.x推荐驱动类 + url: jdbc:mysql://localhost:3306/stu01_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: stu01 # 自定义数据库用户 + password: 123456 # 数据库用户密码 + # 跨域配置(配合CorsConfig类,双重保障) + web: + cors: + allowed-origins: http://localhost:5174 # 前端实际端口 + allowed-methods: "*" + allowed-headers: "*" + allow-credentials: true + +# MyBatis-Plus配置 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 下划线转驼峰 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(测试用) + global-config: + db-config: + id-type: AUTO # 主键自增 + type-aliases-package: top.awinx.stu01.entity # 实体类包扫描 +# mapper-locations: classpath:mapper/*.xml # Mapper XML文件扫描 +# mybatis: +# mapperLocations: classpath:mapper/*.xml + +logging: + level: + top.awinx.stu01.mapper: debug + com.baomidou.mybatisplus: debug + org.mybatis: debug + +# JWT自定义配置(适配jjwt 0.13.0) +jwt: + secret: abcdefghijklmnopqrstuvwxyz123456 # 32位密钥(测试用,生产需替换) + expire: 3600000 # 过期时间:1小时(毫秒) + header: Authorization # 请求头中JWT令牌的key + prefix: Bearer # 令牌前缀 diff --git a/stu01/src/main/resources/mapper/SysUserMapper.xml b/stu01/src/main/resources/mapper/SysUserMapper.xml new file mode 100644 index 0000000..0c5b04c --- /dev/null +++ b/stu01/src/main/resources/mapper/SysUserMapper.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/stu01/src/test/java/top/awinx/stu01/Stu01ApplicationTests.java b/stu01/src/test/java/top/awinx/stu01/Stu01ApplicationTests.java new file mode 100644 index 0000000..30beb0a --- /dev/null +++ b/stu01/src/test/java/top/awinx/stu01/Stu01ApplicationTests.java @@ -0,0 +1,13 @@ +package top.awinx.stu01; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Stu01ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/stu01/target/classes/application.yml b/stu01/target/classes/application.yml new file mode 100644 index 0000000..9be6930 --- /dev/null +++ b/stu01/target/classes/application.yml @@ -0,0 +1,46 @@ +# 服务器配置 +server: + port: 8080 # 后端端口,与前端代理配置一致 + servlet: + context-path: /api # 接口前缀,适配前端跨域代理 + +# 数据源配置(适配MariaDB,兼容MySQL驱动) +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver # Spring Boot 3.x推荐驱动类 + url: jdbc:mysql://localhost:3306/stu01_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: stu01 # 自定义数据库用户 + password: 123456 # 数据库用户密码 + # 跨域配置(配合CorsConfig类,双重保障) + web: + cors: + allowed-origins: http://localhost:5174 # 前端实际端口 + allowed-methods: "*" + allowed-headers: "*" + allow-credentials: true + +# MyBatis-Plus配置 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 下划线转驼峰 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(测试用) + global-config: + db-config: + id-type: AUTO # 主键自增 + type-aliases-package: top.awinx.stu01.entity # 实体类包扫描 +# mapper-locations: classpath:mapper/*.xml # Mapper XML文件扫描 +# mybatis: +# mapperLocations: classpath:mapper/*.xml + +logging: + level: + top.awinx.stu01.mapper: debug + com.baomidou.mybatisplus: debug + org.mybatis: debug + +# JWT自定义配置(适配jjwt 0.13.0) +jwt: + secret: abcdefghijklmnopqrstuvwxyz123456 # 32位密钥(测试用,生产需替换) + expire: 3600000 # 过期时间:1小时(毫秒) + header: Authorization # 请求头中JWT令牌的key + prefix: Bearer # 令牌前缀 diff --git a/stu01/target/classes/mapper/SysUserMapper.xml b/stu01/target/classes/mapper/SysUserMapper.xml new file mode 100644 index 0000000..0c5b04c --- /dev/null +++ b/stu01/target/classes/mapper/SysUserMapper.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..d42078a --- /dev/null +++ b/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,11 @@ +top/awinx/stu01/config/JwtConfig.class +top/awinx/stu01/service/impl/SysUserServiceImpl.class +top/awinx/stu01/config/CorsConfig.class +top/awinx/stu01/dto/LoginRequest.class +top/awinx/stu01/entity/SysUser.class +top/awinx/stu01/Stu01Application.class +top/awinx/stu01/controller/LoginController.class +top/awinx/stu01/service/SysUserService.class +top/awinx/stu01/mapper/SysUserMapper.class +top/awinx/stu01/controller/TestController.class +top/awinx/stu01/common/Result.class diff --git a/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..b350bb0 --- /dev/null +++ b/stu01/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,23 @@ +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/Stu01Application.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/common/Result.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/config/CorsConfig.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/config/JwtConfig.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/config/WebMvcConfig.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/controller/LoginController.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/controller/StudentController.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/controller/TestController.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/dto/LoginRequest.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/dto/StudentAddDTO.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/dto/StudentQueryDTO.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/entity/Student.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/entity/SysUser.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/exception/BusinessException.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/exception/GlobalExceptionHandler.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/interceptor/JwtInterceptor.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/mapper/StudentMapper.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/mapper/SysUserMapper.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/StudentService.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/SysUserService.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/impl/StudentServiceImpl.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/impl/SysUserServiceImpl.java +/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/utils/JwtUtil.java diff --git a/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..02749d9 --- /dev/null +++ b/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +top/awinx/stu01/Stu01ApplicationTests.class diff --git a/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..f516513 --- /dev/null +++ b/stu01/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +/home/awinx/code/java/lerning/java_web/stu01/src/test/java/top/awinx/stu01/Stu01ApplicationTests.java