stu01:权限控制

This commit is contained in:
awin-x 2025-12-14 22:04:53 +08:00
parent 9f388da5c7
commit 22c47b9eb5
14 changed files with 381 additions and 37 deletions

View File

@ -1,32 +1 @@
# 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 `<license>` and `<developers>` 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.

View File

@ -1,3 +1,5 @@
提示词模板:
一、核心协作规则重申
@ -23,10 +25,10 @@
3. 学生信息模块开发 3.3 学生查询参数校验 校验 ID 合法性、防 SQL 注入 合法 ID、非法 ID、SQL 注入拦截 已完成 1. 跳过分组校验设计留作软件测试工程实践报告素材后续忽略2. 仅保留基础校验ID 非负整数 + 存在性、MyBatis-Plus 参数预编译防 SQL 注入3. 简化代码,无分组相关逻辑
3. 学生信息模块开发 3.5 学生并发测试基础支持 支持添加 / 查询接口并发请求 学生查询 / 添加并发测试 未完成 依赖 3.x 默认配置
4. 教师信息模块开发 4.1 后端教师基础 CRUD 实现教师添加 / 更新 / 删除接口 教师添加、更新、删除 已完成 1. 适配新数据库结构teacher 表移除 major/title仅保留 tea_no/name/user_id外键关联 sys_user.id2. 校验逻辑:教师编号 8 位纯数字 + 唯一、姓名非空、关联 userId 存在且为教师类型user_type=23. 仅支持更新教师姓名4. 接口路径POST /api/teacher/add、PUT /api/teacher/update、DELETE /api/teacher/delete/{id}5. 依赖 JWT 拦截器,需携带合法 token
4. 教师信息模块开发 4.2 教师删除关联拦截 教师关联课程 / 学分时拦截删除 删除关联拦截 未完成 校验逻辑:查询 course 表关联关系,存在则拦截;可选 @Transactional 事务处理
4. 教师信息模块开发 4.2 教师删除关联拦截 教师关联课程 / 学分时拦截删除 删除关联拦截 已完成 1. 核心逻辑:删除前查询 course 表中关联的 tea_id存在记录则抛业务异常code=400提示 “教师 IDxx 关联了 xx 门课程禁止删除”2. 无关联时执行物理删除3. 无需修改控制器,仅扩展 Service 层逻辑4. 兼容全局异常处理,返回统一 Result 格式
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.1 后端课程 / 学分基础 CRUD 编写课程 / 学分实体 / DAO/Service/Controller 课程添加、学分添加 已完成 1. 课程添加校验课程名称2-20 字符,仅中文 / 数字 / 字母)、关联教师 ID 存在性接口路径POST /api/course/add2. 学分添加:校验关联学生 ID / 课程 ID 存在性、分数 0-100接口路径POST /api/score/add3. 依赖 JWT 拦截器,返回统一 Result 格式4. 代码简洁,仅满足测试基础需求
5. 课程与学分模块开发 5.2 课程参数校验 校验课程名称合法性、实现课程查询 课程查询、不合法名称 未完成 课程名规则2-20 字符(中文 / 数字 / 字母);接口路径:/api/course/query
5. 课程与学分模块开发 5.3 学分参数校验 校验分数合法性、实现学分更新 学分更新、错误分数 未完成 分数规则0-100超范围返回校验失败接口路径PUT /api/score/update
5. 课程与学分模块开发 5.5 课程查询并发基础支持 确保课程查询接口支持并发请求 课程查询并发测试 未完成 依赖 3.x 默认配置
@ -34,3 +36,7 @@
6. 权限控制模块开发 6.2 后端接口权限拦截 拦截未登录请求、越权访问请求 越权访问 - 学生 - 老师、学生 - 管理员、老师 - 管理员 未完成 基于 JWT 的 user_type 判断权限;拦截器扩展:学生禁访 /api/teacher/、/api/admin/,教师禁访 /api/admin/**
三、执行反馈模块(每次会话必填)
确认完成5.1
测试通过
部分实体在之前的步骤中已经编写内容一致无需修改。部分Mapper已经存在无需修改。
现在开始任务5.2

View File

@ -0,0 +1,32 @@
package top.awinx.stu01.controller;
import jakarta.annotation.Resource;
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.CourseAddDTO;
import top.awinx.stu01.entity.Course;
import top.awinx.stu01.service.CourseService;
/**
* 课程CRUD控制器仅实现添加接口
*/
@RestController
@RequestMapping("/course")
public class CourseController {
@Resource
private CourseService courseService;
/**
* 添加课程
* POST /api/course/add
*/
@PostMapping("/add")
public Result<Course> addCourse(@RequestBody CourseAddDTO addDTO) {
Course course = courseService.addCourse(addDTO);
return Result.success("添加课程成功", course);
}
}

