多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SSM+JSP赛事打分系统课程设计全流程实战

SSM+JSP赛事打分系统课程设计全流程实战 前阵子帮一个学弟把课程设计从零跑通项目就是典型的SSM赛事打分系统评委登录、给选手打分、赛后自动出排名。说实话这两年在各种技术社区聊JavaWeb开口就是Spring Boot加Vue好像SSMJSP已经成了考古词汇。但真到了课程设计、毕业设计和学校内网小工具这类场景这套组合的出现频率远比想象中高。这篇就把整个项目的落地过程完整捋一遍从评分模型怎么设计、Maven怎么配、SSM三件套怎么整合到打分页面、实时排名怎么写再到Maven下载依赖、MySQL连接、IDEA启动那一堆坑一次性说清楚。适合正在做课程设计或毕业设计或者想通过老项目理解Spring核心机制的同学参考。1. 为什么还在用SSMJSP这套“过时”组合做打分系统1.1 SSM在课设与内网工具场景里依然能打先说结论技术选型没有绝对优劣只有场景合不合适。打分系统的典型使用场景是某次比赛几个评委拿着账号登录系统给若干选手打分后台自动算平均分、出排名。这类系统有几个特征用户量小、并发极低、功能边界清晰、部署环境可控本质上是一个内网工具型应用。对于这种项目SSM JSP LayUI的组合有它无法忽视的优势。第一SSM是Spring框架的经典形态Spring的IoC/DI、AOP事务、SpringMVC的请求映射、MyBatis对SQL的控制力都能在这套代码里直观看到这对课程设计和面试准备来说比直接用Spring Boot“自动装好一切”更有价值。第二JSP配合JSTL做服务端渲染数据模型直接在页面里展开不需要前后端联调省掉一套接口文档和一个前端工程。第三LayUI把后台管理常用的表格、弹窗、表单组件打包好了写打分管理页几乎是在拼积木。我这样说不是否定Spring Boot。实际上如果是我自己公司的生产项目大概率也会选用心智负担更低的Boot。但凡是让我评估一个课程设计或毕业设计项目我通常会反问一句你是为了“作品能跑”还是为了“把某个知识点吃透”如果是后者SSM这套组合反而更能逼着你去理解Spring容器、代理机制、Mapper扫描这些底层原理这些恰恰是Boot帮你藏起来的东西。1.2 打分系统选型JSP LayUI比前后端分离更快再展开一点聊前端选型。现在的趋势是前后端分离Vue ElementUI Axios一套下来界面确实漂亮但代价是工程复杂度翻倍Node环境、构建流程、跨域、接口联调、Token鉴权……一个赛事打分系统总共就六七个页面用这套配置大概要多花一半时间在工程搭建上。JSP LayUI的路径要短得多页面写在webapp目录下后端Controller渲染数据模型页面里用EL表达式和JSTL取值需要异步刷新排名时直接调一个返回JSON的接口LayUI的table模块负责把表格画出来打分弹窗用LayUI的layer表单验证用LayUI自带的校验规则。一个评委打分页从前端到后端半天不到就能跑通。要澄清一个误区很多人以为JSP已经淘汰了。其实Servlet容器至今是JavaWeb的底层规范JSP只是被Spring Boot冷落了而已。在课设场景页面要少、要快、要能直接在Tomcat里跑JSP反而是最优解。等真上了生产项目再学前后端分离也不迟这两个能力不冲突。1.3 这套组合的边界什么时候不要选话也不能说满。如果项目具备下面任何一个特征我会劝你放弃SSMJSP直接上Spring Boot需要复杂的权限体系和分布式会话有高并发或大量异步请求的场景页面交互复杂需要大量前端状态管理部署环境强制要求容器化。赛事打分系统显然不在这个范围内所以选它没有问题。选型时把“够用”和“好用”分开判断心里就踏实了。2. 打分系统的表结构设计先把评分模型想清楚2.1 实体关系梳理动手写代码之前先把实体关系理清。这是项目成败的关键数据库设计错了后面反复改表比写代码还烦。赛事match、选手player、评委judge、评分记录score_record是四个核心实体。它们之间的关系可以概括为一场赛事包含多名选手选手属于某场赛事一场赛事有多名评委评委与赛事是多对多选手每被一位评委打一次分产生一条或若干条评分记录。为了简化我按“一场赛事一套数据”来设计。如果将来要支持“同一选手报名多场比赛”可以把player表里的match_id拆出去加一张match_player关联表数据模型会更好扩展。课设场景一般不需要预支复杂度先把基础的做对。评分项的问题值得单独说。现实中赛事打分通常不止一个总分比如“技术分30 完成分30 创意分40”。两种做法一种是把各项得分拼接为明细字段另一种是单独建score_item表。我建议打分范围比较固定时直接用明细字段比如加tech_score、complete_score、creative_score三列如果评分项可能变化就建score_item表和score_detail表虽然查询麻烦一点但配置灵活。下面示例用“评委给选手打一个综合分”作为主流程存score再补充说明评分项的扩展方式。2.2 建表SQL与字段说明核心建表语句可以直接照抄这是SSM赛事打分系统里最常见的一套结构-- 用户表管理员和评委共用 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) DEFAULT , role VARCHAR(20) NOT NULL DEFAULT judge, -- admin / judge create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 赛事表 CREATE TABLE match_info ( id INT PRIMARY KEY AUTO_INCREMENT, match_name VARCHAR(100) NOT NULL, begin_time DATETIME, end_time DATETIME, status TINYINT NOT NULL DEFAULT 0, -- 0未开始 1进行中 2已结束 remark VARCHAR(255) DEFAULT ); -- 选手表 CREATE TABLE player ( id INT PRIMARY KEY AUTO_INCREMENT, match_id INT NOT NULL, player_no VARCHAR(20) DEFAULT , player_name VARCHAR(50) NOT NULL, team_name VARCHAR(50) DEFAULT , create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 评分记录表 CREATE TABLE score_record ( id INT PRIMARY KEY AUTO_INCREMENT, match_id INT NOT NULL, judge_id INT NOT NULL, player_id INT NOT NULL, score DECIMAL(5,2) NOT NULL, comment VARCHAR(255) DEFAULT , create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 同一评委对同一选手只允许存在一条打分记录 UNIQUE KEY uk_judge_player (match_id, judge_id, player_id) );几个值得注意的细节score用DECIMAL(5,2)而不是FLOAT或DOUBLE精确小数避免统计时出现浮点数误差用UNIQUE KEY兜底防重复打分这是数据库层面的最后防线业务层就算判断漏了也不会产生脏数据create_time用DEFAULT CURRENT_TIMESTAMP插入时不用手动赋值password字段建议存BCrypt哈希哪怕课设也尽量不要明文。一个简单的做法是项目中引入jBCrypt登录时用BCrypt.checkpw比对。如果要做评分项就在score_record基础上增加score_item表和score_detail表CREATE TABLE score_item ( id INT PRIMARY KEY AUTO_INCREMENT, match_id INT NOT NULL, item_name VARCHAR(50), max_score DECIMAL(5,2), weight DECIMAL(5,2) DEFAULT 1 ); CREATE TABLE score_detail ( id INT PRIMARY KEY AUTO_INCREMENT, record_id INT NOT NULL, item_id INT NOT NULL, score DECIMAL(5,2) NOT NULL, UNIQUE KEY uk_record_item (record_id, item_id) );这种设计灵活性高但统计平均分时要多走几次JOIN。课设如果没有强需求先把单列score版本做扎实即可。2.3 防重复打分与统计表的设计思路防重复打分是打分系统的核心约束。为什么强调UNIQUE KEY因为在并发场景下两个请求同时进来先查后插的业务判断会有窗口期数据库唯一索引直接把第二次插入挡死这个保障比什么分布式锁都简单可靠。具体到代码层保存打分时用“存在则更新不存在则插入”是常见姿势。我习惯用INSERT ... ON DUPLICATE KEY UPDATE一句SQL解决幂等INSERT INTO score_record (match_id, judge_id, player_id, score, comment) VALUES (1, 2, 3, 95.5, 完成度很高) ON DUPLICATE KEY UPDATE score VALUES(score), comment VALUES(comment);这样评委修改打分的时候同样的接口直接覆盖旧值不会越积越多。3. Maven与SSM整合骨架搭稳了后面才不折腾3.1 pom.xml依赖与版本选择先回答一个高频问题Maven是干嘛的简单说它是一个“项目构建 依赖管理”的工具pom.xml就是它的核心配置文件。你在项目里需要什么jar包在pom.xml里声明坐标Maven自动下载并把它们塞进classpath。SSM项目里大量的“ClassNotFound”报错八成都能追溯到依赖没配好。新手在pom.xml卡住是最高频的事故现场。一个个试版本下载依赖、jar包冲突、编译报错都跟这里有关。我贴一个实测可用的精简依赖清单基于Spring 5.1.20.RELEASE MyBatis 3.5.x MySQL 8.xproperties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.1.20.RELEASE/spring.version /properties dependencies !-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency !-- 数据库驱动 连接池 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.23/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.6/version /dependency !-- 常用工具/JSON -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.9.10/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency /dependencies版本选择有几个原则Spring和MyBatis版本不要盲目追新课设里用5.1.x和3.5.x完全够追新版本反而容易碰到兼容性问题mysql-connector-java的8.x版本对应MySQL 8.0如果数据库是5.7也可以用5.1.49javax.servlet-api必须用provided作用域否则Tomcat启动时会和容器自带的Servlet API冲突这是报错重灾区。3.2 SSM三件套配置要点SSM的整合本质上就是三个配置文件的活applicationContext.xml负责Spring容器spring-mvc.xml负责SpringMVCweb.xml负责把它们挂到Tomcat上。很多同学对这个结构一头雾水我尽量用一句话说透applicationContext.xml管数据源、事务、Service、Mapperspring-mvc.xml只管Controller和页面渲染web.xml是一个入口负责让Tomcat把请求交给SpringMVC。applicationContext.xml的关键片段!-- 数据源 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/score_system?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghaiamp;useSSLfalse/ property nameusername valueroot/ property namepassword value你的密码/ /bean !-- SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.example.score.entity/ /bean mybatis-spring:scan base-packagecom.example.score.mapper/spring-mvc.xml的关键片段context:component-scan base-packagecom.example.score.controller/ mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static//web.xmlservlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping这里有几个高频坑实体类所在的包名必须和typeAliasesPackage一致否则Mapper返回结果集时实体映射不上spring-mvc.xml里如果漏了mvc:resources访问静态CSS/JS会404LayUI就加载不出来视图解析器的prefix指向/WEB-INF/jsp/这意味着页面必须放在WEB-INF下面外部不能直接访问也算一个安全特性。3.3 目录结构与启动顺序一个实测可运行的标准目录结构长这样src ├── main │ ├── java │ │ └── com/example/score │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ └── entity │ ├── resources │ │ ├── mapper │ │ └── spring/applicationContext.xml │ └── webapp │ ├── static │ │ ├── layui │ │ └── css/js │ └── WEB-INF │ ├── web.xml │ └── jsp └── pom.xml启动顺序上我的建议是“先Maven后Tomcat”先执行mvn clean把target清理干净再执行mvn install或直接在IDEA里执行mvn package确认依赖、编译、打包都通过然后把war包部署到Tomcat或者用IDEA的Tomcat集成方式直接run。顺序反过来的结果是Tomcat一启动就报ClassNotFound或Could not initialize class你根本分不清是代码问题还是构建问题。每一步验证完了再进下一步排查范围会小很多。4. 打分功能落地从JSP表单到LayUI渲染4.1 登录与权限拦截赛事打分系统的角色就两个管理员和评委。管理员管基础数据评委只负责打分和看成绩。权限拦截最直接的方式是写一个SpringMVC的HandlerInterceptorpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }在spring-mvc.xml里注册拦截路径mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ mvc:exclude-mapping path/judge/login/ /mvc:interceptor /mvc:interceptors评委是否被允许给某场赛事打分可以在进入打分页前查一次评委与赛事的绑定关系并在打分提交时再校验一次。页面级校验防手滑后端校验防伪造请求两层都得有。4.2 打分页面的LayUI实现打分页面我用的是LayUI table展示选手列表加弹窗打分。页面整体结构是一个搜索表单按姓名或编号筛选一个LayUI table渲染选手数据每行放一个“打分”按钮点击打分按钮弹出layer弹窗里面是LayUI的form表单提交时用form.on(submit)拦截走AJAX到后端评分接口。核心页面片段页面在WEB-INF/jsp下用JSP语法引入LayUIlink relstylesheet href${pageContext.request.contextPath}/static/layui/css/layui.css script src${pageContext.request.contextPath}/static/layui/layui.js/script table idplayerTable lay-filterplayerTable/table script layui.use([table, layer, form], function () { var table layui.table, layer layui.layer, form layui.form; table.render({ elem: #playerTable, url: ${pageContext.request.contextPath}/judge/playerList, cols: [[ {field: playerNo, title: 编号}, {field: playerName, title: 姓名}, {field: teamName, title: 队伍}, {title: 操作, toolbar: #barDemo} ]], page: true }); // 监听行工具条事件 table.on(tool(playerTable), function (obj) { if (obj.event score) { var player obj.data; layer.open({ type: 1, title: 给 player.playerName 打分, content: $(#scoreFormTpl).html(), success: function (layero) { form.render(); // 把playerId塞进隐藏域 layero.find(input[nameplayerId]).val(player.id); } }); } }); form.on(submit(saveScore), function (data) { $.post(${pageContext.request.contextPath}/judge/saveScore, data.field, function (res) { if (res.code 0) { layer.closeAll(); table.reload(playerTable); } else { layer.msg(res.msg); } }); return false; }); }); /script script typetext/html idscoreFormTpl form classlayui-form lay-filterscoreForm stylepadding: 20px; input typehidden nameplayerId div classlayui-form-item label classlayui-form-label综合分/label div classlayui-input-block input typenumber namescore lay-verifyrequired classlayui-input placeholder0-100 /div /div div classlayui-form-item label classlayui-form-label评语/label div classlayui-input-block textarea namecomment classlayui-textarea/textarea /div /div div classlayui-form-item div classlayui-input-block button classlayui-btn lay-submit lay-filtersaveScore提交/button /div /div /form /script后端Controller对应的核心逻辑RestController RequestMapping(/judge) public class ScoreController { Autowired private ScoreService scoreService; GetMapping(/playerList) public Result playerList(RequestParam Integer matchId) { return Result.ok(scoreService.listPlayers(matchId)); } PostMapping(/saveScore) public Result saveScore(RequestParam Integer playerId, RequestParam BigDecimal score, RequestParam(required false) String comment, HttpSession session) { User judge (User) session.getAttribute(loginUser); scoreService.saveScore(judge.getId(), playerId, score, comment); return Result.ok(); } }有几个细节容易被坑LayUI的表格url返回格式必须是{code:0, msg:, count:..., data:[...]}后端Result要按这个结构组装否则表格不渲染form.render()要等弹窗内容进入DOM后再调用否则下拉框、单选框样式出不来前后端交互的数据结构要定义清楚用统一的Result对象返回不要随手塞个Map后面维护会痛苦。4.3 成绩统计与实时排名排名是整个项目里最体现SQL功底的地方。评委完成打分后后端查一次聚合SQL就能把排名算出来SELECT p.id AS playerId, p.player_no AS playerNo, p.player_name AS playerName, p.team_name AS teamName, ROUND(AVG(s.score), 2) AS avgScore FROM player p LEFT JOIN score_record s ON p.id s.player_id AND s.match_id #{matchId} WHERE p.match_id #{matchId} GROUP BY p.id ORDER BY avgScore DESC;如果赛事规则是“去掉一个最高分、去掉一个最低分再取平均”SQL写成这样SELECT p.id AS playerId, p.player_no AS playerNo, p.player_name AS playerName, p.team_name AS teamName, ROUND((SUM(s.score) - MAX(s.score) - MIN(s.score)) / (COUNT(s.score) - 2), 2) AS finalScore FROM player p JOIN score_record s ON p.id s.player_id WHERE s.match_id #{matchId} GROUP BY p.id ORDER BY finalScore DESC;注意去掉最高最低分的前提是COUNT(s.score)至少为3否则会出现除零问题业务层要提前判断。另外如果用LEFT JOIN版本没有评分记录的选手avgScore是NULL排名可以把NULL选手排在最后ORDER BY avgScore IS NULL, avgScore ASC或者干脆在查询条件里把未打分选手排除按业务需求决定即可。排名页面建议用LayUI table正常渲染同时在页面上放一个“刷新排名”按钮配合table.reload效果直观。有实时刷新需求的话加个定时轮询接口就行打分系统这点实时性用轮询完全够没必要上WebSocket。5. 运行与部署阶段最常遇见的坑5.1 Maven依赖下载慢与依赖冲突新课设一做十个里有九个挂在Maven上。最常见的问题是依赖下载失败或者慢到怀疑人生。第一个动作永远是配阿里云仓库在~/.m2/settings.xml的mirrors节点里加mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror配置完别急着重启先执行mvn clean compile看输出确认依赖下载日志里是否出现Downloading from aliyun这就说明镜像生效了。依赖冲突的表现是明明代码编译过了运行时报ClassNotFoundException或NoSuchMethodError。典型case是slf4j多版本并存、javax.servlet-api重复引入。排查手段很直接在IDEA的Maven Helper插件里用Dependency Analyzer搜相关包名看哪些jar把同一个类带进来了用exclusions排除掉。比如排除常见传递依赖dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version exclusions exclusion groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId /exclusion /exclusions /dependency还有一类报错是Could not find artifact ... in central基本上是因为手动删了本地仓库里的半成品jar或者断点下载导致文件损坏。删掉repository对应目录重新mvn install一遍即可。5.2 MySQL 8.x连接配置与安装问题MySQL也是重灾区。我建议直接装8.x新版本在Windows上的安装比5.7省心。安装向导里选Server only一路Next设置root密码时记牢字符集选utf8mb4后面创建数据库时也需要显式指定utf8mb4避免中文乱码。连接串是最容易踩坑的地方jdbc:mysql://localhost:3306/score_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseserverTimezoneAsia/Shanghai必须写否则8.x驱动会报The server time zone valueuseSSLfalse避免证书警告刷屏驱动类名要用com.mysql.cj.jdbc.Driver6.x以上不再是com.mysql.jdbc.Driver。如果你用Docker跑MySQL启动时注意映射端口并设置默认字符集否则从外部连接时编码会乱。本机实践时的常用启动参数大致是docker run -d --name score-mysql -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot \ -e TZAsia/Shanghai \ mysql:8.0进容器后建库CREATE DATABASE score_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这里必须强调utf8mb4MySQL的utf8是残缺的它不支持emoji和部分生僻汉字utf8mb4才是完整的UTF-8编码。那种“中文显示问号”的祖传问题八成就是这个引起的。MySQL连接报错速查报错信息常见原因处理方法The server time zone value...未设置serverTimezoneURL加serverTimezoneAsia/ShanghaiUnknown database score_system没建库或库名写错执行CREATE DATABASE...Public Key Retrieval is not allowed8.x驱动访问caching_sha2_password用户URL加allowPublicKeyRetrievaltrue或把用户改为mysql_native_password中文变成问号连接字符集或建表字符集不对连接串加characterEncodingutf8建库用utf8mb45.3 IDEATomcat运行配置排查SSM项目在IDEA里跑不起来的场景复盘一下九成是下面几个原因没配置Artifact或配置成了war而不是war exploded。建议在Run Configurations里选Tomcat Server Local在Deployment里选中Artifact用war exploded模式运行。这样修改JSP后不需要重启Tomcat调试效率高。Tomcat版本不匹配。Java 8对应Tomcat 8.5或9可以Java 17以上就要考虑兼容性问题。课设环境老老实实JDK 8 Tomcat 8.5/9最稳。web.xml没挂DispatcherServlet或applicationContext.xml没加载。启动后直接500先看控制台日志里的Context初始化有没有成功。JSP访问404。确认视图解析器前缀、后缀和实际目录结构一致确认WEB-INF下页面没有被直接外部访问。数据库没先启动。这个听起来很傻但实际经常发生Tomcat启动了Druid连接池初始化时连不上MySQL抛出异常新手还以为是项目配置错了。排错顺序我习惯这么走先看IDEA的Run日志再开Tomcat的catalina.out把异常栈定位到具体的class和line然后看浏览器开发者工具判断是404还是500是前端加载不了静态资源还是后端接口报错。每一步都能明确缩小范围比瞎改配置快得多。做完这套系统回看最值钱的反而功能本身——打分、排名、权限技术上都没有门槛。真正让人长进的是整个工程从零组装的过程被Maven依赖教育过一遍被MySQL时区教育过一遍被Tomcat的war exploded教育过一遍再去理解Spring Boot那种“什么都是自动配好”的体验才知道它替你省掉了多少事。如果你也在做类似的课设我建议别急着复制粘贴代码先把表结构画清楚再把Maven和SSM整合跑通最后再往上叠页面。顺序对了后面的坑会少一大半。最后再补一句个人体会课设项目可以“老”但思路要“新”——把评分项的配置化、排名的去极值算法这些细节做好答辩时反而比满屏的Spring Boot更有讲头。
返回列表