
每年到这个节点后台和私信里总有一堆人问学长我毕设想做个Java Web项目但不想做图书管理、学生管理系统还有没有别的路子说实话图书管理和学生管理系统不是不能做只是答辩时十个组里有八个做这个评委听到书名就已经失去兴趣了。今天我把自己的一个完整毕业设计项目拆开来讲——基于SpringBoot和Vue的反欺诈平台附带完整的项目源码、SQL脚本和接口文档。这套东西覆盖了SpringBoot、Vue、MyBatis-Plus、MySQL、Knife4j接口文档、JWT鉴权这些Java Web毕设里最常考的技术点而且是带真实业务场景的不是教科书式的CRUD。这篇文章我会把从选题逻辑、技术选型、数据库设计、接口文档到部署踩坑的完整链路都过一遍无论你是想直接拿这套模板做二次开发还是想参考它的功能设计思路都能少走很多弯路。1. 毕设选题的底层逻辑为什么反欺诈比管理系统更容易拿高分1.1 评委想看到的三个点操作系统管理类项目天然缺失先说一个很多同学没想明白的事毕业设计答辩评委到底在评什么我在学校跟评委老师交流过核心就三条工作量够不够技术点覆盖够不够业务流程能不能讲清楚。管理系统类项目最大的问题是业务太浅。拿图书管理系统来说借书、还书、查书用户表加图书表加借阅记录表三张表就能画完ER图加班加点两天就能写完。这种项目不是不能过但想拿优秀基本不可能因为没有任何一个环节能体现设计能力。反欺诈平台就不一样了。它的业务背景天然带冲突性——有正常的业务请求也有恶意的欺诈请求系统要在这两者之间做判断。这个判断的过程就是你展示技术设计的地方是走名单匹配还是走规则打分还是两个都走每种方案有什么取舍这类为什么这么设计的问题在管理系统类项目里根本问不出来。1.2 反欺诈平台的业务闭环恰好卡在Java Web毕设的舒适区很多同学一听反欺诈第一反应是这不得用到机器学习其实是自己吓自己。反欺诈体系分很多层——有基于大数据的团伙欺诈识别有基于行为序列的模型评分但也有非常成熟的轻量级方案名单库、规则引擎、风险事件管理。这些轻量级方案不需要Python、不需要TensorFlow用Java就能完整落地。黑名单表存一批历史欺诈用户规则表配置一些可量化的风控规则比如同一设备短时间内注册超过5次触发告警单笔订单金额超过阈值且收货地址与常用地址不一致风险事件表记录每一次风控判定结果供运营人员人工复核。这套业务闭环做下来前端有页面可展示后端有逻辑可讲解数据库有表关系可画图接口文档有内容可写正好卡在Java Web毕设的能力范围内。既不会因为太简单显得没工作量又不会因为算法太难让答辩下不来台。2. 技术架构与代码组织SpringBoot和Vue是怎么分工协作的2.1 版本选型为什么我固执地守着SpringBoot 2.7技术选型这块我直接给结论再解释原因分层技术选型版本建议说明后端框架SpringBoot2.7.x稳定资料多兼容性最好持久层MyBatis-Plus3.5.x单表CRUD不用写SQL省大量时间数据库MySQL8.0.x主流Navicat直接连鉴权方案JWTjjwt 0.9.1无状态前后端分离标配前端框架Vue2.6.x配合Element UI生态最成熟UI组件库Element UI2.15.x后台管理系统的颜值担当HTTP客户端Axios1.x封装后统一管理请求接口文档Knife4j2.0.9比原生Swagger好看调试方便为什么后端用2.7而不用3.x这是我踩过坑之后的肺腑之言。SpringBoot 3.0起强制要求JDK 17很多老教材里的代码、网上搜到的博客配置在3.x下会直接报错。而大多数高校机房和毕设答辩环境装的还是JDK 8SpringBoot 2.7配JDK 8是最稳的组合。另外SpringBoot 2.7的自动化配置和第三方starter兼容性也更好Knife4j这些文档组件在3.x下需要额外适配与其在环境配置上浪费时间不如把省下来的时间花在业务功能上。2.2 后端分层和前端目录怎么组织让代码看起来专业代码结构这一块直接决定了评委翻你源码时的心情。后端我没用什么花里胡哨的DDD就用了最经典、也最能讲清楚的三层架构加一个common包com.example.fraud ├── controller // 接口层只做参数接收和结果封装 ├── service // 业务逻辑层核心判断都在这 ├── service.impl // service接口实现 ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体类 ├── common // 统一返回体、异常处理、常量 ├── config // 配置类跨域、Knife4j、拦截器 └── utils // JWT工具类、脱敏工具类前端目录同样按职责拆分别把所有组件都堆在App.vue里src ├── api // 所有接口请求统一放这里 ├── assets // 静态资源 ├── components // 公共组件比如表格、弹窗 ├── router // 路由配置含守卫 ├── store // Vuex管理token和用户信息 ├── utils // axios封装等工具 └── views // 页面级组件 ├── login // 登录页 ├── dashboard // 风控总览 ├── blacklist // 黑名单管理 ├── rule // 规则配置 └── event // 风险事件管理这样组织的好处是答辩的时候特别好讲前端一个文件夹对应一个功能模块后端按请求链路controller到mapper一层层往里找评委顺着目录结构就能走通一条完整的功能路径。不用你多解释代码自己会说话。2.3 核心业务链路一次风控请求是怎么流转的讲完目录结构必须把系统的主动脉讲清楚——也就是前端的风险识别按钮点下去之后后端到底做了什么。这也是答辩时被问概率最高的地方。前端提交一笔订单数据到/api/risk/evaluate接口后端Controller接收后交给RiskEvaluateService这个Service当前置处理完会把请求参数转成一份标准化的风控上下文里面包含用户ID、手机号、IP地址、设备编号、订单金额、收货地址等信息。接下来我设计了一个三段式处理流程名单引擎先行拿风控上下文里的用户ID、手机号、设备编号去黑名单表做精确匹配命中直接返回拒绝不再往下执行。白名单命中的话则跳过后续规则。规则引擎打分每组规则有一个风险分值比如同设备号当日累计注册次数大于5次加30分订单金额大于5000元且收货地址与常用地址不一致加20分。系统遍历所有启用状态的规则逐条匹配累加风险总分。策略引擎决策根据预先配置的阈值比如总分大于等于60判定为高风险30到60分判定为可疑低于30分判定为通过。判定结果连同命中的规则明细一起写入风险事件表页面端实时可见。这套链路的精妙之处在于每一段都可以单独拿出来讲设计思路而实操上又完全用Java能实现。我在规则匹配时为了避免每次请求都全表扫描把规则表加了一层Redis缓存启动应用时加载一次到内存规则变更时再刷新缓存。对毕设来说这已经是非常亮眼的优化点了。3. SQL脚本设计这套建表语句背后的风控字段逻辑3.1 核心表结构每一张表解决一个什么业务问题数据库设计是那个所谓SQL脚本文件里最值钱的部分。整个系统我总共设计了6张核心表不多不少既能覆盖业务场景又不至于让建表工作量失控。第一张是sys_user管理员账户表字段包括id、username、passwordBCrypt加密存储、real_name、status、create_time。这里注意密码一定不能用明文这是评委喜欢挑刺的点。第二张是fraud_blacklist黑名单表字段除了基础id外核心是identity_type和identity_value的组合比如identity_type取值为MOBILE、DEVICE、ID_CARD、ACCOUNT四种identity_value存具体的手机号、设备编号、身份证号或账号。为什么这么设计因为同一个黑名单库要支持按不同维度去查询用单列存类型、单列存值的方案最灵活比给每个维度建一张表清爽得多新增一种名单维度时都不用改表结构加个枚举值就行。第三张是fraud_rule规则表字段包括rule_code、rule_name、rule_type、condition_expression、risk_score、status。这里的condition_expression我建议存类似device_register_count5 ip_risk_levelhigh这样的可读表达式前端页面上用JSON编辑器维护后端拿到表达式后再解析执行。对毕设来说不用真的去做一个解释器直接用简单的字符串匹配加表达式拆分就能顶住。第四张是fraud_strategy策略表字段主要是strategy_name、threshold_high、threshold_low、action_code。它和规则表是一对多的关系一个策略下面挂多条规则。这样分组的好处是运营人员可以配置普通交易策略和大额交易策略不同策略挂不同的规则组合。第五张是fraud_risk_event风险事件表这是整个系统的日志核心。字段包括event_no事件编号由时间戳加随机数生成、user_id、identity_type、identity_value、order_no、transaction_amount、risk_score、risk_level、rule_detail、status、create_time。rule_detail字段我用JSON格式存储命中的规则明细方便前端直接展示。第六张是sys_operation_log操作日志表记录管理员的登录、名单新增、规则修改等敏感操作字段包含operator、operation_type、operation_content、operation_ip、create_time。3.2 索引设计、初始化数据和SQL脚本的导入姿势建表语句写完只是第一步真正体现数据库功底的是索引设计和初始化数据。索引方面我对fraud_risk_event表的identity_type加identity_value做了联合索引因为查询风险事件时最常用的过滤条件就是查某个手机号的所有历史风险记录。对fraud_blacklist表的identity_type和identity_value也做了联合索引这是名单匹配的查询路径走索引和不走索引在高并发场景下性能能差一个数量级。外键我没建业务系统里外键会拖累写入性能表间关系靠代码逻辑去维护这也是现在的主流做法答辩时可以理直气壮地解释。初始化数据一定要精心准备。默认管理员账号是admin密码是admin123存储在SQL脚本里的是BCrypt加密后的串这样用户导完数据库就能直接登录不用再去查加密规则。黑白名单表里各插入几条演示数据规则表里插入几条能出效果的风控规则比如同设备当日注册次数超过5次风险分加30分IP命中高危地区风险分加40分。为什么非得放数据因为答辩现场评委一定会让你演示如果页面上一片空白你要花十分钟现场造数据效果非常尴尬。而如果一进去就有数据、就有风险事件可看演示的流畅度完全不是一个级别。SQL脚本导入这块我见过太多人在答辩前折腾数据库导入翻车。这里给两个稳妥方案第一是命令行的source方式在MySQL的bin目录下执行mysql -uroot -pfraud_data.sql或者进入mysql客户端后执行source /path/to/fraud_data.sql第二是用可视化工具的运行SQL文件功能Navicat和数据Grip都是右键数据库、选择运行SQL文件选中脚本后等它跑完。导入完成后一定用show tables;确认一下8张表都在再查一下sys_user表有没有数据确认没问题再关工具。4. 接口文档与Knife4j把调试页面变成答辩加分项4.1 为什么毕设里要单独配接口文档以及怎么接入接口文档这东西很多同学的毕设里是完全不存在的——前端要调接口了直接去后端代码里翻RequestMapping的注解。毕设阶段这样做确实能跑通但它暴露的问题在答辩时会被放大当你跟评委讲这个项目是前后端分离架构时评委必然要问一句那你们的接口是怎么定义的前端怎么知道后端给什么数据所以这套项目里我集成了Knife4j也就是增强版的Swagger UI。接入步骤很简单在pom.xml里加依赖application.yml里加配置然后启动项目访问/doc.html就能看到接口文档页面。我在配置时特意加了如下配置让Knife4j认识统一的接口前缀Bean public Docket docket() { return new Docket(DocumentationType.OAS_30) .groupName(反欺诈平台) .select() .apis(RequestHandlerSelectors.basePackage(com.example.fraud.controller)) .paths(PathSelectors.any()) .build() .securitySchemes(Collections.singletonList(new ApiKey(Authorization, Authorization, header))) .globalRequestParameters(globalRequestParameters()); }这里有个大坑必须单独拎出来说如果你在项目里配置了统一的servlet前缀比如server.servlet.context-path: /fraud或者给Controller类加了RequestMapping(/api)前缀那么Knife4j默认扫描到的接口路径和实际访问路径可能对不上导致调试页面发起请求时404。解决办法是在Docket里配置pathMapping把前缀补进去。我自己的做法是后端所有接口都用/api开头Knife4j页面上看到的地址就统一带着完整前缀调试时直接用不会再出现点击发送之后一堆人排队等你排查路径的情况。4.2 统一返回体和典型接口长什么样接口文档不只是能看到接口列表这么简单规范化的统一返回体更重要。我的项目里所有接口都返回同一个结构{ code: 200, message: 操作成功, data: { } }封装成一个泛型类ResultTcode等于200表示成功非200表示业务异常message带出提示文案。这样前端axios拦截器只需要判断一次code不用每个接口单独处理成功失败逻辑。拿最核心的风控评估接口举例接口路径是POST /api/risk/evaluate请求参数是一个订单和用户信息的复合JSON返回结果除了基本风险等级外还会带一个hitRules数组里面是命中规则的编码、名称和风险描述。Knife4j的优势就是可以直接在文档页面上发请求我每次演示都直接在/doc.html里选这个接口把参数填好点击发送几秒钟就能看到完整的风控判定流程非常直观。5. 前端实现与联调实战从脚手架搭建到页面路由守卫5.1 项目创建、axios封装和环境变量这些基本功前端搭建我用的是Vue CLI为什么要用Vue CLI而不是Vite因为Vue CLI是基于webpack的网上的教程最多、报错最好查Vite虽然快但偶发的兼容性问题对新手不友好。vue create fraud-front之后第一件事就是安装Element UI和axios依赖。axios封装是前端项目的魂。我把它拆成四个关键点第一baseURL写环境变量VUE_APP_BASE_URL开发环境指向http://localhost:8080/api生产环境打包后能单独改第二请求拦截器里从Vuex中取token塞进请求头的Authorization字段第三响应拦截器里统一解包ResultT结构如果code不等于200就弹出轻提示第四判断HTTP状态码401时自动跳转登录页。这套封装的好处是后续每次调用接口前端里只写业务逻辑不用重复处理这些公共逻辑。5.2 路由守卫、登录态和跨域联调路由守卫是前后端分离项目里鉴权的前端配合部分。我在router/index.js里给每个页面配置了meta字段然后在全局前置守卫里做判断没有token时除了登录页之外一律重定向到/login有token时如果去登录页则重定向到首页。这样后端JWT校验和前端路由守卫双保险用户未登录时根本看不到任何业务页面。前后端联调时最常遇到的是跨域问题。因为我前端开发服务器跑在8081端口后端跑在8080端口浏览器默认会拦截跨域请求。我的处理方式是后端加一个全局CORS配置类允许8081端口跨域访问Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }有同学会问为什么不用前端devServer的proxy代理解决两种方案都能通但proxy方案只在开发环境生效后端CORS方案前后端都适用答辩演示时用后端CORS方案你就不需要额外讲解前端代理配置那一套少一个变量就少一份翻车概率。6. 本地部署全流程与实战踩坑记录6.1 从环境准备到前后端同时跑起来拿到源码后完整部署的顺序我建议按下面的步骤来每步验证通过再继续安装JDK 8、Maven 3.6、MySQL 8.0、Node 14。这四个环境缺一不可Node版本不要高于16Vue CLI项目在Node 18上偶尔会出现OpenSSL兼容报错。建库并导入SQL脚本用Navicat新建一个fraud_platform数据库字符集选utf8mb4然后运行SQL脚本文件确认6张表创建成功。修改后端配置打开application.yml把数据库用户名、密码改成你自己的本地账号密码端口保持8080不动。启动后端在项目根目录执行mvn spring-boot:run等待看到Started FraudApplication字样再访问http://localhost:8080/doc.html确认Knife4j页面能打开。启动前端在front目录执行npm install然后npm run serve访问http://localhost:8081用admin/admin123登录。联调验证登录后进黑名单页面点新增按钮录入一条数据刷新后确认数据还在说明前后端连接正常。6.2 四个高频报错的排查链路这个项目从零开发到稳定运行我记录过十几个报错挑四个最典型、最容易被搜到的分享第一个是Knife4j页面白屏或接口列表为空。排查思路不是先去改pom版本而是先确认三件事启动日志里有没有报swagger相关的异常、Docket扫描包路径是否写对、controller类上有没有加Api注解。我遇到的情况是扫描包路径写成了com.example.controller但实际Controller在com.example.fraud.controller包里导致扫描不到任何接口。第二个是数据库表字段映射不上。MyBatis-Plus默认开启驼峰映射但如果实体类字段名和表字段名命名风格不一致比如数据库是identity_type实体类是identityType需要确认application.yml里配置了map-underscore-to-camel-case: true。这个配置缺了会莫名其妙出现字段找不到或者全是null的情况。第三个是JWT过滤器导致的所有请求都被拦了。我最初写的拦截器对所有路径都生效结果登录接口自己也被拦截了返回401。排查之后在拦截器注册配置里加了excludePathPatterns把/api/auth/login、/doc.html、/webjars/**这些路径放行。注意/doc.html必须放行否则接口文档页面也会被鉴权拦截。第四个是前端npm install报错node-sass安装失败。这个项目里Element UI本身不需要node-sass如果你的模板中用了scss就得注意尽量选择Vue CLI默认的sass-loader版本或者改用dart-sass。遇到node-sass相关报错时最省事的方法是把package.json里和sass相关的包全部删掉重新npm install然后用Element UI自带的普通css项目照样能跑。6.3 数据安全性处理密码加密与敏感信息脱敏最后还有一个常见讲解点就是数据安全。管理员密码我用BCrypt加密存储登录时通过BCryptPasswordEncoder.matches()方法校验。黑名单里的手机号、身份证号在前端展示时要做脱敏处理手机号显示成138****1234身份证号显示成110***********1234。这不仅是为了演示好看更是为了让评委看到你有安全合规意识。数据库SQL脚本里如果出现了明文敏感数据答辩时被问到就很被动了。我在实际做这个项目时还有一个体会与其把时间花在把界面做得花里胡哨上不如把接口文档里的参数说明写清楚、把SQL脚本里的初始化数据准备到位。评委真正看的不是你的按钮颜色好不好看而是你能不能把一条请求从浏览器点进来到数据库查出来走的完整路径讲明白。这套SpringBoot加Vue的反欺诈平台源码之所以能帮你撑住场面核心就在于每个环节都能讲出设计依据。拿到源码之后建议先对照SQL脚本把表关系画一遍再跟着接口文档把核心流程调通一遍最后再看前端页面怎么对接。三轮下来答辩时不管老师往哪个方向问你都能接得住。