View File

@ -0,0 +1,32 @@
package top.awinx.stu01.controller;
import jakarta.annotation.Resource;
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.ScoreAddDTO;
import top.awinx.stu01.entity.Score;
import top.awinx.stu01.service.ScoreService;
/**
* 学分CRUD控制器仅实现添加接口
*/
@RestController
@RequestMapping("/score")
public class ScoreController {
@Resource
private ScoreService scoreService;
/**
* 添加学分
* POST /api/score/add
*/
@PostMapping("/add")
public Result<Score> addScore(@RequestBody ScoreAddDTO addDTO) {
Score score = scoreService.addScore(addDTO);
return Result.success("添加学分成功", score);
}
}

View File

@ -0,0 +1,19 @@
package top.awinx.stu01.dto;
import lombok.Data;
/**
* 课程添加请求DTO
*/
@Data
public class CourseAddDTO {
/**
* 课程名称2-20字符仅中文/数字/字母
*/
private String courseName;
/**
* 关联教师ID非空且存在
*/
private Long teaId;
}

View File

@ -0,0 +1,24 @@
package top.awinx.stu01.dto;
import lombok.Data;
/**
* 学分添加请求DTO
*/
@Data
public class ScoreAddDTO {
/**
* 关联学生ID非空且存在
*/
private Long stuId;
/**
* 关联课程ID非空且存在
*/
private Long courseId;
/**
* 分数0-100
*/
private Integer score;
}

View File

@ -0,0 +1,41 @@
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;
import java.time.LocalDateTime;
/**
* 学分实体类匹配stu01_db.score表
*/
@Data
@TableName("score")
public class Score {
/**
* 学分ID主键自增
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 关联学生ID外键关联student.id
*/
private Long stuId;
/**
* 关联课程ID外键关联course.id
*/
private Long courseId;
/**
* 分数0-100
*/
private Integer score;
/**
* 创建时间数据库默认CURRENT_TIMESTAMP
*/
private LocalDateTime createTime;
}

View File

