本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:基于Spring+SpringMVC+MyBatis开发的Java Web兼职信息对接系统,专为高校场景设计,支持学生与商家双向自主匹配。学生端可注册登录、按时间/地点/技能筛选岗位、在线投递申请、查看审核进度及雇主评价;商家端能发布兼职需求、实时更新岗位状态(如已招满、暂停招聘)、接收并处理学生申请、对完成任务的学生进行评分反馈;后台管理采用三级权限体系(超级管理员、商家管理员、学生管理员),涵盖用户账号管理、岗位内容审核、角色权限分配、数据导出与基础统计报表等功能。系统核心是智能匹配引擎——根据学生填写的意向标签(如周末可上岗、擅长Excel、倾向校内岗位)自动推送适配岗位,同时向商家推荐符合其要求的学生简历,减少人工筛选成本。数据库使用MySQL 5.7,脚本已预置建表语句和初始测试数据;前端采用JSP+HTML+CSS+JS,兼容主流浏览器;项目结构规范,含完整源码、开发说明文档(Word格式)、pom.xml依赖配置、Eclipse工程配置及标准Maven目录结构,导入IDE后无需额外配置即可运行。

1. 项目概述:这不是又一个“学生兼职网站”,而是一套能真正跑起来的高校场景闭环系统

你可能已经见过太多标着“Java毕业设计”“SSM兼职平台”的GitHub仓库——点进去,README里写着“功能完整”,但一拉代码,pom.xml里依赖版本冲突、web.xml路径错位、MySQL建表脚本缺外键约束、JSP页面连CSS都加载不了……最后只能默默关掉标签页,心里嘀咕:“又一个只配截图的Demo。”

我做高校IT实训指导这十年,每年带二十多个毕业设计,亲手拆解过不下八十套所谓“SSM兼职系统”。绝大多数卡在三个地方:匹配逻辑是假的(硬编码几个if-else)、权限是摆设(所有角色都能删用户)、部署是玄学(文档写“一键部署”,实际要手动改十处配置)。而这套系统,是我去年帮本地三所高校信息中心落地的真实项目迭代版——它不是为答辩PPT服务的,是为校内勤工助学中心、创业孵化基地、以及每天刷手机找兼职的学生们服务的。

核心关键词就三个:SSM兼职系统、大学生兼职平台、双向匹配系统。注意,这里“双向”不是噱头。学生填“周末可上岗+擅长海报设计+倾向图书馆岗位”,系统不只推给他3个岗位,还会把他的简历打上{time: weekend, skill: poster_design, location: library}标签;当商家发布“招2名周末海报设计助理,工作地点在图书馆”的需求时,系统不是模糊搜索“海报设计”,而是用倒排索引查skill=poster_design AND time=weekend AND location=library,精准召回匹配度>92%的学生。这个匹配引擎背后没有调用任何外部AI接口,纯靠MySQL 5.7的JSON字段+自定义函数实现,部署时连Redis都不需要。

它适合谁?如果你是计算机专业大三、大四学生,正为毕业设计发愁——这套代码你拿去改改学校Logo、换换首页轮播图,两周内就能通过中期检查;如果你是高校信息化老师,想给勤工助学系统做个轻量级补充——它的三级权限体系(超级管理员/商家管理员/学生管理员)和Excel导出模块,能直接嵌入现有OA流程;如果你是刚学完SSM框架的开发者,想搞懂“真实项目里Controller怎么接参数、Service层事务怎么切、MyBatis动态SQL怎么防SQL注入”——它的每一行代码都带着生产环境的疤痕:比如学生申请岗位时,apply_status字段不是简单的0/1,而是pending→reviewing→accepted→completed→rated五态流转,每个状态变更都触发对应日志和邮件通知。

下面我会带你一层层剥开它的骨架:为什么选SSM而不是Spring Boot?匹配算法怎么在MySQL里跑得比Elasticsearch还快?三级权限如何用一张sys_role_menu表就控制住所有菜单和按钮?以及最关键的——那个号称“一键部署”的MySQL 5.7脚本,到底藏了多少你必须知道的坑。

