(课设)青知平台Java后端开发日志——用户模块+资源模块 📅 2026/8/24 13:42:14 这是任务要求飞书文档https://my.feishu.cn/wiki/QYdkw1AMqi2fsokXVSXcnRyZnyc?fromfrom_copylink 目录导航8.14 - 项目初始化与基础框架搭建A. 学生注册8.15 - 注册功能完善与密码加密B. 登录8.16 - JWT登录与拦截器C. 教师注册D. 统一JSON返回值格式E. 业务异常与全局异常处理F. 修改个人信息G. 修改密码8.17 - 管理员功能H. 管理员I. 做EXCEL导入8.18 - 资源模块开始A. 建立资料库B. 上传文件的数据库记录8.19 - 资源查询与分页C. 文件上传并和资源记录关联D. 文件类型校验E. 用户重新上传资源和删除资源8.20 - 文件下载与收藏功能F. 文件下载G. 资源收藏和取消收藏H. Swagger API文档I. 日志Slf4j花了十天左右把Java的基础语法过了一遍所以我已经下过JDK和IDEA了springbootmysqlmaven啥啥的这些我没听说过的东西打算边做边学做一点学一点。之前学的在我的Java学习笔记里面已发布8.14首先在IDEA中创建SpringBootMaven项目新建 HelloController启动项目在浏览器访问接口并看到 Hello QingZhi。注意controller为根包的子包controller的核心职责是前后端的通信桥梁接收前端发来的 HTTP 请求调用后端业务逻辑处理可以调用service)然后把处理结果返回给前端。调用业务层。然后安装MySQL安装成功后在cmd里输入mysql -u root -p如果让输入密码就没问题了如果说文件找不到就是MySQL 的安装路径没有添加到 Windows 的环境变量中问问AI把它添加一下就欧克了。在windows里面打开mysql自带的客户端创建 qingzhi 数据库和第一张 user 表注意中文兼容性和账号UNIQUE账号密码NOT NULL下一步让 Spring Boot 成功连接 MySQL——去IDEA里在pom.xml里面加入mysql驱动依赖并在核心配置文件application.properties里进行配置。如此便打通了浏览器 → Spring Boot → MySQL通路接下来尝试在MYSQL里面建立一个学生试试看然后建立一个entity包存放所有数据库相关的类这里有一个小技巧写好private的诸多属性后快捷键Altinsert可以自动补充对应的setter和getter方法创建一个 mapper 包存放数据库的各种接口。里面定义继承BaseMapper的接口这样就能直接调用MyBatis-Plus 已经帮我们写好的操作 User 的常用数据库方法。紧接着创建测试接口测试成功这样最基础的框架就搭建好了。存用户数据的地方处理用户的方法开始写功能A.学生注册先实现写入再加密码加密此功能的实现用了Service层和Controller层的各一个文件来实现authcontroller文件负责接受并转换前端传来的信息并传入authservice方法而在service层分工authservice方法负责注册的业务逻辑调用usermappermapper专管把数据传入数据库userMapper.insert(user) //MyBatis-Plus 会根据 User 实体上的注解自动生成 INSERT 语句把数据插入到数据库里面。注MyBatis-Plus配置好了会自动调用不用再引入或者什么的提示RestController标记为“API 控制器”1.标记为控制器让 Spring 在启动时扫描到这个类并把它实例化成 Bean托管到 Spring 容器中。2.自动返回 JSON这个注解包含了 ResponseBody也就是说方法里返回的 Java 对象如 User、Map会被 Spring Boot 自动转换成 JSON格式的响应体直接返回给前端。3.处理 HTTP 请求配合方法上的 PostMapping 等注解就能接收来自客户端的请求。RequestBody可以自动把 JSON 转成 User 对象示例public String registerStudent(RequestBody User user) {}service:在service下的文件中在类的声明前进行注释用于标记这是一个 Service 层的 BeanSpring 识别他才会扫描并创建 AuthService 的实例进而让controller需要注入数据的时候能找到它写好之后用postman建议安装一个客户端别用网页版测试一下http传输再去MySQL里看看传进去没有在命令行里写SELECT * FROM user;测试成功就ok了。8.15下一步加一个业务规则注册时先检查学号是否已经存在。标准写法// 构建查询数据库需要用的条件是eq(“account”,user.getaccount)的安全加强版LambdaQueryWrapper wrapper new LambdaQueryWrapper();wrapper.eq(User::getAccount, user.getAccount());// 按wrapper条件在数据库里面查询信息若单一则返回对象实体。如果查询到多条数据会抛出异常如果查不到返回 null。User existingUser userMapper.selectOne(wrapper);//其中wrapper 的数据类型是 LambdaQueryWrapper这是 MyBatis-Plus 框架提供的一个查询条件构造器类指要操作的是user类型的实体。注查询账号用 LambdaQueryWrapper是因为账号在数据库里你必须通过条件去硬盘上把它“拽”出来。而匹配密码直接用 matches( password,user.getPassword()是因为密码密文已经被“拽”出来了在 user 对象里现在只需要在内存里进行数学运算比对。注意改完service层之后记得随之改controller层的return。然后加密码先在service逻辑里加按密码要求判断然后把密码从明文存储改成 BCrypt 加密存储这里先加一个加密工具的依赖然后要创建配置类config)Bean告诉 Spring这个方法返回的对象需要注册到 Spring 容器中,之后可以就被其他类直接拿来用。在service层新建密码对象在insert操作之前加一步加密。这样再注册信息后MySQL里面密码就不显示明文而是乱码了。注意理解一下在整个过程中密码经历了什么整条数据流如下① 用户输入“abc12345”↓② HTTP JSON{“password”: “abc12345”}↓③ Spring RequestBodyUser userpassword “abc12345”↓④ ControllerauthService.registerStudent(user)↓⑤ Serviceuser.getPassword()→ “abc12345”↓⑥ BCryptencodedPassword→ “$2a10 1010xxxxx…”↓⑦ setPassword 这里并非重新创建一个user而是直接在其基础上进行修改user.password→ “$2a10 1010xxxxx…”↓⑧ MapperuserMapper.insert(user) 也就是说数据库从没见过真正的密码↓⑨ MySQLpassword→ “$2a10 1010xxxxx…”B登录Go on!开始写我们的登录操作 JWT(登录成功后服务器发给用户的临时通行证【一串加密的字符串】——token)先载入几个JWT的依赖然后写Util 工具类这里主要写的是依据用户提供的信息和我们自定义的类密钥加密生成独一无二的token为什么不把注册功能写成工具类而要把登录功能写成工具类答因为登录功能中有生成Token这个通用操作而注册功能中没有类似的可复用操作。核心操作验证身份 → 生成 TokenPS这里语法有点忘了再复习一下就是SecretKey KEY 接口名实例名创建的是其实现类的引用变量这一块没看懂没看懂没看懂。。。可以参考B站上视频【【极简入门】15分钟学会JWT的使用】https://www.bilibili.com/video/BV1cK4y197EM?vd_sourcea46d0971e74a0a8fe32a8fef61e60678哦懂了要先定义一个服务于整个类的公用key持有key的人才能签发有效的token那些加密文字都是结合这个自定义的key用算法算出来的然后在写带个人信息生成token的Jwts.builder()然后service层controller层也都要做对应修改。8.16下一步做 JWT 校验 登录拦截器(验token先给 JwtUtil 增加两个方法解析 token 和 判断 token 是否有效防篡改防过期机制先用Jwts.parser()方法得到claims传入token通过特定方法得到当时输入的键值对集合只有没有过期格式正确未被篡改的才能通过不能被解码的则在执行该方法时异常用try-catch抛出并返回false。这个写在util工具类里面接下来写拦截器新创建interceptor类利用HandlerInterceptor接口封装好的功能先获取token先看是否为null以及前缀对不对然后调用util写好的防伪判断方法进行检验最终返回True和False决定能不能进入controllerComponent把当前类注册为 Spring 容器的一个 Bean组件因为后面要在配置类中用 Autowired 注入这个拦截器如果没加 ComponentSpring 就找不到它最后在config里面加一个WebConfig用来设定所有请求都要先经过interceptor的校验并设置了几个豁免口比如登录注册啥的接下来在controller里面加一个临时接口试试未登录的拦截功能登陆后再访问要带着登录返回的token去临时接口网页key写Authorizationvalue写tokenBearer 刚刚返回的那一串跑通了ok最后一步从 token 里取出当前登录用户的 userId让后端知道“现在是谁在访问”。这个在拦截器里面写因为是每次http请求都自己解码一次然后暂存在给这次HTTP请求分配的HttpServletRequest 对象里面。以供后续controller啥的使用。最后最后还有一点在拦截器里写的request.setAttribute,写对应的request.getAttribute让暂存的id信息能返回前端。可以回到entity里面把password的声明前面加上一个JsonIgnore在把 Java 对象转换成 JSON 时忽略被标注的字段——防止敏感信息通过接口泄露给前端。即使 BCrypt 密文不能直接还原也不应该把密码哈希返回出去。由此我的JWT登录功能就写完了棒棒哒~C.教师注册这一块和学生注册重复的逻辑很多所以主要涉及代码复用的问题。思路是把把注册核心逻辑账号查重验证密码加密抽成一个私有方法然后写非常简单的registerStudent()和registerTeacher()再去去AuthController 增加教师注册接口。D.把接口返回值统一成 JSON 格式把状态码1、成功消息“Success!”和业务数据data打包成一个 Result 对象返回。这里result写两遍构造方法一遍有参下面写success、error方法时用一遍无参支持手动创建setter以及给 Jackson、MyBatis 等框架在反序列化时使用——很多框架的实现依赖无参构造方法所以只需养成习惯以后写含参构造方法时都一起写上一个无参构造方法。无参可传可以写null为了给开发者提供两种灵活的使用方式写两个success方法——一种用默认的Success!消息一种用自定义的成功消息。一种只含data一个参一种还含一个String message。这样用默认省事且工整自定义信息的就可以在注册成功收藏成功登录成功修改成功等诸多场景下复用省代码。E.业务异常 全局异常处理这一部分在exception包里面写先写一个ExceptionHandler当一个 Controller 方法抛出异常时由被 ExceptionHandler 标记的方法来“接住”这个异常并返回一个友好的响应给前端就是我们写好的rsult规范格式里面的error而不是直接把错误堆栈信息抛给用户。接下来就是把注册登录接口也改成统一 Result 异常处理改service层之前在登录注册等等遇到异常只返回一个String正常运行的那里那样容易出事改成一个异常处理就直接throw出来强制停止再返回一个标准报错信息error方法。authcontroller里面的注册方法也可以只留下sucess的返回了因为error已经在service层就已经被抛出并返回信息了。登录同理。并把返回值都改成result里面的标准格式比如String token authService.login(account, password);return Result.success(token);这样重新分工Controller只负责接请求 返回成功结果Service只负责业务判断出错则throw BusinessException、GlobalExceptionHandler统一处理错误不负责注册成功的情况成功全权交给controller处理。F.修改个人信息目标用户只能改自己的信息不能通过前端传 id 去改别人。且修改个人信息时不允许修改account password role1.在service层定义一个含参——id的更新方法然后在controller里面的更新方法先取自己request里暂存的拦截器解码出来的id然后调用更新时强制参数为自己的id这样就能防他人篡改。2.不允许修改的直接这样处理 user.setAccount(null);至于更新操作先用user.setId(userId);锁定对象然后直接用现成的updateById(user)MyBatis-Plus 提供的一个智能更新方法。优化之前为了快速写 /users/me在 Controller 里直接用了userMapper.selectById(userId);但是现在写好了UserServiceUserController 其实不应该再直接持有 UserMapper而是只对接UserService。这样在UserService最后再加一个以userid为参能返回user的方法。原则Controller 调 ServiceService 调 Mapper。Controller 通常不要直接操作 Mapper。注意咱的核心功能是在service层的一个类里面写的想调用其中方法就得依托人家的对象所以在controller里面要先声明一个userservice对象同样userservice类里面也要声明一个usermapper对象来连上人家。G.修改密码这个用Map和键值对来完成参数RequestBody MapString, String bodyString oldPassword body.get(“oldPassword”);String newPassword body.get(“newPassword”);userService.changePassword(userId, oldPassword, newPassword);因为要验证旧密码、加密新密码所以 UserService 也需要 BCryptPasswordEncoder。8.17H.管理员让系统里存在一个固定管理员账号具有用户管理权限。先查询管理员用这一套LambdaQueryWrapper wrapper new LambdaQueryWrapper();wrapper.eq(User::getAccount, “Admin”);User admin userMapper.selectOne(wrapper); //零条返回null一条返回user对象多条抛出异常没有的话就创建初始化管理员账户应该先写死一个初始化密码管理员不能自己注册所以先按这个登进去之后可以再连接口修改密码。当类继承了CommandLineRunner接口表明Spring每启动一次run都会自动执行。所以才要先判断有没有再插入管理员然后写管理员权限校验目标普通用户访问 /admin/** 返回“无权限”管理员访问则放行这个从拦截器里调出role并在拦截器里加一个管理员路径判断测试没问题接下来我们要做管理员crud——管理员操作在逻辑上和用户操作是一样的地位也是平等的。所以就另设一个AdminUserController和AdminUserService(为了省事我把这个放在userservice里面了没有单独拎出来因为那里局部变量的声明是全的管理员查询用户信息、管理员新增用户、管理员修改用户信息、管理员删除用户、重置密码把“查询自己发布资源”和“收藏列表”先预留接口不实现等资源表建好后补I.做EXCEL导入导入操作属于数据库操作在entity层。暂定excel格式账号 | 姓名 | 手机号 | 邮箱 | 院系 | 专业 | 角色 | 初始密码其中角色STUDENT/TEACHERpublic class UserImportRow {ExcelProperty(“账号”)private String account;public String getAccount() {return account; }public void setAccount(String account) {this.account account;}}下一步给 Excel 导入补必填校验 账号重复校验 密码校验 角色校验校验统统在service层也写在userservice里面。for (int i 0; i rows.size(); i) {UserImportRow row rows.get(i);// Excel 第一行是表头所以数据第1行实际是 Excel 第2行int excelRow i 2;if (row.getAccount() null || row.getAccount().isBlank()) {throw new BusinessException(“第” excelRow “行账号不能为空”);}}Transactional(rollbackFor Exception.class)事务回滚“自动回滚整个数据库操作” 把这一系列数据库操作当作一个整体只要有一步失败前面所有已执行的操作全部撤销恢复到方法执行前的状态。——确保数据一致性手机号 / 邮箱格式校验先不做了假的就假的吧。——————————————————————————————用户模块完结资源模块开始8.18A.建立资料库资料库建立和之前存用户的是一样的之前是qingzhi里面的user表现在新建一个resource表仍是声明表头及填入方式限制等然后在entity里面建的文件就放一堆变量的声明和setter getter再让主代码连数据库建新的mapper则是定义一个继承了BaseMapper操作表的接口MyBatis-Plus已经帮我们写好了各种操作。B.上传文件的数据库记录纯资源记录 CRUD 审核状态流转做接下来就是进service层和controller层写功能了service层就是先声明局部变量mapper然后在一堆判断之后insert的方法上传资源、查询资源、修改自己上传的资源、删除自己上传的资源、已审核未审核资源的可见性、管理员资源审核分页展示用MyBatis-Plus的分页插件在config里面进行配置MybatisPlusInterceptor MyBatis-Plus 的拦截器插件核心所有增强功能都在它上面添加。interceptor.addInnerInterceptor(…) 往拦截器里添加一个内部插件new PaginationInnerInterceptor(DbType.MYSQL) 创建分页插件并指定数据库类型为 MySQL不同数据库的分页语法不同。return interceptor; 把配置好的拦截器交给 Spring 管理。再把ResourceService改成带分页的——先创建一个分页对象Page pageObject new Page(page, pageSize);规定了页码和每页几条。然后正常查询、筛选approved及按时间排序最后返回resourceMapper.selectPage(pageObject, wrapper);会自动生成分页MySQL。在controller层把那个page和pagesize用RequestParam(defaultValue “1”) long page,RequestParam(defaultValue “10”) long pageSize放在参数里。8.19按发布时间范围查询 时间倒序 添加参数RequestParam(required false) LocalDate startDate和…RequestParam 告诉 Spring“这个参数的值需要从 URL 的查询参数中提取”required false 这个参数不是必须的如果 URL 里没有这个参数Spring 会传 null 进来而不会报 400 错误LocalDate startDate 参数名称是 startDate类型是 LocalDateJava 8 引入的日期类型只有年月日没有时分秒在servicce层也改如果传了startday如果传了endday用ge()lt()如下if (startDate ! null) {wrapper.ge(Resource::getCreatedAt,startDate.atStartOfDay());}日期格式要求yyyy-MM-ddC.文件上传并和资源记录关联在controller层仅需加上一个 RequestParam(“file”) MultipartFile file) 参数在service层大小检查、防重名覆盖UUIDoriginalname然后创建一个 File 对象指向一个叫 uploads 的文件夹路径是相对于当前运行目录的即项目启动的位置。但是有可能还没有这个文件夹if (!uploadDir.exists()) { uploadDir.mkdirs();}在后面用这个检验然后再创建也来得及。然后try {file.transferTo(destination);} catch (IOException e) {throw new BusinessException(“文件保存失败”);} 用这个写入并抛出异常。最后就再存数据库信息。D.文件类型校验先判断格式对不对然后取.后的部分建立一个允许的后缀库然后据此判断后缀是否合法就行了语法.toLowerCase();——把字符串里的所有英文字母全部转换成小写——做字符串适配时若不在意大小写应该先全部转为大写或小写。字符串操作substring取…之后的字符串例如unhappy.substring(2) returns “happy”E.用户重新上传资源和删除资源把update也接入文件操作规则修改资源时文件可选。没传文件就保留旧文件传了新文件就保存新文件、更新数据库成功后再删除旧文件。这一块优化一下不再一个一个写了而是把文件和数据库操作隔离先写一个存文件savefile和删除文件的方法然后接入资源create、update、delete方法中理解File类型File 对象本身 不是 物理文件它在 Java 中只是一个 “路径名的抽象表示”可以理解为一张写有地址的“便签”或“指针”。savefile进行一系列合规检查后创建唯一文件名创建文件存储目录创建失败且无此文件时抛出异常用File destination new File(uploadDir, storedFilename);创建一个指向具体文件的 File 对象把文件真正存入硬盘的操作file.transferTo(destination);放入try-catch里面最终应该返回return destination.getPath();路径。deletefile删文件是用path删掉的private void deleteFile(String filePath) {if (filePath null || filePath.isBlank()) {return;}File file new File(filePath);if (file.exists()) {boolean deleted file.delete();if (!deleted) {System.out.println(“文件删除失败” filePath);}}}8.20F.文件下载先用token校验用户以及查询要下载的文件是否存在resource里面找一遍file里面找一遍。org.springframework.core.io.Resource fileResource new FileSystemResource(file);把硬盘上的一个物理文件包装成一个 Spring 框架可以统一处理的“资源对象”。处理一下文件名中文兼容性String fileName resource.getFileName();String encodedFileName URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll(“\”, “%20”);然后是返回文件流告诉浏览器弹出下载框八股return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, “attachment; filename” encodedFileName “”).contentType(MediaType.APPLICATION_OCTET_STREAM).body(fileResource);G.资源收藏和取消收藏(没收藏就收藏收藏了就取消收藏就是单独建立一个表放收藏的资源在entity中建立收藏列表用的表名与类名相同只有首字母大小写不同时可以不加TableName(“user_favorite”)来说明该实体类和数据库中哪个表关联但是这里显然类名表名不统一所以必须要加。H.SwaggerSwagger API 文档生成工具通过在代码里加注解自动生成可视化、可在线调试的 API 文档彻底告别手写文档保证代码和文档永远同步。这个文档是给前端看的首先加入 SpringDoc 的 Starter 依赖添加基础配置可选用于定制文档标题与描述——写在application.properties在 Controller 中加上友好的注解可选——为了让生成的接口文档更加清晰规范可以在你的 ResourceController 或实体类上适当加一些 OpenAPI 注解如Tag(name “资源管理模块”, description “资源的上传、下载、查询、修改与删除”)别忘了写config配置文件以及把这个http从webconfig的登陆名单里加上豁免金牌。http://localhost:8080/swagger-ui/index.html浏览器访问这个就可获得漂亮的可视化页面I.日志Slf4j就是在需要日志的地方在return之前加一个log让黑盒变成明盒。这也是一个写好的功能加一个依赖一个import再加一个Slf4j就行了在exception、service层、JWT拦截器加一定要放在return之前放在return后面永远都执行不了然后改controller层Service记录业务结果上传成功、删除成功Controller记录用户请求入口所以 Controller 不要大量加日志否则一个页面查询会产生大量无意义日志。所以只记录发送请求不记录成功。最后还需要在application.properties里面配置日志输出文件就能让 Spring Boot 把日志保存到文件了