资讯动态

SpringBoot国学平台:MyBatis-Plus分页与Token鉴权

发布时间:2026/9/20 12:06:32 来源:尧图企业网站定制
简介本资源为基于SpringBoot的大学生国学自主学习平台毕业设计论文文档面向计算机相关专业本科毕业生以及需要完成课程设计、项目实践的学生可用于参考系统设计与论文写作的整体思路。压缩包内共1个docx文件约3.09MB包含摘要、英文摘要、目录以及系统相关技术等完整章节围绕信息管理系统、数据挖掘、硬件优化与软件开发等方向展开论述涉及SpringBoot框架的快速开发与灵活配置、学习行为数据的分析与个性化推荐、平台在提高学习效率和实时反馈方面的优势以及移动学习、人工智能等发展趋势的探讨。就价值而言读者可据此了解一个国学自主学习平台从需求分析、技术选型到功能实现的完整脉络借鉴其章节组织与论证方式用于自己的论文框架搭建也能从中获取系统模块划分与数据库设计的表达参考。目前已有171人学习下载适合需要毕业设计选题参考、论文结构与技术方案借鉴的读者。1. 从一份毕设源码说起SpringBoot 国学平台的选题与边界一份基于 SpringBoot 的大学生国学自主学习平台源码放在毕设选题里看似普通拆开却能把后端工程师的日常活儿过一遍。学生打开浏览器看资料、发帖、收藏管理员在后台管学生、管资料分类、管公告和论坛两条角色线跑在同一套工程里靠一张 token 表维持登录态。它真正能交付的是三件事把散落的国学资料共享收进统一入口把论坛讨论和收藏挂到同一份用户数据上把管理员手工登记换成页面操作。适合正做同类毕设、需要一个能跑起来骨架的人。边界也要提前认清单数据源、单体分层、权限只到角色一级没有细粒度数据权限和分布式会话想直接改成在线教育 SaaS 会很快撞墙。后面几章只拆它真正落地的部分。2. SpringBoot 工程骨架与自动装配的落点2.1 依赖清单与版本对齐先讲一个高频翻车点springboot 版本太高。用 IDEA 新建 springboot 项目时默认勾最新版本地 JDK 却是 1.8编译直接抛Unsupported class file major version。国学平台这种体量SpringBoot 2.7.x JDK 8 是稳妥组合MyBatis-Plus 对它的支持也最稳。依赖坐标用途spring-boot-starter-weborg.springframework.bootWeb MVC 与内嵌 Tomcatmybatis-plus-boot-startercom.baomidou:3.5.3.1单表 CRUD、条件构造器、分页mysql-connector-jcom.mysql:8.0.33MySQL 8 驱动时区参数必带lombokorg.projectlombok:1.18.28Data 省去 getter/setterhutool-allcn.hutool:5.8.20文件、字符串、日期工具依赖不多是因为这个系统的业务复杂度并不在框架层而在表关系和角色划分上。硬塞 Redis、消息队列只会让毕设答辩时多出一堆解释成本。2.2 启动类与自动装配原理package com.guoxue.platform; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication MapperScan(com.guoxue.platform.mapper) public class GuoxueApplication { public static void main(String[] args) { // 内嵌 Tomcat 随 main 方法启动不需要外置容器 SpringApplication.run(GuoxueApplication.class, args); } }逻辑说明SpringBootApplication是SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解的组合。EnableAutoConfiguration会读取各 starter 包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports把DataSourceAutoConfiguration、MybatisPlusAutoConfiguration这类配置类按条件装配进容器这就是加了依赖就能用的根本原因。MapperScan必须手写是因为 MyBatis 的 Mapper 只有接口没有实现类要靠扫描注册成MapperFactoryBean。参数说明MapperScan的 value 写具体的 Mapper 包路径写成com.guoxue.platform这种父包启动时会扫到 Service、Controller 接口并报错。2.3 application.yml 的关键参数server: port: 8080 servlet: context-path: /guoxue spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/guoxue_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.guoxue.platform.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl逻辑说明与参数说明serverTimezoneAsia/Shanghai不写MySQL 8 会抛The server time zone value xxx is unrecognized这是新手第一个拦路虎。map-underscore-to-camel-case: true让material_name自动映射到materialName省掉一大堆Results。log-impl打开后控制台直接打印执行的 SQL 和入参联调阶段必开上线前关掉否则日志量翻倍。max-file-size是单文件上限max-request-size是整个请求上限。学习资料要传 PDF 附件20MB 够用两个值不一致时会遇到单文件没超限但整体请求被拒的怪现象。2.4 部署形态与前后端对接这套系统走 B/S前端页面放resources/static下接口统一前缀/guoxue/api直接mvn package打成可执行 jar 丢服务器java -jar即可。如果前端交给 Vue 做前后端分离后端补一层 CORSConfiguration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)必须和allowCredentials(true)搭配换成allowedOrigins(*)会在携带 Cookie 的请求上被浏览器直接拒绝控制台报 CORS 头里Access-Control-Allow-Origin不能为通配符。3. 学习资料、学习论坛、收藏三张核心表的落地3.1 从 E-R 图到物理表数据库设计规范里说的职责分离落到操作层面就三件事表名前缀统一、主键一律bigint自增、时间字段统一叫addtime。E-R 图给的是概念模型物理表还得补类型长度、非空约束和索引。学习资料是本平台最核心的表字段如下。字段类型说明idbigint主键自增addtimetimestamp创建时间material_namevarchar(200)资料名称非空category_idbigint资料分类 id非空student_idbigint上传学生 idstudent_namevarchar(100)上传学生姓名file_urlvarchar(500)资料附件地址cover_urlvarchar(500)封面图地址introlongtext资料简介3.2 建表 SQL 与索引取舍CREATE TABLE study_material ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, addtime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, material_name VARCHAR(200) NOT NULL COMMENT 资料名称, category_id BIGINT NOT NULL COMMENT 资料分类id, student_id BIGINT DEFAULT NULL COMMENT 上传学生id, student_name VARCHAR(100) DEFAULT NULL COMMENT 上传学生姓名, file_url VARCHAR(500) DEFAULT NULL COMMENT 资料附件, cover_url VARCHAR(500) DEFAULT NULL COMMENT 封面图, intro LONGTEXT COMMENT 资料简介, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_addtime (addtime) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学习资料;逻辑说明idx_category服务于按资料分类筛选这个最高频查询idx_addtime服务于列表默认按发布时间倒序。不给student_name建索引因为按姓名搜索是模糊匹配走不了索引反而拖慢写入。参数说明utf8mb4而不是utf8国学资料里出现生僻字、异体字不会被截断intro用longtext避免正文稍长就报Data too long for column。3.3 学习论坛的父子节点与状态字段学习论坛表用parentid做自关联parentid 0是主帖其余是回复配合isdone记录状态。好处是不用再单独建一张回复表删主帖时按parentid批量清理即可。CREATE TABLE study_forum ( id BIGINT NOT NULL AUTO_INCREMENT, addtime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title VARCHAR(200) NOT NULL COMMENT 帖子标题, content LONGTEXT COMMENT 帖子内容, parentid BIGINT NOT NULL DEFAULT 0 COMMENT 父节点id0为主帖, userid BIGINT NOT NULL COMMENT 发帖用户id, username VARCHAR(200) DEFAULT NULL, avatarurl LONGTEXT, isdone VARCHAR(200) DEFAULT 开放 COMMENT 状态, PRIMARY KEY (id), KEY idx_parent (parentid), KEY idx_user (userid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学习论坛;逻辑说明查主帖列表用WHERE parentid 0 ORDER BY addtime DESC点进详情再按parentid 当前帖id拉回复。这种两次查询的写法比自连接直观数据量不大时性能也完全够。参数说明isdone用varchar存中文状态而不是tinyint是为了后台管理页面直接展示开放/关闭代价是查询时不能用数值比较需要注意传参大小写。3.4 收藏表的多态设计与分页接口收藏表把userid refid tablename组合起来同一张表既能存资料收藏也能存点赞、踩用type字段区分1 收藏、21 赞、22 踩、41 关注inteltype标记推荐类型前端只读不写。RestController RequestMapping(/api/collect) public class CollectController { Autowired private CollectService collectService; GetMapping(/page) public R page(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer limit, RequestParam(required false) String tablename, HttpServletRequest request) { Long userId (Long) request.getAttribute(userId); PageCollect p new Page(page, limit); LambdaQueryWrapperCollect qw new LambdaQueryWrapper(); qw.eq(Collect::getUserid, userId) .eq(StringUtils.hasText(tablename), Collect::getTablename, tablename) .orderByDesc(Collect::getAddtime); return R.ok().put(data, collectService.page(p, qw)); } }逻辑说明eq的第一个参数是布尔条件tablename为空时整个条件不会拼进 SQL省掉一个 if 分支。Page对象由 MyBatis-Plus 的分页插件接管前提是配置类里注册了PaginationInnerInterceptor。忘了注册的话分页会静默失效接口不报错但返回全量数据这是最容易被忽略的一类问题。参数说明file_url存相对路径域名前缀在回显时拼换部署环境不用改数据userId从拦截器写入的 request 属性取绝不能从请求参数取否则越权。4. Token 表、双角色鉴权与登录流程4.1 用 token 表替代内存 Session 的理由B/S 架构下直接用 HttpSession 也能跑通但系统里既然已经有 token 表好处有三服务重启后登录态不丢一张表能同时记录username、role、tablename为后续某用户在某张表上的操作留出扩展位前端把 token 放请求头接口能被 Postman 单独调用联调省事。4.2 token 表结构与签发逻辑字段类型说明idbigint主键useridbigint用户 idusernamevarchar(100)用户名tablenamevarchar(100)表名区分管理员/学生rolevarchar(100)角色tokenvarchar(200)令牌串addtimetimestamp生成时间expiratedtimetimestamp过期时间Service public class TokenServiceImpl extends ServiceImplTokenMapper, Token implements TokenService { Override public String generate(Long userId, String username, String tableName, String role) { // 同一用户重复登录先清旧 token避免表无限增长 LambdaQueryWrapperToken qw new LambdaQueryWrapper(); qw.eq(Token::getUserid, userId).eq(Token::getTablename, tableName); this.remove(qw); Token token new Token(); token.setUserid(userId); token.setUsername(username); token.setTablename(tableName); token.setRole(role); token.setToken(UUID.randomUUID().toString().replace(-, )); token.setAddtime(new Date()); // 默认 2 小时过期实际项目里读配置表参数更好 token.setExpiratedtime(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000L)); this.save(token); return token.getToken(); } }逻辑说明先删后插保证一个用户在同一张表上只有一条有效 token避免旧 token 还能继续用。UUID 去掉横线是 32 位十六进制长度可控、无碰撞顾虑。过期时长写死在代码里不算优雅常见做法是从配置表config表里读token_expire_hours参数。参数说明tablename传students表示学生、admins表示管理员这个值决定后续查哪张用户表校验账号。4.3 拦截器做身份校验角色校验往下放Component public class AuthInterceptor implements HandlerInterceptor { Autowired private TokenService tokenService; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Token); if (token null || token.isEmpty()) { throw new BusinessException(请先登录); } Token t tokenService.getOne(new LambdaQueryWrapperToken().eq(Token::getToken, token)); if (t null) { throw new BusinessException(登录已失效); } if (t.getExpiratedtime().before(new Date())) { tokenService.removeById(t.getId()); throw new BusinessException(登录已过期请重新登录); } // 把用户身份写进 request控制器直接取用 request.setAttribute(userId, t.getUserid()); request.setAttribute(role, t.getRole()); return true; } }逻辑说明拦截器只做身份校验角色粒度校验放到具体接口上读request.getAttribute(role)判断或自定义注解 AOP。全部塞进拦截器会让路径规则越写越长到最后没人敢改。注意拦截器必须注册才生效注册时用addPathPatterns圈定受保护路径excludePathPatterns放行登录、注册和静态资源。Configuration public class InterceptorConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/register, /static/**); } }4.4 密码修改与改密后的令牌清理管理员和学生共用一套修改密码接口区别只在tablename参数。PostMapping(/updatePassword) public R updatePassword(RequestBody MapString, String body, HttpServletRequest request) { String oldPwd body.get(oldPassword); String newPwd body.get(newPassword); Long userId (Long) request.getAttribute(userId); String table body.get(tablename); User user userService.getById(userId, table); if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(oldPwd.getBytes()))) { return R.error(原密码不正确); } user.setPassword(DigestUtils.md5DigestAsHex(newPwd.getBytes())); userService.update(user); // 改密后清掉该用户所有 token强制重新登录 tokenService.removeByUser(userId, table); return R.ok(); }逻辑说明改密后清 token 这一步常被漏掉漏了会出现密码已改但旧设备仍在线的尴尬。MD5 是最省事的写法稍微讲究一点会拼一个固定盐值再哈希。参数说明request.getAttribute(userId)来自拦截器不要从请求体取userId否则等于主动开一个越权入口。tablename虽然来自前端但只用于选表实际用户 id 是拦截器给的改它只能选错表、选不出别人的数据。5. 联调排错与几个能立刻复现的坑系统跑起来之前报错基本集中在这几类每一条都能在下一次运行里复现。现象根因处理启动报Failed to configure a DataSourceyml 缩进错或 datasource 键名写错检查spring.datasource是否顶格、url 是否被断行分页接口返回全量数据没注册PaginationInnerInterceptor在 MyBatis-Plus 配置类里addInnerInterceptor时间字段比实际少 8 小时URL 缺serverTimezone补serverTimezoneAsia/Shanghai接口报 401 但 token 明明传了请求头大小写不一致拦截器用getHeader(Token)前端必须同名前端页面全 404没带context-path访问路径要拼上前缀/guoxue分页插件的注册代码很短但缺了它排错成本极高Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 不写这一行Page 对象就是个摆设 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }另一个容易反复踩的地方是 E-R 图与物理表不一致。E-R 图里学习资料只画了资料分类、上传日期、学生姓名、资料名称、学号、资料附件、资料图片七个属性但物理表必须补id和addtime并且把资料图片拆成cover_url列表缩略图和file_url附件原文件。答辩时被问到为什么 E-R 图字段比表少是常见提问提前把这一层对应关系在文档里标出来比临场解释轻松得多。最后给一个自测顺序先用curl直调/api/login拿 token确认账号密码、密码哈希、token 落表三步都通再拿这个 token 调/api/material/page验证拦截器、分页、SQL 映射最后才点前端页面。顺序反过来一旦页面上白屏你分不清是后端 401、分页失效还是前端路由配错排查面会一下子放大三倍。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价