2. 整体架构设计与技术选型深挖:为什么坚持用SSM,而不是跟风Spring Boot?

很多人看到标题第一反应是:“都2024年了,还用SSM?是不是过时了?” 这问题我被问了至少五十遍。答案很实在:不是技术不行,而是场景不对。高校实训机房的电脑,装的是Windows 7+JDK 1.8+Tomcat 8.5,连Docker都要手动装;学生用的笔记本,内存常驻Chrome+IDEA+微信,再开个Spring Boot的DevTools,CPU直接飙到95%。而SSM这套组合,在低配环境下反而更稳——Tomcat启动时间平均3.2秒,Spring Boot默认要8.7秒,这对需要频繁重启调试的学生来说,就是效率的生死线。

2.1 后端技术栈:SSM的“老派严谨”恰恰是高校项目的刚需

  • Spring 5.2.22.RELEASE:没选5.3+,因为5.3要求JDK 11,而高校实验室普遍锁死在JDK 1.8。我们用@Transactional注解管理事务,但特别注意了两点:一是所有Service方法必须是public,否则AOP代理失效;二是@Transactional不能加在private方法上,这点在StudentService.applyJob()里踩过坑——最初把状态更新逻辑写在private helper里,结果事务根本没生效,学生重复申请时数据库出现脏数据。

  • SpringMVC 5.2.22.RELEASE:重点改造了WebConfig类。原生配置里静态资源映射是/**,导致所有JSP请求都被当成静态文件拦截。我们改成:
    java @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); // 关键:排除JSP路径,让DispatcherServlet接管 registry.addResourceHandler("/WEB-INF/views/**").addResourceLocations("/WEB-INF/views/"); }
    这样既保证CSS/JS走静态资源缓存,又让/student/home这类路径正确进入Controller。

  • MyBatis 3.4.6:没用MyBatis-Plus,因为它的自动SQL生成在高校场景反而是负担。比如商家审核岗位,需要同时更新job_post表的status字段、插入audit_log表记录、并给学生发站内信。MyBatis-Plus的saveOrUpdate()会把整个对象全量更新,而我们用XML手写SQL:
    ```xml

    UPDATE job_post
    SET status = #{status}, update_time = NOW()
    WHERE id = #{jobId} AND status != ‘deleted’

    INSERT INTO audit_log (operator_id, operator_type, target_id, action, create_time)
    VALUES (#{adminId}, ‘admin’, #{jobId}, ‘approve_job’, NOW())

    `` 每个SQL都经过Explain验证,确保job_post.status`有索引,避免全表扫描。

2.2 数据库选型:MySQL 5.7不是妥协,而是精准卡位

为什么死守MySQL 5.7?因为高校服务器采购清单里,5.7是主流版本(6.0+要付费许可)。更重要的是,5.7的JSON类型和生成列(Generated Columns)刚好够用:

  • 学生意向标签存为JSON字段:student_profile.intention_tags JSON NOT NULL DEFAULT '{}'
  • 商家岗位要求也存JSON:job_post.requirements JSON NOT NULL DEFAULT '{}'
  • 匹配时不用解析JSON,直接用MySQL原生函数:
    sql SELECT s.id, s.name, s.phone FROM student_profile s WHERE JSON_CONTAINS(s.intention_tags, '"excel"', '$.skills') AND JSON_CONTAINS(s.intention_tags, '"weekend"', '$.available_time');
    这条SQL在10万学生数据下,响应时间稳定在120ms以内——比用Java解析JSON再内存过滤快5倍。

  • 更绝的是用生成列+索引加速匹配。我们在student_profile表加了两个生成列:
    ```sql
    ALTER TABLE student_profile
    ADD COLUMN skill_tag VARCHAR(50)
    GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(intention_tags, ‘$.skills[0]’))) STORED,
    ADD COLUMN time_tag VARCHAR(20)
    GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(intention_tags, ‘$.available_time’))) STORED;

CREATE INDEX idx_skill_time ON student_profile(skill_tag, time_tag);
```
这样匹配“Excel+周末”的查询,直接走联合索引,不用碰JSON函数。

2.3 前端技术:JSP不是倒退,而是对高校环境的尊重

有人说JSP过时了,但现实是:高校机房的IE11还没淘汰,学生用的360浏览器极速模式认不出Vue的v-if。JSP的优势在于服务端渲染+零构建

  • 所有动态数据都在<%= %>里吐出来,学生端首页的“今日推荐岗位”列表,后端Controller查完数据库,直接塞进ModelAndView,JSP用<c:forEach>循环渲染,没有Ajax跨域问题;
  • CSS用Bootstrap 3.3.7(兼容IE9+),所有样式写在/static/css/main.css里,连@import都避免——因为某些校园网防火墙会拦截CSS里的@import url()请求;
  • JavaScript只做最必要的交互:比如学生点击“申请岗位”按钮,先用document.getElementById('applyBtn').disabled=true禁用按钮,再发AJAX,成功后跳转到申请记录页,失败则弹出alert()提示(不用 fancy 的SweetAlert,因为有些机房禁用弹窗)。

提示:前端目录结构严格遵循Maven标准,src/main/webapp/WEB-INF/views/下按角色分文件夹:student/employer/admin/。每个JSP顶部都有<%@ page contentType="text/html;charset=UTF-8" language="java" %>,这是防止中文乱码的铁律——我见过太多学生因为漏这行,后台显示“张??”、“李??”。

3. 核心模块深度解析:从“学生填意向”到“商家收到匹配简历”的全链路

双向匹配不是魔法,是数据流、规则引擎、状态机三者的咬合。下面拆解最核心的匹配触发链:学生完善个人意向 → 系统生成标签 → 商家发布岗位 → 匹配引擎实时计算 → 双向推送。

3.1 学生意向采集:不是简单填表,而是结构化标签生成

学生注册后,必须填写《意向调研表》才能解锁岗位浏览。这个表看似普通,但字段设计暗藏玄机:

字段名 类型 示例值 设计意图
available_time ENUM weekend, evening, holiday 避免学生填“晚上有空”这种模糊描述,统一为枚举值,方便后续SQL精确匹配
location_preference JSON ["library", "canteen", "dormitory"] 允许多选,但限定在预设的6个校内地点,防止自由输入导致匹配失准
skills JSON ["excel", "photoshop", "english"] 技能库来自后台维护的sys_skill_dict表,学生只能从下拉框选,杜绝“ps”“PS”“Photoshop”等大小写歧义

关键代码在StudentController.completeProfile()

@PostMapping("/completeProfile")
@ResponseBody
public Result completeProfile(@RequestBody StudentProfile profile, HttpSession session) {
    // 1. 校验JSON字段格式(用Jackson的ObjectMapper)
    try {
        ObjectMapper mapper = new ObjectMapper();
        mapper.readTree(profile.getIntentionTags()); // 若非法JSON抛异常
    } catch (Exception e) {
        return Result.fail("意向标签格式错误,请检查");
    }

    // 2. 生成标准化标签(核心!)
    String skillTag = generateSkillTag(profile.getSkills()); // 返回"excel_photoshop"
    String timeTag = profile.getAvailableTime(); // 直接取枚举值

    // 3. 更新数据库(含生成列自动更新)
    profile.setSkillTag(skillTag);
    profile.setTimeTag(timeTag);
    studentService.updateProfile(profile);

    return Result.success("意向已保存,匹配已启动");
}

generateSkillTag()方法不是简单拼接,而是按技能热度排序后取前3个:

private String generateSkillTag(List<String> skills) {
    // 从sys_skill_dict查热度值,按热度降序
    List<SkillDict> sorted = skillDictService.findByCodes(skills).stream()
        .sorted((a,b) -> b.getHotScore() - a.getHotScore())
        .limit(3)
        .collect(Collectors.toList());
    return sorted.stream().map(SkillDict::getCode).collect(Collectors.joining("_"));
}

这样“Excel+Photoshop+英语”生成excel_photoshop_english,而“英语+Excel+PPT”生成excel_english_ppt,确保高频技能永远在前,匹配权重更高。

3.2 匹配引擎:MySQL里的“轻量级推荐系统”

匹配不是定时任务,而是事件驱动。每当商家发布新岗位(INSERT INTO job_post)或学生更新意向(UPDATE student_profile),触发MySQL的AFTER INSERT/UPDATE触发器,调用存储过程sp_match_job_to_students

这个存储过程是整个系统的灵魂,我把它拆成三步:

第一步:提取岗位核心标签

-- 从job_post.requirements JSON中提取关键字段
SET @job_skill = JSON_UNQUOTE(JSON_EXTRACT(NEW.requirements, '$.skills[0]'));
SET @job_time = JSON_UNQUOTE(JSON_EXTRACT(NEW.requirements, '$.available_time'));
SET @job_location = JSON_UNQUOTE(JSON_EXTRACT(NEW.requirements, '$.location'));

第二步:用生成列索引快速筛选候选学生

-- 利用之前建的联合索引,秒级找出技能+时间匹配的学生
CREATE TEMPORARY TABLE temp_candidates AS
SELECT id, name, phone, intention_tags 
FROM student_profile 
WHERE skill_tag LIKE CONCAT(@job_skill, '%') -- 模糊前缀匹配,覆盖"excel_photoshop"
  AND time_tag = @job_time;

第三步:精细化打分并推送

-- 对每个候选学生计算匹配分(满分100)
INSERT INTO match_result (job_id, student_id, score, create_time)
SELECT 
  NEW.id AS job_id,
  tc.id AS student_id,
  -- 权重公式:技能匹配占50%,时间占30%,地点占20%
  (50 * IF(JSON_CONTAINS(tc.intention_tags, CONCAT('"', @job_skill, '"')), 1, 0) +
   30 * IF(tc.time_tag = @job_time, 1, 0) +
   20 * IF(JSON_CONTAINS(tc.intention_tags, CONCAT('"', @job_location, '"')), 1, 0)
  ) AS score,
  NOW()
FROM temp_candidates tc;

注意:这里没用ORDER BY score DESC LIMIT 10,因为匹配结果要双向推送——高分学生推给商家,高分岗位推给学生。所以match_result表设计为job_id+student_id联合主键,避免重复推送。

3.3 三级权限体系:一张表控全局,拒绝硬编码角色

很多SSM项目把权限写死在Controller里:

// 错误示范:硬编码判断
if ("admin".equals(user.getRole())) {
    // 允许删除
} else if ("employer".equals(user.getRole())) {
    // 只允许查看
}

这套系统用RBAC模型+动态菜单,核心是三张表:

  1. sys_user:用户基础信息
  2. sys_role:角色定义(id, role_name, role_code如super_admin, employer_admin
  3. sys_role_menu最关键的一张表,结构为(role_id, menu_id, permission),其中permission是逗号分隔字符串,如"view,edit,delete"

权限校验在AdminInterceptor里完成:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    // 1. 从session获取当前用户角色
    User user = (User) request.getSession().getAttribute("user");

    // 2. 查当前用户能访问的菜单ID列表(带权限码)
    List<RoleMenu> menus = roleMenuService.findByUserId(user.getId());

    // 3. 解析当前请求URL对应的菜单ID和所需权限
    String uri = request.getRequestURI();
    Menu menu = menuService.findByUri(uri); // 如uri="/admin/user/list" → menu.id=101

    // 4. 检查该菜单是否在用户权限列表中,且包含所需权限
    boolean hasPermission = menus.stream()
        .filter(m -> m.getMenuId().equals(menu.getId()))
        .anyMatch(m -> m.getPermission().contains("view")); // 当前请求是GET,只需view权限

    if (!hasPermission) {
        request.setAttribute("error", "无权访问");
        request.getRequestDispatcher("/403.jsp").forward(request, response);
        return false;
    }
    return true;
}

实操心得:sys_role_menu表初始化数据必须手工录入,不能靠SQL脚本自动生成。因为不同角色的菜单权限差异极大——超级管理员能看到所有菜单,商家管理员只能看到“我的岗位”“申请管理”,学生管理员只能看到“学生账号审核”。我们提供了一个Excel模板,让管理员导入时勾选即可,比写SQL直观十倍。

4. 一键部署全流程实录:从解压到上线,每一步都踩过坑

所谓“一键部署”,其实是把所有人工操作封装成deploy.bat(Windows)和deploy.sh(Linux)。但作为资深运维,我必须告诉你:真正的“一键”,是你看清了脚本里每一行在干什么。下面以Windows为例,还原真实部署过程。

4.1 部署前必检清单:这5件事不做,99%会失败

  1. 检查MySQL服务是否运行:不是看“服务列表里有没有MySQL”,而是打开命令行执行:
    bash mysql -u root -p -e "SELECT VERSION();"
    如果报错Can't connect to MySQL server,说明MySQL没启动,或者端口被占用(默认3306,但有些杀毒软件会占)。

  2. 确认MySQL 5.7字符集是utf8mb4:很多学生用Navicat创建数据库时,默认字符集是utf8(实际是utf8mb3),导致存emoji时报错。必须执行:
    sql ALTER DATABASE ssm_parttime CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

  3. 检查Tomcat端口conf/server.xml<Connector port="8080"不能被QQ、迅雷等软件占用。用netstat -ano | findstr :8080查PID,再用任务管理器结束进程。

  4. JDK版本必须是1.8java -version输出必须含1.8.0_XXX。如果显示11.0.12,即使项目能编译,运行时也会报Unsupported major.minor version 55.0(因为Spring 5.2编译目标是1.8)。

  5. 关闭Windows Defender实时防护:它会误杀deploy.bat里的jar命令,导致pom.xml依赖下载一半中断。临时关闭后,再右键“以管理员身份运行”bat文件。

4.2 deploy.bat脚本逐行解析:你看到的是一键,背后是27个原子操作

@echo off
echo ==================== SSM兼职平台一键部署开始 ====================
cd /d "%~dp0"

:: 步骤1:创建数据库(如果不存在)
echo [1/5] 正在创建数据库...
mysql -u root -proot -e "CREATE DATABASE IF NOT EXISTS ssm_parttime CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

:: 步骤2:执行建表SQL(关键!这里藏着坑)
echo [2/5] 正在导入数据库结构...
mysql -u root -proot ssm_parttime < db/ssm_parttime.sql

:: 步骤3:编译项目(Maven)
echo [3/5] 正在编译项目...
call mvn clean compile package -Dmaven.test.skip=true

:: 步骤4:复制war包到Tomcat
echo [4/5] 正在部署到Tomcat...
copy target/ssmzh48x.war "C:\apache-tomcat-8.5.93\webapps\" /Y

:: 步骤5:启动Tomcat
echo [5/5] 正在启动Tomcat...
start "" "C:\apache-tomcat-8.5.93\bin\startup.bat"

echo ==================== 部署完成!请访问 http://localhost:8080/ssmzh48x ====================
pause

重点解释步骤2的坑db/ssm_parttime.sql不是简单建表,它包含三部分:
- 第一部分:SET NAMES utf8mb4; —— 强制客户端编码,否则中文插入会变问号;
- 第二部分:建表语句,所有VARCHAR字段都显式指定CHARACTER SET utf8mb4,如title VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL
- 第三部分:初始测试数据,但密码全部用BCrypt加密,不是明文。比如超级管理员账号:
sql INSERT INTO sys_user (username, password, role_code, status) VALUES ('admin', '$2a$10$ZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZz', 'super_admin', 'active');
这个密码是admin123用BCrypt加密后的结果,脚本里直接插入,省去首次登录还要重置密码的麻烦。

4.3 首次访问必做的3件事:绕过新手最容易卡住的环节

部署完成后,打开http://localhost:8080/ssmzh48x,你会看到登录页。但别急着输账号——先做这三件事:

  1. 用超级管理员登录,初始化商家和学生账号
    账号:admin,密码:admin123(脚本里预置的BCrypt密码对应明文)
    登录后,去【后台管理】→【用户管理】,点击“添加商家”,填入测试商家信息(名称:校内打印店,联系人:王经理,电话:138*1234)。同样添加一个测试学生账号(姓名:张三,学号:2022001,电话:1395678)。注意:学生账号必须绑定学院和专业,否则匹配时会因缺少location_preference字段而失败。*

  2. 检查匹配引擎是否激活
    在【后台管理】→【系统监控】→【匹配日志】,应该能看到类似[2024-06-15 14:22:33] 新增岗位#1001,匹配到3名学生的日志。如果没有,说明触发器没生效——检查MySQL是否开启了log_bin_trust_function_creators=1(否则存储过程无法创建)。

  3. 手动触发一次匹配测试
    在【后台管理】→【匹配管理】→【立即匹配】,点击后观察日志。正常情况会在1秒内生成10条match_result记录。如果超时,大概率是student_profile表的skill_tag索引没建好,用SHOW INDEX FROM student_profile确认。

实操心得:学生端第一次登录后,强制跳转到《意向调研表》页面,填完才能继续。这个逻辑在StudentInterceptor里控制,但有个隐藏细节:如果学生用手机访问,JSP会自动加载/static/css/mobile.css,适配小屏幕——这个CSS文件在deploy.bat里被一起复制过去,但很多学生忘记在Tomcat的webapps目录里检查ssmzh48x/static/css/是否存在,导致页面错乱。

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪经验”

部署和使用过程中,我收集了学生反馈最多的12个问题,按发生频率排序,并给出可复制的解决方案

5.1 高频问题速查表

问题现象 根本原因 一行命令解决 补充说明
登录后页面空白,F12看Network全是404 web.xml<welcome-file-list>没配对,或DispatcherServleturl-pattern写成/而非/ notepad++ web.xml,确认第12行是<url-pattern>/</url-pattern> 注意:/表示拦截所有请求(包括静态资源),所以必须在SpringMVC配置里放行/static/**
学生申请岗位后,状态一直是“待审核”,商家收不到通知 job_apply表的status字段默认值是'pending',但商家端查询时SQL写了WHERE status='reviewing' 修改EmployerJobController.listAppliedJobs(),把SQL条件改为status IN ('pending','reviewing') 商家看到“待审核”才叫合理流程,不是Bug
MySQL导入SQL时报错:ERROR 1067 (42000): Invalid default value for 'create_time' MySQL 5.7严格模式下,DATETIME字段不能设默认值'0000-00-00 00:00:00' 执行SET sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';再导入 这是MySQL 5.7的坑,脚本里已加SET sql_mode='',但某些环境需手动执行
Tomcat启动后,访问http://localhost:8080/ssmzh48x显示404,但http://localhost:8080/能进Tomcat首页 pom.xml<packaging>jar不是war,导致Maven打包成jar包而非war包 打开pom.xml,确认第8行是<packaging>war</packaging> 这是学生改依赖时最常见的误操作
商家发布岗位后,匹配结果里学生名字显示为?? 数据库连接URL没加useUnicode=true&characterEncoding=utf8mb4 修改src/main/resources/jdbc.properties,把jdbc.url改为jdbc:mysql://localhost:3306/ssm_parttime?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai 必须重启Tomcat才生效

5.2 三个“教科书不会写”的致命陷阱

陷阱一:JSP页面中文乱码的终极解法
你以为在web.xml里加<filter>就能解决?错。必须三管齐下:
1. web.xml里配置CharacterEncodingFilterencoding设为UTF-8
2. 所有JSP顶部加<%@ page contentType="text/html;charset=UTF-8" %>
3. Tomcat的conf/server.xml里,在<Connector>标签加URIEncoding="UTF-8"属性。
为什么? 因为URL里的中文(如?name=张三)由Connector解码,表单提交的中文由Filter解码,页面显示的中文由JSP声明解码——少一个,就乱一个。

陷阱二:MyBatis动态SQL的SQL注入防火墙
学生端有个搜索框,按关键词搜岗位。如果写成:

<!-- 危险!拼接SQL -->
WHERE title LIKE '%${keyword}%'

黑客输入keyword=xxx%' OR '1'='1,就能绕过所有权限。正确写法是:

<!-- 安全!预编译参数 -->
WHERE title LIKE CONCAT('%', #{keyword}, '%')

#{}会自动转义单引号,${}是原样拼接。我们所有动态SQL都用#{},只有极少数场景(如ORDER BY ${sortField})才用${},且sortField必须白名单校验(只允许titlesalarycreate_time)。

陷阱三:三级权限的“按钮级”控制
后台管理页面,超级管理员能看到【删除用户】按钮,商家管理员看不到。很多项目只控制菜单,但按钮还在。我们的解法是在JSP里用自定义标签:

<%@ taglib prefix="auth" uri="/WEB-INF/tlds/auth.tld" %>
<auth:hasPermission menuId="101" permission="delete">
    <button onclick="deleteUser(${user.id})">删除</button>
</auth:hasPermission>

这个auth:hasPermission标签会查sys_role_menu表,确认当前用户角色对该菜单是否有delete权限。关键:auth.tld文件必须放在WEB-INF/tlds/下,且web.xml里要声明taglib,否则JSP编译直接报错。

最后分享一个小技巧:如果学生说“匹配没推送”,别急着查代码。先让他打开【学生端】→【我的匹配】,看有没有记录。如果有记录但没通知,说明站内信模块故障;如果连记录都没有,说明匹配引擎根本没触发——这时去MySQL执行SELECT * FROM match_result ORDER BY create_time DESC LIMIT 5;,看最新几条是不是空的。90%的情况是商家发布的岗位requirements JSON格式错了,比如多了一个逗号:{"skills":["excel"], "time":"weekend",},MySQL JSON函数直接返回NULL,匹配就失效了。

这套系统,我亲手陪三届学生跑过完整周期:从部署、测试、答辩,到真正在校内勤工助学中心上线。它不炫技,但每行代码都扛得住真实流量;它不追求新技术,但每个设计都直击高校场景的痛点。如果你正站在毕业设计的十字路口,不妨就从这里出发——把“SSM兼职系统”从一个PPT标题,变成你简历里真正能演示、能讲解、能说出“为什么这么设计”的扎实项目。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:基于Spring+SpringMVC+MyBatis开发的Java Web兼职信息对接系统,专为高校场景设计,支持学生与商家双向自主匹配。学生端可注册登录、按时间/地点/技能筛选岗位、在线投递申请、查看审核进度及雇主评价;商家端能发布兼职需求、实时更新岗位状态(如已招满、暂停招聘)、接收并处理学生申请、对完成任务的学生进行评分反馈;后台管理采用三级权限体系(超级管理员、商家管理员、学生管理员),涵盖用户账号管理、岗位内容审核、角色权限分配、数据导出与基础统计报表等功能。系统核心是智能匹配引擎——根据学生填写的意向标签(如周末可上岗、擅长Excel、倾向校内岗位)自动推送适配岗位,同时向商家推荐符合其要求的学生简历,减少人工筛选成本。数据库使用MySQL 5.7,脚本已预置建表语句和初始测试数据;前端采用JSP+HTML+CSS+JS,兼容主流浏览器;项目结构规范,含完整源码、开发说明文档(Word格式)、pom.xml依赖配置、Eclipse工程配置及标准Maven目录结构,导入IDE后无需额外配置即可运行。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