ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android校园招聘系统开发全流程:MySQL设计与Servlet接口实战

Android校园招聘系统开发全流程:MySQL设计与Servlet接口实战 简介面向计算机专业毕业设计或项目实践本资源是一套基于Android Studio开发的校园兼职求职招聘系统配套服务端后台与MySQL数据库完整覆盖学生求职与用人单位招聘流程。Android端包含注册登录、个人信息管理、兼职搜索、简历投递、岗位收藏、消息通知、即时聊天和评价反馈模块管理端提供用户认证与权限管理、兼职信息发布维护、简历筛选管理和消息推送。资源包共414个文件压缩后约8.36MB主要文件类型包括Java源码、编译后的Class文件、Jar依赖库、Jsp/Js/Css前端页面、SQL数据库脚本、Png与Jpg界面截图、Gif功能演示动图及一份Docx说明文档目录结构较完整便于按Android客户端、服务端后台和数据库脚本分类查阅。内容预览显示兼职信息、注册登录、聊天消息、数据库连接等核心模块均有对应实现。目前已有192人学习下载对于需要完成毕业设计或快速上手Android与服务端整合开发的读者具有较好的参考价值。1. 校园招聘系统从零搭建的完整链路这套基于 Android 的校园兼职求职招聘系统本质上是一个移动端 服务端 数据库的三层结构。Android 端承担学生和用人单位的操作界面服务端后台提供接口和数据逻辑MySQL 负责持久化存储。很多人在做类似毕业设计或练手项目时最大的误区是一上来就写界面结果到联调阶段才发现接口设计完全不符合业务需求。从实际项目角度看这个选题覆盖了移动开发中最典型的几类场景多角色登录与权限区分、复杂条件查询、文件级的状态流转求职申请、以及实时性要求不高的即时通信。它不涉及支付、不涉及地图定位业务边界清楚非常适合用来理解一个完整业务系统从前端到后端到数据库的协作方式。适合的人群是正在做 Android 毕业设计的学生以及想补全客户端如何与后端交互这块知识短板的初级开发者和转岗测试工程师。下文从数据库表结构设计开始逐步拆解服务端接口、Android 端网络层封装、核心业务状态机实现最后落到部署验证和排错技巧。所有代码基于 Android Studio Servlet JDBC 方案核心类与项目中的 RegisterAction、JobAction、JdbcUtils、HttpUtil 一一对应。2. MySQL 数据库设计与 JdbcUtils 连接池实现2.1 六张核心表的结构设计与字段选择数据库是这个系统的地基。根据项目功能描述可以归纳出六张核心表用户表多角色合一、兼职信息表、简历表、收藏表、消息表、评价表。这里有一个关键设计决策学生和用人单位放在同一张 user 表用 role 字段区分而不是拆成两张表。这种设计的理由是权限模型简单。学生和用人单位共享手机号登录、头像、昵称等字段拆表会导致 auth 逻辑重复增加不必要的联合查询。role 字段取值为 1学生和 2用人单位后续所有接口只要根据 role 判断是否有权限操作即可。下面是建表 SQL 的核心部分CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(11) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, nickname VARCHAR(32), avatar VARCHAR(128), role TINYINT DEFAULT 1 COMMENT 1-学生 2-用人单位, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE job ( id INT PRIMARY KEY AUTO_INCREMENT, company_id INT NOT NULL, title VARCHAR(64) NOT NULL, description TEXT, salary VARCHAR(32), location VARCHAR(64), status TINYINT DEFAULT 1 COMMENT 1-招聘中 0-已下线, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;需要注意几个细节phone 字段加 UNIQUE 约束避免同一手机号重复注册password 字段长度设为 64这是 MD5 加密后十六进制字符串的长度job 表冗余了 salary 和 location 字段查询时不需要关联其他表这在移动端分页加载时能显著减少响应时间。charset 必须用 utf8mb4否则用户昵称里出现 emoji 字符会直接报错。简历表和收藏表都是关联表结构。简历表 resume 包含 student_id、job_id、content简历内容或附件 URL、status投递状态、create_time。收藏表 favorite 则是 student_id 和 job_id 的联合唯一索引防止重复收藏。如果使用 Navicat 或命令行管理数据库结构我一般会这样做先写一个 init.sql 文件把所有建表语句放在事务里执行然后在测试阶段直接用 DROP TABLE IF EXISTS 重建脚本重置环境省得每次手动清理脏数据。2.2 JdbcUtils 使用 DBCP 连接池的原因和实现项目类列表中出现了 JdbcUtils.class这是数据库操作的基础工具类。常见做法是基于 DBCP 或 C3P0 连接池实现。在这个项目里我推荐用 DBCP因为它的依赖少、配置直观对 Servlet 项目的侵入性最小不像 C3P0 那样需要额外的 XML 配置文件。public class JdbcUtils { private static BasicDataSource dataSource new BasicDataSource(); static { dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/campus_job?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMaxWait(5000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(ResultSet rs, PreparedStatement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { } } } }setInitialSize(5) 表示启动时预创建 5 个连接setMaxActive(20) 限制最大连接数setMaxWait(5000) 表示拿不到连接时最多等待 5 秒然后抛出异常。URL 里的 characterEncodingutf8 解决中文乱码serverTimezoneAsia/Shanghai 解决 MySQL 8.x 的时区报错。这里要强调一个常见坑close 方法里的 conn.close() 在连接池环境下不是真的关闭连接而是把连接归还给池子。所以三层架构里 DAO 层必须在 finally 中调用 close 方法否则连接会被一直占用超过 MaxActive 后系统直接卡死。判断方式很简单如果系统运行一段时间后请求全部超时且 MySQL 的 show processlist 里出现大量 Sleep 状态的连接基本就是连接泄漏了。2.3 聚合查询在后台统计中的应用管理员端需要统计各岗位收到的简历数量这个场景适合使用 MySQL 的聚合函数查询。后台的 Dashboard 页面可以通过一个 JOIN 语句同时拿到岗位信息和简历投递数SELECT j.id, j.title, j.salary, COUNT(r.id) AS apply_count FROM job j LEFT JOIN resume r ON j.id r.job_id WHERE j.status 1 GROUP BY j.id, j.title, j.salary ORDER BY apply_count DESCLEFT JOIN 保证没有收到简历的岗位也会出现在结果里COUNT(r.id) 只统计关联到的简历记录数GROUP BY 后面必须列出 SELECT 中非聚合的所有字段MySQL 的 ONLY_FULL_GROUP_BY 模式会直接报错。这个查询结果可以直接转换成 JSON 数组返回给管理端前端用 RecyclerView 展示。3. 服务端 Servlet 接口设计与 RegisterAction 登录注册实现3.1 Servlet 层实现 RegisterAction.class 完整代码服务端采用 Servlet 作为 HTTP 层每个 Action 类对应一个业务动作。这里的 Action 类继承 HttpServletdoPost 方法中首先设置请求和响应编码然后从 request 中获取参数调用 DAO 层方法操作数据库最后把结果封装成 JSON 写回客户端。以用户注册为例public class RegisterAction extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(application/json;charsetUTF-8); String phone request.getParameter(phone); String password request.getParameter(password); String nickname request.getParameter(nickname); int role Integer.parseInt(request.getParameter(role)); UserDao userDao new UserDao(); JSONObject result new JSONObject(); if (userDao.isPhoneExist(phone)) { result.put(code, 1); result.put(msg, 手机号已注册); } else { boolean success userDao.addUser(phone, MD5Util.md5(password), nickname, role); result.put(code, success ? 0 : 1); result.put(msg, success ? 注册成功 : 注册失败); } response.getWriter().write(result.toString()); } }password 传入后经过 MD5Util.md5 加密再存库这是 Web 项目的底线操作。明文密码在任何场景下都不应该落库。虽然 MD5 本身不够安全但对于课程设计级别的项目已经够用如果要更严谨可以使用加盐处理MD5(password 固定盐值字符串)盐值存放在服务端配置文件中。3.2 在 web.xml 或注解中配置 Servlet 映射及参数说明Servlet 3.0 之后支持 WebServlet 注解配置省去 web.xml 烦琐的映射声明。注解方式的优势是配置就近原则代码可读性更好。比如在 RegisterAction 类上方加一行即可WebServlet(/api/register)完整访问路径由项目上下文路径加上注解值组成比如部署在 Tomcat 的 ROOT 应用下客户端请求的 URL 就是http://服务器IP:8080/api/register。Android 端的 HttpUtil 会拼装这个完整地址。如果使用模拟器访问本机注意 10.0.2.2 映射到开发机的 localhost这个细节很多新手会踩坑。登录接口 LoginAction 与注册逻辑对称流程是根据手机号查出 user 记录比对数据库中密码的 MD5 值是否一致一致则返回完整用户信息userId、nickname、avatar、role给客户端。客户端拿到这些数据后存储到 SharedPreferences后续请求带上 userId 作为身份标记。3.3 个人信息管理接口的多表协同个人信息管理涉及到头像上传。Android 端通过 HttpURLConnection 以 multipart/form-data 形式提交图片文件服务端用一个 FileUploadAction 接收并保存到服务器指定目录然后只把图片的访问 URL 写回数据库的 avatar 字段。前端展示时直接使用 Glide 加载这个 URL。// Android端上传头像代码片段 File file new File(avatarPath); String boundary ----WebKitFormBoundary System.currentTimeMillis(); HttpURLConnection conn (HttpURLConnection) new URL(BASE_URL /api/upload).openConnection(); conn.setRequestMethod(POST); conn.setDoOutput(true); conn.setRequestProperty(Content-Type, multipart/form-data; boundary boundary); DataOutputStream os new DataOutputStream(conn.getOutputStream()); // 拼接表单字段和文件二进制数据 os.writeBytes(-- boundary \r\n); os.writeBytes(Content-Disposition: form-data; name\file\; filename\ file.getName() \\r\n); os.writeBytes(Content-Type: image/jpeg\r\n\r\n); FileInputStream fis new FileInputStream(file); byte[] buffer new byte[1024]; int len; while ((len fis.read(buffer)) ! -1) { os.write(buffer, 0, len); } os.writeBytes(\r\n-- boundary --\r\n);逻辑说明这段代码手动拼装 multipart 报文格式boundary 是随机生成的分隔字符串服务端根据这个分隔符解析出文件内容。与直接用第三方库相比这种手写方式能让学生理解 HTTP 协议层的原始报文格式。参数说明请求头 Content-Type 必须带有 boundary 值文件流写入后必须以\r\n--boundary--结尾代表报文结束。4. Android 端 HttpUtil 封装与多条件兼职搜索4.1 HttpUtil.class 统一管理 GET 和 POST 请求Android 端网络层核心是 HttpUtil.class它把 HttpURLConnection 的细节封装起来对外提供 get 和 post 两个静态方法。之所以不用 OkHttp是为了减少依赖和混淆成本课程设计答辩时也可以直接解释底层原理。项目使用 Android 6.0 及以上系统时必须注意明文流量默认禁止访问需要在 AndroidManifest.xml 的 application 标签中加一行android:usesCleartextTraffictrue或者配置网络安全策略只允许特定域名明文传输。public class HttpUtil { public static String post(String url, MapString, String params) { try { StringBuilder sb new StringBuilder(); if (params ! null) { for (Map.EntryString, String entry : params.entrySet()) { sb.append(entry.getKey()).append() .append(URLEncoder.encode(entry.getValue(), UTF-8)).append(); } sb.deleteCharAt(sb.length() - 1); } HttpURLConnection conn (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod(POST); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setDoOutput(true); conn.getOutputStream().write(sb.toString().getBytes(UTF-8)); InputStream is conn.getInputStream(); BufferedReader reader new BufferedReader(new InputStreamReader(is, UTF-8)); StringBuilder response new StringBuilder(); String line; while ((line reader.readLine()) ! null) { response.append(line); } return response.toString(); } catch (Exception e) { e.printStackTrace(); return null; } } }逻辑说明参数被 URLEncode 编码后拼成查询字符串写入请求体服务端通过 getParameter 直接取值。connectTimeout 设置的是 TCP 连接建立超时时间readTimeout 是服务端响应超时时间这两个值应该根据业务调整兼职列表查询可以设置为 5 秒头像上传接口建议放宽到 15 秒。返回值是纯字符串由调用层用 JSONObject 解析。4.2 JobAction 实现关键词、地点、薪资范围的多条件拼接兼职搜索是核心功能。学生端传入三个条件keyword关键词、location地点、priceRange薪资范围。服务端 JobAction 接收到这些参数后动态拼接 SQL而不是使用固定的查询语句。public class JobDao { public ListJob searchJobs(String keyword, String location, int minSalary) { StringBuilder sql new StringBuilder(SELECT * FROM job WHERE status 1 ); ListObject params new ArrayList(); if (keyword ! null !keyword.isEmpty()) { sql.append(AND (title LIKE ? OR description LIKE ?) ); params.add(% keyword %); params.add(% keyword %); } if (location ! null !location.isEmpty()) { sql.append(AND location LIKE ? ); params.add(% location %); } if (minSalary 0) { sql.append(AND CAST(salary AS UNSIGNED) ? ); params.add(minSalary); } sql.append(ORDER BY create_time DESC LIMIT 20); return query(sql.toString(), params); } }这里采用 StringBuilder 拼接配合 PreparedStatement 的参数占位符能够有效防止 SQL 注入。salary 字段在数据库中是 VARCHAR 类型因为薪资存的是200-300元/天这种文本CAST(salary AS UNSIGNED) 取出数字前缀进行数值比较。如果用户选择不限薪资前端传 0 过来这个条件就会被跳过。LIMIT 20 做初步分页控制避免一次性加载全表数据。Android 端搜索界面采用一个 SearchView 两个 Spinner地点和薪资范围搜索结果展示在 RecyclerView 中。由于 Android 不允许在 UI 线程执行网络请求需要使用子线程或 AsyncTask 调用 HttpUtil回调结果后通过 runOnUiThread 切回主线程刷新适配器。4.3 FastJSON 解析 Java 对象与 Android 实体类字段映射服务端返回的 JSON 格式为{code:0,data:[...]}Android 端通过 FastJSON 的JSON.parseObject或JSON.parseArray转换成实体类。以兼职列表为例服务端从数据库中查询出 ListJob 后转为 JSONArrayAndroid 端这样解析// Android端解析兼职列表 String response HttpUtil.post(BASE_URL /api/job/search, params); JSONObject jsonObject JSON.parseObject(response); if (jsonObject.getIntValue(code) 0) { ListJob jobList JSON.parseArray( jsonObject.getString(data), Job.class); adapter.setData(jobList); }实体类字段名必须与数据库列名或服务端 JSON 字段名保持一致否则解析出来的对象字段全部是默认值。常见做法是在服务端 JSONObject.put 时就使用与 Java 实体类一致的驼峰命名比如数据库列名 create_time服务端转为 createTime 再写入 JSON。5. 简历投递、收藏与消息聊天模块实现5.1 投递简历的状态机设计与 DAO 层数据流转兼职申请是整个系统里状态最复杂的模块。学生投递简历后这条记录经历的状态依次为0待审核→ 1已通过或 2已拒绝。部分系统还会增加 3已下架状态用于用人单位关闭岗位后自动驳回所有待处理申请。状态流转图不需要画复杂的图只要在代码中保证每个状态变更操作都经过校验即可。public class ResumeDao { // 学生投递简历status默认0待审核 public boolean addResume(int studentId, int jobId, String content) { String sql INSERT INTO resume(student_id, job_id, content, status, create_time) VALUES(?,?,?,0,NOW()); // 执行前先查询是否已投递 String checkSql SELECT id FROM resume WHERE student_id? AND job_id?; // 如果已存在则不能重复投递 } // 用人单位审核更新状态 public boolean reviewResume(int resumeId, int status) { String sql UPDATE resume SET status? WHERE id?; // status只允许传1或2 } }重复投递检查放在 DAO 层执行逻辑是查询该学生是否已对该岗位投递过简历。如果已投递直接返回提示否则插入新记录。这个检查需要放在一个数据库事务里否则两个请求同时进来可能产生重复数据。对于课程设计用 synchronized 或者唯一索引兜底即可生产环境应该考虑用 SELECT FOR UPDATE 或者数据库唯一约束。5.2 收藏列表的双层判断与跨表信息加载收藏功能是典型的判断 vs 操作异步问题。学生第一次点击收藏时需要先判断是否已收藏已收藏则取消关注删除记录未收藏则插入记录。所以接口设计上用 POST 传递 action 参数区分 add 和 remove 两种操作客户端根据按钮的当前状态决定传什么值。收藏列表的展示需要跨表查询SELECT 语句从 favorite 表关联 job 表取回兼职的标题、薪资、地点等展示信息。Android 端提供收藏列表入口进入后加载该学生的所有收藏记录。服务端分页返回数据时需要注意如果收藏的岗位被管理员下架JOIN 结果中 status0 的数据要过滤掉或者显示已下架的灰色样式。5.3 基于轮询的即时消息方案与 Group 类设计消息聊天模块是本项目的亮点也是很多学生感觉困难的地方。项目中的 Chatroom、Message、Group 三个类构成了聊天功能的主体。在不引入 WebSocket 的前提下最实用且容易解释清楚的方案是轮询客户端每隔几秒请求一次获取新消息接口服务端返回该用户相关的聊天记录。-- 获取两个用户之间的聊天记录 SELECT * FROM message WHERE (send_id? AND recv_id?) OR (send_id? AND recv_id?) ORDER BY create_time ASC LIMIT 20;public class Chatroom { private ListMessage messageList; private int peerUid; // 聊天对象 private int currentUid; private Handler handler new Handler(Looper.getMainLooper()); private Runnable pollTask new Runnable() { Override public void run() { fetchNewMessages(); handler.postDelayed(this, 3000); // 每3秒轮询一次 } }; private void fetchNewMessages() { new Thread(() - { String response HttpUtil.post(BASE_URL /api/chat/history, HttpUtil.buildParams(uid, String.valueOf(currentUid), peerId, String.valueOf(peerUid))); // 解析并刷新RecyclerView }).start(); } }注意几个细节每次轮询的查询条件要带上最后一条消息的 IDWHERE id ?只拿增量减少数据传输量轮询间隔设置为 3 秒间隔太短会打满数据库连接池连接太长消息延迟严重聊天记录表要建立 (send_id, recv_id) 联合索引否则数据量上来之后轮询查询会全表扫描接口耗时直接翻倍。贴出的 Runnable 用 Handler.postDelayed 实现不死循环界面退出时一定要在 onDestroy 里调用 handler.removeCallbacksAndMessages(null) 释放。6. 部署启动顺序与 Android Studio 高版本适配技巧6.1 服务端使用 Tomcat 配合 MySQL 8.x 部署的注意事项部署第一步是确认环境版本兼容性。JDK 版本、Tomcat 版本和 Servlet API 版本必须对应Tomcat 8.5 对应 Servlet 3.1Tomcat 9 对应 Servlet 4.0。MySQL 驱动选择要注意如果使用 MySQL 8.x驱动类名是 com.mysql.cj.jdbc.Driver而不是老版本的 com.mysql.jdbc.Driver。把项目打成 WAR 包放入 Tomcat 的 webapps 目录后启动顺序应该是先启动 MySQL 服务确认 3306 端口正常监听再启动 Tomcat。如果数据库中还没有创建表和初始数据先执行 init.sql 脚本否则服务端启动时会报Table doesnt exist错误。启动完成后先用浏览器直接访问http://localhost:8080/项目名/api/register测试接口连通性把注册参数直接拼在 URL 后用浏览器模拟 POST 请求不方便建议安装一个 Postman 或者用 curl 命令验证。curl 测试的命令行方式curl -X POST http://localhost:8080/campus_job/api/register \ -d phone13800138000passworde10adc3949ba59abbe56e057f20f883enicknametestrole1字段含义-X 指定请求方法-d 后面跟的是请求体内容格式是键值对用 连接服务端通过 getParameter 获取这些值。这段命令验证接口时如果返回{code:0}就说明服务端通了。注意 POST 请求测试用 curl 时要确保 URL 没有空格密码参数应该传 MD5 加密后的值但测试环境传明文也行因为服务端做的是二次加密对比的是 MD5(MD5原文)的最终结果。6.2 Android 端连不上 10.0.2.2 时排查错误方向Android 模拟器访问开发机本地的 Tomcat 时URL 必须写http://10.0.2.2:8080这个地址是 Android 模拟器专门为宿主机 loopback 接口预留的别名。如果你用真机调试需要把 URL 改为开发电脑在局域网中的 IP比如http://192.168.1.100:8080并且保证手机和电脑在同一 WiFi 下。常见错误有三种Tomcat 没有监听 0.0.0.0 地址电脑防火墙拦截了 8080 端口手机和电脑不在同一个网段。关于明文流量限制Android 9API 28及更高版本默认禁止所有 HTTP 明文请求这个问题会在调用 HttpUtil 时抛出CLEARTEXT communication not permitted异常。解决方式在 AndroidManifest.xml 的 application 标签中配置android:usesCleartextTraffictrue。定位问题时优先看服务端日志。在 Action 类中加一条 System.out.println 打印请求参数Tomcat 的 catalina.out 会实时输出。客户端报错时先用浏览器访问同一个地址排除服务端问题再检查 Android 端网络权限是否在 Manifest 中声明。6.3 时间字段格式化与聚合函数在统计报表中的联调聊天记录和简历投递记录都涉及时间展示。MySQL 中 DATETIME 字段通过 JDBC 读取后是一个 java.sql.Timestamp 对象直接 toString() 会得到2025-01-15 14:23:45.0这种带小数点的格式Android 端展示很不友好。常见做法是在服务端格式化成指定格式的字符串再返回。使用 SimpleDateFormat 时需要注意这个类不是线程安全的如果服务端采用多线程处理请求应该在每次调用时创建新实例而不是定义成 static 成员变量。SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm); String createTimeStr sdf.format(job.getCreateTime());管理端的统计报表模块可以用第 2 章提到的聚合函数查询将每日岗位发布数、每日投递数做成折线图。Android 端展示图表时推荐 MPAndroidChart 库它支持 LineChart 和 BarChart 两种最基本的形式数据源就是聚合函数的查询结果。统计查询的性能优化上给 create_time 字段加索引避免 MySQL 对全表进行日期函数计算导致的索引失效。到这里从数据库设计到服务端接口再到 Android 端网络封装和核心业务模块的实现与部署一条完整的开发链路已经全部跑通。实际动手时建议先跑通注册登录和数据展示再逐个加状态流转和聊天功能每完成一个模块都用 curl 验证一次接口等所有接口稳定后再一次性联调 Android 端这样排错范围最小项目进度也最好把控。本文还有配套的精品资源点击获取
返回列表