简介:面向高校毕业设计、课程设计与期末大作业场景,这是一份基于SSM(Spring+SpringMVC+MyBatis)框架的校园反诈骗微信小程序完整源码案例,后端使用Java实现服务接口,前端包含微信小程序与Vue管理页面,可快速搭建防骗资讯、实时预警、用户反馈、互动社区等核心功能模块。压缩包共897个文件,涵盖112个java源码、104个js脚本、97个vue组件、238个png图片资源、21个wxml页面及2个sql数据库脚本,整体约40.26MB,并附带1-install.bat等安装运行脚本,便于直接部署调试。资源已有164人学习,特别适宜希望参考SSM整合流程、小程序与后端API交互方式及反诈骗业务数据表设计的开发者,可作为毕业设计参考、期末大作业原型或学习用完整项目模板。
1. 校园反诈骗微信小程序的设计与开发:为什么后端也该早点介入
校园反诈骗微信小程序的项目难点,往往不在前端页面,而在后端能不能把“学生举报—预警推送—管理端核查—效果统计”这条闭环串起来。只靠静态页面和本地数据,小程序上线后既没法知道哪些学院发案高,也没法判断宣传内容有没有被读完。SSM 后端在这里承担的不只是增删改查,还有登录身份、举报流转和管理端统计。下面按一个可交付的案例来拆:工程骨架怎么切、反诈业务表怎么设计、code 换 token 怎么落、举报和文章接口怎么约定、管理端统计怎么做,最后给出本地联调和改源码的验证顺序。适合正在做毕业设计或接手校园服务端项目的开发同学。
2. 用 SSM 搭校园反诈小程序的后端骨架:工程结构、数据表和登录鉴权
SSM 是老组合,但在校园这类交付场景里依然常见:前端上传到 Nginx,后端打 war 包丢进 Tomcat,数据库用 MySQL,新同学接手也容易看懂。很多团队问要不要直接上 Spring Boot,如果项目允许,用 Boot 更省事;但如果你拿到的案例是 SSM,还是先按 SSM 的约定把 web.xml、spring-mvc.xml、mybatis-config.xml 理清楚,后面改源码才不慌。
2.1 先切好包名和 Maven 依赖:SSM 的最小可行性配置
项目名可以叫 campus-antifraud,基础包用 com.campus.antifraud,下面按 controller、service、mapper、pojo、interceptor、config 分包。SSM 与 Spring Boot 最大的不同是web.xml少了会直接启动报错,依赖也建议在 pom 里锁住版本。以下是一份足够跑通的依赖片段:
<properties> <spring.version>5.3.23</spring.version> <mybatis.version>3.5.10</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.31</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> </dependencies>这里 spring-webmvc 和 spring-jdbc 版本必须一致,否则容器装配时会出现 NoSuchMethodError。mybatis-spring 负责把 SqlSessionFactory 交给 Spring 托管,Mapper 接口扫描后可以直接注入。fastjson 用来处理小程序提交的 JSON 和微信接口响应,也可以换成 Jackson,但要注意@RequestBody序列化配置。实际开发时还要引入javax.servlet-api(scope 为 provided)和 commons-fileupload 处理图片上传。框架版本不必追求最新,校园反诈这种系统主要看重稳定。
2.2 反诈业务需要哪几张表:用户、举报、文章和公告
SSM 项目里表结构写好后,后端接口基本定了一半。校园反诈骗微信小程序最少要四张业务表,常见设计如下:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| tb_user | 学生和管理员信息 | id, openid, nickname, college_id, role, create_time |
| tb_report | 举报记录 | id, user_id, type, phone, img_url, description, status, create_time |
| tb_article | 反诈知识文章 | id, title, content, category, read_count, status, publish_time |
| tb_notice | 反诈预警公告 | id, title, content, level, create_time |
tb_user.role建议用 0 表示学生、1 表示管理员,管理员账号用单独脚本创建,不要在注册接口放开。tb_report.status用 0 待核查、1 已联系、2 已归档,管理端看到待核查记录时优先处理。img_url不要存 base64,一张举报图片少则几百 KB,存库会让列表查询和备份都变慢,后端收到 MultipartFile 后写磁盘,数据库只存访问路径。字段命名尽量与前端约定一致,比如create_time在 MyBatis 开启mapUnderscoreToCamelCase后会自动映射成 createTime,管理端写统计 SQL 时能少踩很多坑。
2.3 登录不写 session:用 code 换 token,再把用户落库
微信小程序自带的登录态不适合持久化使用,常见做法是wx.login拿到临时 code,后端拿着 code 向微信接口换取 openid,再签发出项目自己的 token。这也是前后端分离项目实战里最常考到的“微信小程序用 code 换 token”流程。后端接口可以这样写:
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserMapper userMapper; @Autowired private WxConfig wxConfig; @PostMapping("/login") public Result login(@RequestBody LoginRequest request) { // 临时 code 一次性有效,接口地址固定 String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxConfig.getAppId() + "&secret=" + wxConfig.getAppSecret() + "&js_code=" + request.getCode() + "&grant_type=authorization_code"; JSONObject response = HttpUtil.getJson(url); if (response.getIntValue("errcode") != 0) { return Result.error(400, "微信登录失败"); } String openid = response.getString("openid"); User user = userMapper.findByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setNickname("微信用户"); user.setRole(0); userMapper.insert(user); } String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(new LoginResponse(token, user)); } }这段代码有四个关键点。第一,js_code只能使用一次,前端不能用缓存 code 去请求两次以上。第二,appid和secret放在 property 文件里,不要写进前端。第三,接口返回里不要带 openid,openid 属于身份信息,只需要后端使用。第四,token 要带过期时间,学生端推荐 7 天,管理端推荐 2 小时,以免手机丢失后 token 长期有效。拿到 token 后,小程序把它放进请求头Authorization,后续接口拦截器只解析这个头,不再依赖微信 session。
3. 小程序端反诈功能落地:举报提交、文章列表和预警公告的接口设计
后端登录做好后,就开始碰业务接口。校园反诈小程序用户路径很清晰:先看反诈知识,再判断自己遇到的情况是否属于诈骗,然后举报异常信息;管理员从管理端看到举报,电话或线下处理,把状态从待核查改成已归档。
3.1 请求统一封装:token 和错误码收敛到同一个文件
小程序端直接用wx.request没有问题,但项目页面一多,每次都要写 token 和错误处理,后面维护成本会很高。常见做法是封装一个utils/request.js,把 baseURL、Authorization、code 判断统一放在里面:
// utils/request.js const BASE_URL = 'http://192.168.1.20:8080/campus-antifraud/api'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${path}`, method, data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { // 后端统一返回 { code, msg, data } if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(new Error(res.data.msg)); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(new Error(res.data.msg)); } }, fail: reject }); }); } module.exports = { request, BASE_URL };这样做的原因是把登录过期和业务错误分开处理。code===401时统一跳登录,业务错误只做 toast 提示。请求方法默认 GET,但上传图片时不要走这个封装,因为wx.uploadFile的请求格式和 request 不一样。
3.2 举报提交:先传图片再传文本的两步接口
举报是敏感行为,接口需要做到“图片能传、描述必填、类型可选”。建议前端先调上传接口拿文件 URL,再提交举报内容;不要一次性把 base64 塞进 JSON。接口约定如下:
| 操作 | HTTP | 接口 | 请求格式 |
|---|---|---|---|
| 上传截图 | POST | /api/report/upload | multipart/form-data,字段名 file |
| 提交举报 | POST | /api/report/submit | JSON 字段 type、phone、imgUrl、description |
| 我的举报记录 | GET | /api/report/my?page=1&size=10 | 分页参数 |
| 公告列表 | GET | /api/notice/list?page=1&size=10 | 分页参数 |
对应后端核心代码:
@RestController @RequestMapping("/api/report") public class ReportController { @Autowired private ReportMapper reportMapper; @PostMapping("/upload") public Result upload(@RequestParam("file") MultipartFile file) throws IOException { if (file.isEmpty() || file.getSize() > 5 * 1024 * 1024) { return Result.error(400, "图片为空或超过5MB"); } String ext = StringUtils.getFilenameExtension(file.getOriginalFilename()); String filename = System.currentTimeMillis() + "_" + UUID.randomUUID() + "." + ext; file.transferTo(new File(uploadConfig.getDir(), filename)); return Result.ok("/files/" + filename); } @PostMapping("/submit") public Result submit(@RequestBody ReportVO report, HttpServletRequest request) { Integer userId = TokenUtil.getUserId(request.getHeader("Authorization")); report.setUserId(userId); report.setStatus(0); reportMapper.insert(report); return Result.ok(report.getId()); } }上传接口有两个容易被忽略的细节:file.transferTo的目录必须已存在,否则抛 IOException,建议在@PostConstruct里创建目录。ReportVO里只接收 type、phone、imgUrl、description,不要把 openid 放在请求体里,后端从 token 取用户 id 更安全。举报状态置 0 后,管理端轮询待核查列表即可处理。
3.3 反诈文章与预警公告:列表、详情和搜索接口公开但可统计
反诈知识库不需要登录才能看,所以文章和公告列表要从拦截器白名单里放出去。文章列表接口建议带上 category 和分页,具体 SQL 写到 Mapper XML 里更容易维护:
<select id="selectArticlePage" resultType="ArticleVO"> SELECT id, title, category, read_count, publish_time FROM tb_article <where> status = 1 <if test="category != null and category != ''"> AND category = #{category} </if> </where> ORDER BY publish_time DESC LIMIT #{offset}, #{size} </select>这里 category 可以用“刷单返利”“冒充熟人”“注销校园贷”等校园高发类型,前端做成 Tab 切换。列表页不查 content 字段,详情页再根据 id 查,减少小程序首屏流量。read_count 在详情接口里自增,对应语句是UPDATE tb_article SET read_count = read_count + 1 WHERE id = #{id},这一点能让管理端判断哪类文章真正被学生看过。搜索功能可以用AND title LIKE CONCAT('%', #{keyword}, '%'),但 keyword 必须用#{}预编译,不能字符串拼接,否则容易产生 SQL 注入。
4. SSM 后端管理端的权限与统计:反诈数据不能只进不出
反诈小程序如果只有学生端没有管理端,就只是“文章浏览器”。真正让系统产生价值的,是管理端每天能看到新增举报、按学院排名、按类型统计,知道哪个宣传点效果差。
4.1 用拦截器区分学生和管理员权限
SSM 项目一般不引入 Spring Security,一个 HandlerInterceptor 就够用。先从请求头解析 token 并写入 request attribute,方便 Controller 直接获取:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); Integer userId = TokenUtil.getUserId(token); if (userId == null) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"msg\":\"登录已过期\"}"); return false; } request.setAttribute("userId", userId); request.setAttribute("role", TokenUtil.getRole(token)); return true; } }在 spring-mvc.xml 里配置拦截路径:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/api/**"/> <mvc:exclude-mapping path="/api/auth/login"/> <mvc:exclude-mapping path="/api/article/**"/> <mvc:exclude-mapping path="/api/notice/**"/> <bean class="com.campus.antifraud.interceptor.AuthInterceptor"/> </mvc:interceptor> </mvc:interceptors>拦截器只是身份校验,并没有解决角色校验。管理员接口建议单独以/api/admin/**开头,在 Controller 里读取request.getAttribute("role"),非管理员直接返回 403。更规范的做法是自定义@RequireRole(1)注解,再在拦截器里反射读取,但小项目这样做容易过度设计。先按下面的角色边界收紧接口:
| 角色 | 可访问区块 | 接口示例 |
|---|---|---|
| 学生 | 举报、文章、公告 | /api/report/submit、/api/article/list |
| 管理员 | 举报处理、文章管理、统计报表 | /api/admin/report/audit、/api/admin/stats/overview |
| 匿名 | 登录、公开文章/公告 | /api/auth/login、/api/notice/list |
4.2 按学院和诈骗类型分组统计:SQL 的 GROUP BY 比后端循环快得多
很多同学做统计时先把全表查出来进 Java 循环,数据量一上来就会卡。反诈报表的常见需求是按时间范围统计每个学院的举报量和处理量,一条 SQL 就够了:
SELECT IFNULL(u.college_id, -1) AS college_id, r.type, COUNT(r.id) AS report_count, COUNT(DISTINCT r.user_id) AS reporter_count, SUM(CASE WHEN r.status IN (1, 2) THEN 1 ELSE 0 END) AS handled_count FROM tb_report r LEFT JOIN tb_user u ON r.user_id = u.id WHERE r.create_time >= #{beginTime} AND r.create_time <= #{endTime} GROUP BY u.college_id, r.type ORDER BY report_count DESC这个 SQL 的IFNULL(u.college_id, -1)用来接收未填写学院的老用户;COUNT(DISTINCT r.user_id)表示举报人数,避免一个人提交多条时影响整体判断。主表数据变大时要给 create_time 建普通索引,组合索引(create_time, type)对范围查询收益更明显。如果管理端要做雷达图,可以把 type 固定成几个枚举值,统计维度少、命中高,前端不用做复杂动态配置。这里不建议在 SQL 里用DATE_FORMAT做小时级分组,格式化函数会让索引失效,日维度统计可以接受。
4.3 PageHelper 分页和两个常踩的坑
后台管理列表不能全量返回。SSM 项目用 PageHelper 比手写 LIMIT 省事,但有两个坑。第一个是startPage后必须紧跟 Mapper 方法调用,中间不能有任何其他查询;第二个是 PageHelper 分页只对第一条 SQL 生效,如果 Mapper 里有多条 SQL,要拆开:
@Service public class ReportAdminService { @Autowired private ReportMapper reportMapper; public PageInfo<ReportVO> pageReport(ReportQuery query, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<ReportVO> list = reportMapper.selectReportPage(query); return new PageInfo<>(list); } }上线后如果发现 count 查询特别慢,可以在 mybatis-config.xml 里给 PageHelper 设置reasonable=true和supportMethodsArguments=true。reasonable让页码越界时自动修正,supportMethodsArguments让分页参数可以直接传给 Mapper 方法参数对象,两者作用不同,别混着改。
5. 联调验证与源码改造:让反诈小程序先跑通再交付
5.1 本地联调:地址、开发者工具和真机连不上时的排查
本地联调最常遇到的是路径和 IP 问题。后端 war 包部署在 Tomcat,启动后先打开/campus-antifraud/api/notice/list,看到 JSON 再动小程序端。微信开发者工具里本地开发阶段勾选“不校验合法域名”,请求地址写 localhost 或局域网 IP。真机预览时不能使用 localhost,要把 BASE_URL 改成电脑的局域网 IP,同时保证手机和电脑在同一个网段;如果连不上,先关掉电脑防火墙或查一下 Tomcat 端口有没有被放行。前后端分离项目实战里最容易出问题的不是业务逻辑,而是跨域和本地 IP 配置。
5.2 用 curl 验证登录与举报闭环
验证接口不要每次用小程序点,后台联调更快。先用测试 code 登录:
curl -X POST http://localhost:8080/campus-antifraud/api/auth/login \ -H "Content-Type: application/json" \ -d '{"code":"test_code"}'如果返回 token,说明微信接口配置正确。再带上 token 提交举报:
curl -X POST http://localhost:8080/campus-antifraud/api/report/upload \ -H "Authorization: 上一步返回值里的token" \ -F "file=@screenshot.jpg"最后请求管理端统计接口,检查 status 变化,确认整条链路没有断。小程序端还要做三类异常验证:token 过期后会不会跳登录、上传非图片文件时有没有被拦截、文章搜索关键字为空时会不会返回全量数据。
5.3 拿到源码案例后先改的三个文件
拿到现成源码案例,先不要急着跑全部功能,改三个地方就够了。第一改db.properties里的数据库链接,把 localhost 和密码换成自己的;第二改wx.properties里的 appid 和 secret,否则登录会拿到 40029 错误;第三改上传目录指向本机绝对路径,Windows 下比如D:/upload,Linux 下比如/data/upload。这三个配置对应数据库、微信登录、文件存储三条线,全部改完后,基本就能把整个校园反诈小程序跑起来。
本文还有配套的精品资源,点击获取