ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java+SSM+Flask混合架构搭建数学竞赛网站全流程解析

Java+SSM+Flask混合架构搭建数学竞赛网站全流程解析 数学竞赛类网站这套题目在毕业设计里真的算常青树了。每年都有大量学生做类似选题但大多数版本都停留在“能跑就行”的状态代码结构、业务逻辑、部署方式都存在不少隐患。这篇文章我结合自己带过的项目经验把基于JavaSSMFlask混合架构的数学竞赛网站从设计思路、核心代码、部署调试到排坑过程完整拆开讲一遍适合正在做毕设、课设或者想自己搭一个数学练习与竞赛平台的开发者参考。1. 项目整体设计与技术选型思路1.1 为什么用JavaSSMFlask这种混合架构拿到“数学竞赛网站”这个题目第一步要做的不是急着写代码而是先把技术架构定下来。目前市面上的方案大致分三类纯JavaSSM或SpringBoot、纯PythonDjango或Flask、前后端分离VueSpringBoot。而标题里这个组合——JavaSSMFlask看起来有点“混搭”实际却是很多高校毕业设计里非常实用的方案背后其实有很现实的考虑。SSMSpringSpringMVCMyBatis是Java后端里非常经典的组合它稳、资料多、面试也常问。用SSM来做网站的主体业务比如用户登录、试题管理、竞赛发布、成绩记录这些核心模块是非常稳妥的。而Flask作为Python端的轻量框架非常适合做两件事一是实现一些需要快速开发的辅助接口比如智能组卷、成绩统计分析、爬虫抓取竞赛资讯等二是为Java端提供一个可调用的Python服务方便展示“多种技术栈协作”的能力这在毕业设计的答辩中是一个很加分的亮点。当然也有很多人会问为什么不直接用SpringBoot代替SSM说实话如果是从零开始自己做项目SpringBoot确实更省事。但很多高校的课程体系和毕设大纲还停留在SSM阶段题目要求里明确写了“基于SSM”所以标题里保留SSM是有原因的。再说了SSM虽然配置繁琐但恰恰是这种繁琐能逼着你把Spring的IOC、AOP、MyBatis的映射机制这些东西真正弄明白而不是像用SpringBoot那样把一切都自动屏蔽掉。1.2 前端与数据交互的设计考量前端部分大多数数学竞赛网站用的都是服务端渲染加少量Ajax的方式。用Thymeleaf或者JSP来渲染页面配合jQuery和Bootstrap做交互。这种方案的好处是开发速度快、对SEO友好、调试也直观特别适合课设毕设这种时间紧、任务重的场景。数据交互方面Java端和Flask端的通信我推荐两种方式方式一Java端通过HTTP Client比如RestTemplate或OkHttp调用Flask的RESTful接口这种方式逻辑清晰两端完全解耦。方式二共用同一个MySQL数据库Java负责写Flask负责读然后通过Redis做缓存同步。这种方式少了一层HTTP调用性能更好一点但要注意数据一致性问题。我自己在做类似项目时更喜欢方式一。原因很简单Java和Flask各自维护独立的业务边界接口文档写清楚联调的时候不容易出“这个字段谁改的”这种扯皮问题。而且答辩时展示两端独立部署、通过接口通信比“共用一个库”听起来更有技术含量。1.3 核心功能模块拆解一个完整的数学竞赛网站至少要包含以下模块这也是评阅老师重点看的部分。我列一个表格方便对照模块名称核心功能技术要点用户模块注册、登录、个人信息维护Session管理、密码加密、验证码试题模块试题录入、分类、检索、在线答题批量导入、分页查询、题目类型区分竞赛模块发布竞赛、在线报名、模拟竞赛时间控制、竞赛状态机、防作弊成绩模块自动判分、成绩排名、统计分析判定规则、成绩可视化资讯模块竞赛公告、数学文章展示富文本发布、分类展示管理后台用户管理、试题审核、数据统计权限控制、图表展示这六大模块基本覆盖了“数学竞赛网站”“数学练习网站”“数学学习资源”这些热词的全部需求。后面我会重点挑几个容易出问题的模块详细讲代码实现和踩坑过程。2. 核心业务模块详解与代码实现2.1 用户登录与权限控制的正确姿势用户模块看起来简单但登录态管理和权限控制是做网站最容易出安全隐患的地方。很多同学的代码里登录就是查一下用户名密码对不对然后把用户ID存在session里就完事了。这种做法在课设答辩中勉强能过但实际上漏洞挺多的。我建议的做法是分三层第一层表单提交时用JS做一次基础校验比如用户名非空、密码长度限制这层是为了用户体验。第二层后端Controller里再做一次严格的参数校验防SQL注入和XSS攻击。比如MyBatis里一定要用#{}而不是${}因为${}是字符串拼接很容易被注入。第三层密码不能明文存数据库。至少要用MD5加盐或者直接用BCrypt。我通常用Spring自带的BCryptPasswordEncoder每次校验时把用户输入的密码和数据库里的哈希值做比对。// 用户登录的Service层核心逻辑 public User login(String username, String password) { User user userMapper.findByUsername(username); if (user null) { throw new BusinessException(用户名不存在); } // BCrypt校验password是用户输入的明文user.getPassword()是库里的哈希值 if (!bcryptPasswordEncoder.matches(password, user.getPassword())) { throw new BusinessException(密码错误); } // 更新最后登录时间 userMapper.updateLastLoginTime(user.getId()); return user; }权限控制方面用一个拦截器就够了。定义一个LoginInterceptor重写preHandle方法检查session里有没有user信息。没有就重定向到登录页。然后Admin相关的请求再检查这个用户的角色字段是不是“admin”。注意角色判断不能只在前端隐藏按钮后端接口必须也校验否则别人直接构造请求就能删数据了。2.2 试题管理从批量录入到智能组卷试题管理是数学竞赛网站的核心也是工作量最大的部分。一道题要有题干、选项如果是选择题、正确答案、解析、难度等级、知识点标签。在数据库设计中我建议把题目基础信息放一张表题目和知识点的关联关系放另一张表采用多对多的设计。这样后面做筛选、分组、统计都非常方便。批量录入这块很多同学是一个个在后台手输太累了。比较实用的做法是支持Excel导入。我用的是Apache POI前端上传xlsx文件后端解析每一行校验字段完整性后逐条插入数据库。// 使用POI读取Excel的简化代码 Workbook workbook WorkbookFactory.create(inputStream); Sheet sheet workbook.getSheetAt(0); for (Row row : sheet) { if (row.getRowNum() 0) { continue; // 跳过表头 } Question question new Question(); question.setContent(row.getCell(0).getStringCellValue()); question.setType(row.getCell(1).getStringCellValue()); question.setAnswer(row.getCell(2).getStringCellValue()); question.setDifficulty(row.getCell(3).getNumericCellValue()); questionService.addQuestion(question); }这里有个坑POI读取单元格时数字类型的cell要用getNumericCellValue字符串类型要用getStringCellValue如果不提前判断getCellType()很容易抛异常。我在这上面吃过亏后来都写了统一的解析工具类把所有cell值都先转成字符串再处理。智能组卷是一个加分功能。简单版本的做法是根据用户选择的知识点、难度分布和题量先从数据库里把符合条件的题目全部查出来然后按比例随机抽取。稍微智能一点可以给每道题设置一个权重值比如最近被抽中次数多的题目权重降低实现“不重复刷题”的学习体验。这个逻辑用SQL加Java配合很容易实现。2.3 竞赛流程设计报名、开赛、交卷、判分数学竞赛网站里的“竞赛”功能核心难点在于时间控制和状态流转。一场竞赛的状态我一般设计为四个未开始、进行中、已结束、已发布成绩。整个流程是这样的管理员创建竞赛设置开始时间、结束时间、参赛限制。用户浏览竞赛列表在“未开始”状态下可以报名。到了开始时间竞赛状态自动变为“进行中”用户可以进入答题页面。用户交卷或时间截止自动交卷进入判分环节。所有参赛者成绩统计完成后管理员发布成绩用户可以看到排名。状态判断有个细节不能只靠前端按钮控制显示后端每次请求竞赛详情时都要根据当前时间动态判断状态。因为用户完全可以绕过前端直接调接口获取题目。所以我的做法是在Service层写一个getCompetitionStatus(Competition competition)方法每次请求都实时计算保证业务逻辑安全。交卷判分这块如果题目全是选择题和判断题那判分就很简单——遍历用户的答案跟正确答案比对就行。但数学竞赛往往有填空题和解答题。填空题要注意答案格式的统一比如“3/4”和“0.75”本质等价但字符串比对认为是不同的。我的处理办法是填空题设置多个可接受的答案或者用正则做归一化比较。解答题则需要人工阅卷我的做法是系统先自动判选择和填空把得分记录下来然后管理员在后台对解答题进行评分最终分数等于两部分之和。2.4 Flask端辅助功能成绩分析与资讯接口Flask在整个项目里的角色是一个Python辅助服务。我主要让它干三件事成绩数据分析、竞赛资讯采集、模拟成绩预测。成绩分析这块Flask通过HTTP接口接收Java端传过来的成绩数据JSON格式然后用Pandas做统计计算比如平均分、最高分、分数段分布再配合Matplotlib生成一张图片返回给Java端Java端直接在前端页面上展示。这样图表绘制完全不用Java代码省了很多麻烦。from flask import Flask, request, jsonify import pandas as pd import matplotlib.pyplot as plt import io import base64 app Flask(__name__) app.route(/api/analyze, methods[POST]) def analyze(): data request.get_json() # Java端传过来的成绩列表 df pd.DataFrame(data) avg_score df[score].mean() max_score df[score].max() # 生成分数分布图转base64返回 plt.hist(df[score], bins10) buf io.BytesIO() plt.savefig(buf, formatpng) buf.seek(0) img_base64 base64.b64encode(buf.read()).decode(utf-8) return jsonify({ avg: avg_score, max: max_score, img: img_base64, total: len(df) }) if __name__ __main__: app.run(host0.0.0.0, port5001)Java端用RestTemplate调用这个接口的代码也很简单构造一个MultiValueMap或JSON对象POST过去拿到返回的JSON解析后把图片的base64字符串直接放进前端的img标签里。实测下来这个链路很稳定数据量不大的情况下响应速度也很快。3. 数据库设计与关键配置文件解读3.1 表结构设计哪些表是必须的怎么建最合理数据库设计是整个项目的地基。很多人的表结构是随用随建到后期发现缺字段就加字段类型不统一关联查询巨慢。我这里直接给出一个比较完整的设计方案建表的SQL你可以直接参考。核心表有七张用户表、角色表、题目表、知识点表、题目知识点关联表、竞赛表、竞赛成绩表。如果要做新闻资讯再加一张文章表。用户表字段id, username, password, salt, email, avatar, role, status, create_time题目表字段id, content, options, answer, analysis, type, difficulty, subject, create_time竞赛表字段id, title, description, start_time, end_time, duration, question_ids, status, create_time这里特别说一下question_ids字段。竞赛和题目是多对多的关系理论上应该建一张关联表但竞赛场景比较特殊——管理员组卷后该场竞赛的题目集合就固定了直接用逗号分隔的ID字符串存起来取题的时候split(,)一下就行。好处是查询快、理解简单坏处是不太好做统计比如“哪道题被哪些竞赛用过”。看项目复杂度自行取舍课设层面我建议直接用字符串存够用且省事。竞赛成绩表字段id, competition_id, user_id, score, total_score, answer_detail, submit_time, rank, statusanswer_detail是个JSON字符串存的是用户每道题的作答详情比如[{question_id: 1, user_answer: C, is_correct: true}]。这样做的好处是成绩复核、人工批改的时候都能还原当时用户的作答情况不用再去联查原始表。3.2 SSM框架配置Spring、SpringMVC、MyBatis的整合细节SSM的配置主要有三个文件spring-context.xml、spring-mvc.xml、mybatis-config.xml或直接通过application.properties。这里我不贴完整配置重点说几个容易被忽视但影响巨大的细节。第一Spring容器和SpringMVC容器要分清。spring-context.xml负责扫描Service、Daospring-mvc.xml只扫描Controller。如果扫描范围重叠会出现事务失效的问题。具体表现就是Service层没加Transactional都正常加了反而报错或者不生效。第二MyBatis的驼峰命名映射一定要开启。数据库字段都是下划线风格比如create_timeJava实体类都是驼峰风格createTime如果不开启map-underscore-to-camel-case: true你查出来的结果全是null这个问题能卡死人。第三数据库连接池用Druid别用默认的。Druid自带监控页面在答辩的时候打开监控页展示SQL执行次数和耗时是很直观的加分项。配置上注意设置initialSize和maxActive别全部用默认值。!-- Druid数据源配置关键参数 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/math_contest?useSSLfalseamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword valueroot/ property nameinitialSize value5/ property namemaxActive value20/ property nameminIdle value5/ property namemaxWait value60000/ /beanJDBC连接串里的characterEncodingutf8非常重要不加大概率会中文乱码。你查出来的题目内容全是问号排查一圈发现是连接串没加编码参数这种低级错误太常见了。3.3 Flask端配置和跨域问题处理Flask端如果要被Java端调用要注意两个问题一个是端口别跟Java的Tomcat冲突Java一般在8080Flask我习惯用5001另一个就是跨域问题。虽然Java后端调用Flask属于服务端到服务端的HTTP通信不存在浏览器跨域限制但如果哪天你的前端页面直接调Flask接口就会遇到跨域。提前把跨域配置写上去避免后面临时抱佛脚。from flask_cors import CORS CORS(app, resources{r/api/*: {origins: *}})还有一点Flask端的接口返回值统一用JSON格式并且在业务异常时也要返回JSON不要让Flask默认返回HTML错误页。给Flask写一个全局异常处理器捕获所有异常后统一返回{code: 500, msg: 服务器内部错误}。4. 项目部署与调试过程中的经验教训4.1 本地环境搭建JDK、Maven、Tomcat、MySQL一步步来环境搭建是很多新手第一个拦路虎。我见过太多人在装环境这一步卡了三天。实际上按顺序操作没那么复杂。JDK装1.8就行毕设项目不需要太高版本太高版本反而可能跟老项目的编译配置不兼容。环境变量配JAVA_HOME和PATH装完在命令行输入java -version验证。Maven解压后配置MAVEN_HOME关键是要换阿里云镜像仓库否则下载依赖能等到天荒地老。Tomcat解压版就行用IDE自带的也没问题。MySQL安装时要注意设置root密码后默认的认证方式如果是caching_sha2_password老版本的JDBC驱动连接会报错。解决办法是在MySQL命令行里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;这一步不做的话你会在Tomcat启动时报Public Key Retrieval is not allowed错误或者干脆连不上数据库。4.2 数据库初始化脚本的编写规范项目交付时数据库脚本是必须的。很多人直接Navicat可视化建库建表最后导出一份SQL交差。这没问题但有两个地方要检查。一是SQL里不要带DROP DATABASE这种破坏性语句评阅老师或者你自己后面想演示的时候误执行一下数据全没了。二是建表语句最好加上IF NOT EXISTS。另外强烈建议写一份data.sql初始化数据脚本里面放几道例题、一个测试用户、一场模拟竞赛。这样别人拿到项目导入后不用自己录入就能直接看到效果。数据的初始化和演示效果直接相关这一份脚本的价值比想象中大。4.3 修改配置文件的小技巧和注意点项目里配置文件包括jdbc.properties数据库连接、log4j.properties日志、spring-mvc.xml静态资源放行、视图解析器等。改配置最容易出的问题有三个属性文件里的符号要转义成amp;上面提过。IP地址或端口改了但前端JS里的Ajax请求地址还是localhost。文件编码问题properties文件默认ISO-8859-1直接写中文注释会导致乱码。尽管不影响运行但看着难受建议把IDE的properties文件默认编码改成UTF-8。日志这块建议把log4j.properties里com.xxx.mapper这一级的日志级别设成DEBUG这样MyBatis的执行日志会打出来你能看到SQL是怎么拼的排查问题非常有用。等项目上线了再调回INFO。4.4 前后端联调我用过的几个快速调试技巧联调阶段我积累了几个特别实用的技巧分享给你们。第一Chrome开发者工具的Network面板是上帝视角所有请求的状态码、耗时、参数一目了然。看到一个请求404先别急着改代码看看URL是不是打错了、Controller里的RequestMapping路径是不是没配对。第二用Postman单独测试后端接口。在写页面之前先把所有接口在Postman里跑通确认返回的数据结构再让前端对接。这样出了问题能快速定位是后端还是前端。第三Java端调用Flask接口如果报连接超时优先检查Flask服务是否启动、端口是否正确、防火墙是否拦截。命令行curl -X POST http://localhost:5001/api/analyze -d {}测一下就知道问题在哪。5. 常见问题与排查技巧实录根据我实际看过和经历过的情况整理了一份高频问题速查表每一个都是真实发生过的。问题现象根本原因解决方案登录时输入正确密码提示“密码错误”密码加密方式不一致比如注册用了BCrypt、登录用MD5统一加密算法保证注册和登录走同一套用户头像图片上传后无法显示上传路径是相对路径前端访问的基础路径不对将上传目录映射成虚拟路径或用绝对路径拼接URL竞赛到了开始时间仍显示未开始状态判断用的是管理员设置的静态状态没有按当前时间实时计算每次请求时用LocalDateTime.now()对比开始时间批量导入Excel时报IllegalStateExceptionPOI读取cell类型没有做判断统一写单元格值转换方法先判类型再取值前端Ajax获取数据后页面空白后端返回的JSON字段名和前端取的字段名不一致统一使用驼峰命名且前后端约定好字段名Flask端接口偶发CORS报错前端跨域调Flask接口且未配置CORS用flask-cors全局开启指定允许的来源部署后MyBatis查询字段全是null忘了开启下划线转驼峰映射mybatis配置中开启map-underscore-to-camel-case中文乱码页面全是问号连接串没加characterEncodingutf8或数据库编码不对检查JDBC URL、数据库建库字符集、页面编码统一UTF-8Tomcat部署war包后访问报404项目访问路径不对没加项目名URL访问前加/项目名或者把war包名设为ROOT除了表格里的我再补充几个比较隐蔽的坑。第一个是并发访问session的问题。有些同学在Controller里直接request.getSession().setAttribute(user, user)然后在拦截器里取。如果同时开两个浏览器标签页登录不同账号后登录的会把先登录的顶掉。解决办法是Session里存用户ID而不是整个用户对象需要用户信息时再查库。这样即使被顶掉也只是多查一次数据库逻辑上不会串。第二个是竞赛答题页面刷新时倒计时重置的问题。这个特别容易出问题。用户进入答题页面后如果按F5刷新页面重新加载如果倒计时是JS的setInterval从零开始算就相当于时间重置了用户可以无限刷新延长答题时间。正确做法是后端在用户进入答题状态时记录一个开始时间戳前端每次获取时间都用“截止时间减去当前服务器时间”来计算剩余时间。刷新页面只是重新拿一下剩余时间而已不会占便宜。第三个是文件上传的安全问题。数学竞赛网站里用户头像上传功能看起来很普通但如果没做文件类型校验恶意用户上传一个JSP或PHP文件然后拼一个路径去访问就能getshell。哪怕只是做个课设也应该对上传文件后缀做白名单校验别用黑名单。同时重命名文件为随机字符串不要保留用户原始文件名。6. 从课设到答辩项目打磨与展示的经验分享项目做完代码部分离交付还有两个重要的收尾工作文档撰写和答辩准备。这部分往往被低估但它直接影响最终成绩。LW也就是毕业论文或设计说明书的结构一般是绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结。写的时候技术介绍不要大段抄书要结合你项目里的场景写。需求分析要用用例图系统设计要有E-R图这些我建议用ProcessOn先梳理思路再放到文档里。代码的核心逻辑截图加简短说明就可以了关键是要展示你的思考过程比如“为什么选MySQL不选Oracle”“为什么组卷用权重随机算法”。调试文档DD我习惯写成带问题痕迹的记录而不是一个纯命令清单。比如某一天我遇到了什么报错、排查思路是什么、最后怎么解决的。这种文档面试或评阅时特别好用因为看得出你真的是在Debug而不是照着教程敲。答辩前把项目的核心亮点提炼成三句话。比如“我的系统基于SSM框架构建主站为Java实现同时引入Python Flask作为数据分析微服务实现了跨语言架构的协作”。这句话能背下来就赢了。最后再分享一个实操中的小技巧在做完项目后把整套环境的启动步骤写成一个README.md文档放在项目根目录。里面写清楚从数据库初始化到Tomcat启动、从Flask服务启动到浏览器访问的每一个步骤。这个文档既是给别人看的也是给以后的你自己看的。等过了一个月再回头看项目你会发现没有这份文档连你自己都忘了该怎么启动。数学竞赛网站这类项目技术点不算深但胜在业务清晰、模块完整是对SSM框架、数据库设计、前后端交互能力的一次全面检验。只要把用户、试题、竞赛、成绩这四个核心模块做扎实再点缀一点Flask辅助功能和优秀的文档这个项目就能成为一份非常拿得出手的毕设作品。做项目的过程中踩的那些坑调整的那些细节其实比最后的分数更有价值工作后面试聊起这些经历才是真正能打动人的东西。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表