@ -8,12 +8,13 @@ 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.entity.SysUser;
import top.awinx.stu01.utils.JwtUtil;
import java.io.PrintWriter;
/**
* JWT拦截器校验请求头中的token合法性
* JWT拦截器校验请求头中的token合法性并基于user_type控制访问权限
*/
public class JwtInterceptor implements HandlerInterceptor {
@ -23,7 +24,7 @@ public class JwtInterceptor implements HandlerInterceptor {
private JwtConfig jwtConfig;
/**
* 预处理请求到达Controller前校验token
* 预处理请求到达Controller前校验token并进行权限控制
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
@ -48,7 +49,55 @@ public class JwtInterceptor implements HandlerInterceptor {
return false; // 拦截请求
}
// 4. token合法放行请求
return true;
// 4. 解析token获取用户信息进行权限控制
SysUser sysUser = jwtUtil.getUserFromToken(token);
Integer userType = sysUser.getUserType(); // 1-学生 2-教师 3-管理员
String requestPath = request.getRequestURI(); // 获取当前请求路径
// 5. 权限匹配逻辑核心
// 5.1 管理员3放行所有接口
if (userType == 3) {
return true;
}
// 5.2 教师2禁止访问/admin/**接口
if (userType == 2) {
if (requestPath.startsWith("/api/admin/")) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
Result<Object> result = Result.error("权限不足:教师禁止访问管理员接口");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(result));
writer.flush();
writer.close();
return false;
}
return true;
}
// 5.3 学生1禁止访问/teacher/**/admin/**接口
if (userType == 1) {
if (requestPath.startsWith("/api/teacher/") || requestPath.startsWith("/api/admin/")) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
Result<Object> result = Result.error("权限不足:学生禁止访问教师/管理员接口");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(result));
writer.flush();
writer.close();
return false;
}
return true;
}
// 6. 未知用户类型兜底
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
Result<Object> result = Result.error("未知的用户类型,权限校验失败");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(result));
writer.flush();
writer.close();
return false;
}
}

View File

@ -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.Score;
/**
* 学分MapperMyBatis-Plus基础CRUD
*/
@Mapper
public interface ScoreMapper extends BaseMapper<Score> {
}

View File

@ -0,0 +1,15 @@
package top.awinx.stu01.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.awinx.stu01.dto.CourseAddDTO;
import top.awinx.stu01.entity.Course;
/**
* 课程Service接口
*/
public interface CourseService extends IService<Course> {
/**
* 添加课程
*/
Course addCourse(CourseAddDTO addDTO);
}

View File

@ -0,0 +1,15 @@
package top.awinx.stu01.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.awinx.stu01.dto.ScoreAddDTO;
import top.awinx.stu01.entity.Score;
/**
* 学分Service接口
*/
public interface ScoreService extends IService<Score> {
/**
* 添加学分
*/
Score addScore(ScoreAddDTO addDTO);
}

View File

@ -0,0 +1,54 @@
package top.awinx.stu01.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import top.awinx.stu01.dto.CourseAddDTO;
import top.awinx.stu01.entity.Course;
import top.awinx.stu01.entity.Teacher;
import top.awinx.stu01.exception.BusinessException;
import top.awinx.stu01.mapper.CourseMapper;
import top.awinx.stu01.mapper.TeacherMapper;
import top.awinx.stu01.service.CourseService;
import java.time.LocalDateTime;
import java.util.regex.Pattern;
/**
* 课程Service实现类添加核心逻辑
*/
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
@Resource
private TeacherMapper teacherMapper;
// 课程名称校验正则2-20字符仅中文数字字母
private static final Pattern COURSE_NAME_PATTERN = Pattern.compile("^[\\u4e00-\\u9fa5a-zA-Z0-9]{2,20}$");
@Override
public Course addCourse(CourseAddDTO addDTO) {
// 1. 课程名称校验
if (addDTO.getCourseName() == null || !COURSE_NAME_PATTERN.matcher(addDTO.getCourseName()).matches()) {
throw new BusinessException("课程名称必须为2-20字符仅包含中文、数字、字母");
}
// 2. 关联教师ID存在性校验
if (addDTO.getTeaId() == null) {
throw new BusinessException("关联教师ID不能为空");
}
Teacher teacher = teacherMapper.selectById(addDTO.getTeaId());
if (teacher == null) {
throw new BusinessException("关联的教师ID" + addDTO.getTeaId() + "不存在");
}
// 3. DTO转实体
Course course = new Course();
course.setCourseName(addDTO.getCourseName());
course.setTeaId(addDTO.getTeaId());
course.setCreateTime(LocalDateTime.now());
this.save(course);
return course;
}
}

View File

@ -0,0 +1,64 @@
package top.awinx.stu01.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import top.awinx.stu01.dto.ScoreAddDTO;
import top.awinx.stu01.entity.Course;
import top.awinx.stu01.entity.Score;
import top.awinx.stu01.entity.Student;
import top.awinx.stu01.exception.BusinessException;
import top.awinx.stu01.mapper.CourseMapper;
import top.awinx.stu01.mapper.ScoreMapper;
import top.awinx.stu01.mapper.StudentMapper;
import top.awinx.stu01.service.ScoreService;
import java.time.LocalDateTime;
/**
* 学分Service实现类添加核心逻辑
*/
@Service
public class ScoreServiceImpl extends ServiceImpl<ScoreMapper, Score> implements ScoreService {
@Resource
private StudentMapper studentMapper;
@Resource
private CourseMapper courseMapper;
@Override
public Score addScore(ScoreAddDTO addDTO) {
// 1. 关联学生ID存在性校验
if (addDTO.getStuId() == null) {
throw new BusinessException("关联学生ID不能为空");
}
Student student = studentMapper.selectById(addDTO.getStuId());
if (student == null) {
throw new BusinessException("关联的学生ID" + addDTO.getStuId() + "不存在");
}
// 2. 关联课程ID存在性校验
if (addDTO.getCourseId() == null) {
throw new BusinessException("关联课程ID不能为空");
}
Course course = courseMapper.selectById(addDTO.getCourseId());
if (course == null) {
throw new BusinessException("关联的课程ID" + addDTO.getCourseId() + "不存在");
}
// 3. 分数范围校验
if (addDTO.getScore() == null || addDTO.getScore() < 0 || addDTO.getScore() > 100) {
throw new BusinessException("分数必须为0-100之间的整数");
}
// 4. DTO转实体
Score score = new Score();
score.setStuId(addDTO.getStuId());
score.setCourseId(addDTO.getCourseId());
score.setScore(addDTO.getScore());
score.setCreateTime(LocalDateTime.now());
this.save(score);
return score;
}
}

View File

@ -3,27 +3,39 @@
/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/CourseController.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/ScoreController.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/TeacherController.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/CourseAddDTO.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/ScoreAddDTO.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/dto/TeacherAddDTO.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/dto/TeacherUpdateDTO.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/entity/Course.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/entity/Score.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/entity/Teacher.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/CourseMapper.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/mapper/ScoreMapper.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/mapper/TeacherMapper.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/CourseService.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/ScoreService.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/TeacherService.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/impl/CourseServiceImpl.java
/home/awinx/code/java/lerning/java_web/stu01/src/main/java/top/awinx/stu01/service/impl/ScoreServiceImpl.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/service/impl/TeacherServiceImpl.java