简介:面向中小型团队与高校实训场景的 Java 考勤管理系统完整源码包,适合具备 Java Web 基础、希望掌握 Spring 风格业务开发或毕业设计复用的学习者。包内按业务模块划分,覆盖员工管理、考勤打卡、请假/会议申请、固定资产与任务调度等常见功能;压缩包共 2000 个文件,约 55MB,以 1336 个 Markdown 文档、500 个 JavaScript 文件、67 个 Java 文件及 JSON/XML 配置为主,文档与配置占比高,便于对照阅读、按需检索和二次开发。已有 170 人学习下载。其中包含控制器、服务、实体与工具类等完整分层,主线功能覆盖考勤申请、会议与员工管理。借助该源码可快速理解考勤业务表设计、接口分层、审批流转与前后端交互方式,尤其适合课程设计、项目实训或企业内部门禁考勤需求的原型搭建;Java、XML、SQL 等关键文件可直接导入 IDE 运行调试,Markdown 文档辅助梳理实现思路。
1. 考勤管理系统源码:学生项目里最容易被低估的 Java 练手资源
做 Java 课程设计或者毕业设计的人,十有八九会撞上考勤系统这个题目。这份源码就是一个典型的 Java Web 考勤管理系统,基于 JSP + Servlet + MySQL 实现,包含员工管理、部门管理、上下班打卡、考勤统计和月度报表导出几个模块。
它不像商城项目那么绕,但足够把 Java Web 开发里最核心的东西串一遍:数据库设计、会话管理、请求转发、SQL 聚合统计。适合两类人:一类是急着交课程设计的学生,拿回去改改就能跑;另一类是刚学完 Java 基础、想找一个完整项目练手的人,拆开源码看一遍,比看十篇教程都管用。
2. 技术栈与模块拆解:JSP + Servlet + MySQL 这条老链路为什么还在用
2.1 项目形态与选型理由
先说项目形态。这份源码是典型的 Java Web 三层结构:JSP 负责页面展示,Servlet 处理请求转发,JDBC 访问 MySQL 数据库。没有引入 Spring Boot,也没有用 MyBatis,整条链路全是原生 API,依赖只有 Tomcat 和 MySQL 驱动两个 jar 包。
很多读者拿到手第一反应是"这太老了"。我一般会劝一句:老链路恰恰是学习成本最低的链路。Spring Boot 帮你自动配置了太多东西,出了问题反而不容易定位;而 JSP + Servlet 的每一行代码都是显式的,请求怎么进来、怎么查库、怎么把结果塞进 request 再转发到页面,全程没有黑匣子。
选型理由就一条:这份资源的定位是"能跑、能改、能看懂",不是"生产级高并发"。用原生 Servlet 的好处是依赖少,只要 Tomcat 起来了就能跑,不用跟 Maven 仓库里的版本冲突较劲。
常见做法是:登录校验写在 Filter 里,业务逻辑写在 Servlet 的 doGet/doPost 里,数据库访问单独抽出 DBUtil 工具类。后面章节的代码走读会按这个结构展开。
2.2 数据库设计:三张核心表的字段与关联
考勤系统的数据库设计一般围绕三张表展开:员工表、部门表、考勤记录表。员工表挂在部门表下面,考勤记录表引用员工表的主键。这个资源里常见的建表 SQL 大概是这样的:
CREATE DATABASE attendance DEFAULT CHARACTER SET utf8mb4; USE attendance; CREATE TABLE dept ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL ); CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, dept_id INT NOT NULL, emp_no VARCHAR(20) NOT NULL UNIQUE, emp_name VARCHAR(30) NOT NULL, password VARCHAR(32) NOT NULL, role TINYINT DEFAULT 1 COMMENT '1-普通员工 0-管理员', FOREIGN KEY (dept_id) REFERENCES dept(id) ); CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, work_date DATE NOT NULL, check_in TIME, check_out TIME, status VARCHAR(10) DEFAULT '正常', UNIQUE KEY uk_emp_date (emp_id, work_date), FOREIGN KEY (emp_id) REFERENCES employee(id) );这三张表就是整个系统的地基。注意两个设计细节:一是 attendance_record 里加了唯一索引 (emp_id, work_date),目的是防止同一个人同一天插入两条打卡记录;二是 status 字段做了冗余存储,后续统计直接按状态分组,不用每次用上班和下班时间现算。
如果把这三张表画成关系图,就是典型的"部门 1 对多 员工、员工 1 对多 考勤记录"。部门表是独立的,员工表通过 dept_id 关联部门,考勤记录表通过 emp_id 关联员工。查询员工考勤的时候只需要一次 JOIN 就能把员工姓名和部门名称带上,这也是后面统计 SQL 能写短的前提。
初始化数据也是常见的一个坑。很多版本自带的初始化脚本只建表不插数据,登录页没有默认账号。建议手动插入一条管理员数据:工号 admin、密码 admin、role 设为 0,否则第一次登录会卡在"账号不存在"。
2.3 角色权限:管理员与普通员工的两套菜单逻辑
权限这块,原项目没有引入权限框架,就是用最简单的方式:employee 表里 role 字段区分管理员和普通员工,登录成功之后把角色值存进 Session。
管理员能访问员工管理、部门管理、考勤统计这些路径;普通员工只能看到自己的打卡入口和个人考勤记录。实现上常见做法是写一个权限拦截的 Filter,在请求到达 Servlet 之前先检查 Session 里的角色:
// AuthFilter.java 权限拦截核心逻辑 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(false); // 未登录一律踢回登录页 if (session == null || session.getAttribute("user") == null) { resp.sendRedirect(request.getContextPath() + "/login.jsp"); return; } // 已登录但角色不匹配,提示无权访问 String role = (String) session.getAttribute("role"); String uri = request.getRequestURI(); if (uri.contains("/admin/") && !"0".equals(role)) { request.setAttribute("msg", "没有管理员权限"); request.getRequestDispatcher("/error.jsp").forward(req, resp); return; } chain.doFilter(req, resp); }这段 Filter 的逻辑说明:第一层判断只解决"有没有登录",第二层判断解决"登录了但能不能进管理页面"。如果把这两层合并成一个 if,就会出现普通员工直接输入管理页面地址也能进去的漏洞。
参数说明:Session 里存的是 user 对象和 role 字符串,role 用 String 而不是 int,是为了和页面 JSTL 标签里的比较写法对齐。改成 int 也行,但所有比较的地方都要同步改,很容易漏。另一个容易踩的点是 request.getSession(false) 里面的 false,它表示拿不到 Session 时返回 null 而不是新建一个,配合后面的 null 判断正好覆盖未登录场景;如果误写成 getSession(true),匿名用户也会被强制创建 Session,拦截逻辑就废了。
3. 本地跑起来:JDK、MySQL、Tomcat 三件套的配置顺序
3.1 环境版本怎么选
先把环境定下来,再谈导入。这类老项目对版本敏感,版本不匹配的翻车现场我见过太多,给你一个不会踩雷的组合:
- JDK 8:不要拿 JDK 11 或更高跑老项目,JSP 编译和某些 JDBC 驱动在 JDK 9 之后行为有变化
- MySQL 5.7 或 8.0:8.0 需要改驱动类名和时区参数,细节见 3.3
- Tomcat 8.5:兼容性最好,Tomcat 9 对 JSP 的 EL 表达式支持有轻微差异
- IDE 用 Eclipse 或 IntelliJ IDEA 都行,关键是确认项目是什么工程结构
顺序上先装 JDK,再装 MySQL,最后解压 Tomcat。装完 Tomcat 先别急着往里面塞项目,先启动一次确认 8080 端口能正常访问。这里有一个新手常犯的错误:以为 Tomcat 一定要单独装。其实用 IDE 内置的 Tomcat 插件跑更方便,控制台日志和断点调试都更顺手。
版本选型的原则就一句话:和项目里 lib 目录下 jar 包的编译版本对齐。你可以在项目里找到 mysql-connector-java 的 jar,看它支持的最低 JDK 版本,再决定装哪个 JDK。JDK 和 Tomcat 都是用前先查版本,很多诡异报错都是版本不匹配引发。
3.2 导入工程与初始化数据库
拿到 zip 解压之后,常见做法是把整个文件夹拷到 Tomcat 的 webapps 目录下直接运行,但我不推荐这么干。更好的方式是导入 IDE,用 IDE 内置的 Tomcat 插件跑,能看到控制台日志和断点。
初始化数据库的步骤固定三步:
# 第一步:登录 MySQL mysql -uroot -p # 第二步:执行建库建表脚本(脚本文件一般在 db 或 sql 目录下) source /your/path/attendance.sql; # 第三步:验证表是否创建成功 USE attendance; SHOW TABLES;执行完这三步,数据库侧就绪。注意 source 命令后面要写文件的绝对路径,别用相对路径,否则会报 "file not found"。
导入 IDE 的时候有一个常见的坑:项目如果是 Eclipse 的 Dynamic Web Project 结构,直接 File -> Open 打开目录即可;如果拿到手是一个普通文件夹没有 .project 文件,要先转成 Maven 工程或者手动勾选 Web Facet,否则 IDE 不认它是 Web 项目,运行按钮是灰的。
我一般会先看一眼工程根目录下有没有 pom.xml。有 pom.xml 就用 Maven 方式导入,没有就用纯 Web 工程方式导入。两种方式对应的启动按钮不一样,混淆了会在后面部署阶段报各种奇怪错误。
3.3 数据源配置:jdbc.properties 逐项说明
老项目的数据源配置通常写在 src 目录下的 jdbc.properties 或 db.properties 里。打开之后你会看到四五行配置,每一行都别乱改:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/attendance?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456逐项说明:
- jdbc.driver:MySQL 5.7 用 com.mysql.jdbc.Driver,MySQL 8.0 必须改成 com.mysql.cj.jdbc.Driver
- jdbc.url:localhost 换成远程数据库 IP 就是连远程库;characterEncoding=utf8 是保中文不乱码的关键参数
- jdbc.username / jdbc.password:按本地 MySQL 的实际账号改;8.0 如果因为密码加密方式导致连接失败,要在 url 后面追加 serverTimezone=Asia/Shanghai
改完配置之后,重启 Tomcat 让配置重新加载。验证配置是否生效的方式很简单:随便打开一个需要查库的页面,如果页面能正常显示数据列表,说明数据源通了;如果报 SQLException 或 Connection refused,看 5.1 的排查思路。
还有一个细节:jdbc.password 明文写在 properties 文件里是课程设计项目的常态,别因为这个觉得项目有问题。生产环境用加密或环境变量,那是另一个话题。但要注意 properties 文件保存时编码必须是 ASCII 或 UTF-8 without BOM,否则中文注释会变成乱码甚至导致解析失败。
3.4 第一个冒烟测试:从登录页到主界面
数据库初始化完、Tomcat 启动成功之后,先做一个冒烟测试:打开浏览器访问 http://localhost:8080/项目名/login.jsp,用默认账号登录,看能不能正常跳转到主界面。
如果项目名带了中文或者空格,URL 里的路径会出问题,建议把 webapps 下的项目文件夹重命名成纯英文,比如 attendance。
冒烟测试通过之后,再逐个点一遍菜单:员工管理、部门管理、打卡、统计。这一步主要是确认每个 Servlet 的路由映射有没有配全。很多老项目的 web.xml 里配置了一堆 servlet-mapping,少配一条就会在点某个菜单时 404。
我用一个笨办法快速确认路由:把 web.xml 打开,把每个 url-pattern 和对应 Servlet 类名抄在一张纸上,然后逐个访问。抄一遍下来,整个项目的请求流转关系就清楚了,比反复点页面快得多。
提示:如果冒烟测试阶段就报 404,先别怀疑代码,优先检查访问路径的大小写。JSP 文件名和 Servlet 映射都是区分大小写的,login.jsp 写成 Login.jsp 直接 404。
4. 核心代码走读:登录、打卡与统计 SQL 的三个落点
4.1 登录逻辑:Session 与密码校验
登录是第一个值得逐行看的模块。常见写法是 LoginServlet 接收页面提交的 emp_no 和 password,拿着这两个参数去查 employee 表,查到就把用户对象放进 Session。
// LoginServlet.java 核心校验逻辑 String empNo = request.getParameter("empNo"); String pwd = request.getParameter("password"); // 查询用户,注意这里要用 PreparedStatement 而不是拼接 SQL String sql = "SELECT * FROM employee WHERE emp_no=? AND password=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, empNo); ps.setString(2, pwd); ResultSet rs = ps.executeQuery(); if (rs.next()) { // 登录成功:user 对象和 role 同时入 Session request.getSession().setAttribute("user", empNo); request.getSession().setAttribute("role", rs.getString("role")); resp.sendRedirect(request.getContextPath() + "/index.jsp"); } else { // 登录失败:回到登录页并携带错误提示 request.setAttribute("msg", "工号或密码错误"); request.getRequestDispatcher("/login.jsp").forward(req, resp); }逻辑说明:整个登录成败就取决于 rs.next() 是否为 true。这里有两个容易忽略的细节。
第一个细节是请求转发和重定向的区别。登录失败用 forward 回到 login.jsp,request 里的 msg 属性还能带上,登录页可以显示"工号或密码错误";登录成功用 sendRedirect 跳到 index.jsp,让浏览器地址栏变成 index.jsp,这样刷新页面不会重复提交表单。如果把两者搞反,会出现刷新一次就重复登录一次的怪现象。
第二个细节是密码校验。PreparedStatement 的 ? 占位符天然防 SQL 注入,老项目里如果看到字符串拼接的查询语句,建议顺手改掉。employee 表里 password 字段存的是明文,课程设计阶段不用纠结,生产环境必须做加盐哈希处理。
参数说明:empNo 和 password 的取值来自 request.getParameter,这两个参数名要和 login.jsp 表单里的 name 属性完全一致,差一个字母就取到 null。老项目里最常见的登录失败原因不是密码错,而是参数名拼写不一致。
4.2 上下班打卡:时间判定与去重
打卡模块是考勤系统的灵魂。常见做法是同一个 PunchServlet 处理上班和下班两个动作,通过页面按钮区分。时间判定逻辑是这样的:
// PunchServlet.java 打卡时间判定 Timestamp now = new Timestamp(System.currentTimeMillis()); java.sql.Date today = new java.sql.Date(now.getTime()); // 先查今天有没有打卡记录 String querySql = "SELECT check_in, check_out FROM attendance_record " + "WHERE emp_id=? AND work_date=?"; PreparedStatement ps = conn.prepareStatement(querySql); ps.setInt(1, empId); ps.setDate(2, today); ResultSet rs = ps.executeQuery(); if (!rs.next()) { // 今天第一次打卡,记为上班时间 insertSql = "INSERT INTO attendance_record(emp_id, work_date, check_in) VALUES(?,?,?)"; } else if (rs.getTime("check_in") != null && rs.getTime("check_out") == null) { // 已有上班时间但没有下班时间,补下班时间 updateSql = "UPDATE attendance_record SET check_out=? WHERE emp_id=? AND work_date=?"; } else { // 上下班都打过了,提示重复打卡 }逻辑说明:这个分支结构把一天内的打卡行为拆成三种状态——没打过、打了上班没打下班、都打过了。注意 check_out 为 NULL 的判断用 rs.getTime("check_out") == null,而不是 rs.getString(...).equals(""),因为数据库 TIME 字段为空时是 NULL 而不是空字符串。这是新人最容易写错的地方,写错了会导致已打过的下班卡被重复覆盖。
打卡去重靠的是 2.2 里那张表的唯一索引 uk_emp_date。即使代码逻辑漏了判断,数据库层也会拦住重复插入并抛 DuplicateKeyException。这个设计就是典型的前后端双保险:代码判断先拦一次,数据库唯一索引兜底。如果资源里的代码没有这个唯一索引,建议自己加上。
参数说明:System.currentTimeMillis() 取的是服务器时间。如果你的应用部署在云服务器上而用户在北京,时区不一致会导致打卡时间差 8 小时,解决办法见 5.3。另外 empId 这个变量来自登录 Session 里的用户信息,不要从前端传过来,否则任何用户都能伪造别人的打卡记录。
4.3 月度统计:一条 SQL 出报表
考勤统计页面是管理员最常用的模块。常见做法是选一个月度,然后按员工分组统计迟到、早退、正常天数:
SELECT e.emp_no AS 工号, e.emp_name AS 姓名, COUNT(*) AS 出勤天数, SUM(CASE WHEN r.check_in > '09:00:00' THEN 1 ELSE 0 END) AS 迟到天数, SUM(CASE WHEN r.check_out < '18:00:00' THEN 1 ELSE 0 END) AS 早退天数 FROM attendance_record r JOIN employee e ON r.emp_id = e.id WHERE DATE_FORMAT(r.work_date, '%Y-%m') = ? GROUP BY e.id, e.emp_no, e.emp_name;这个 SQL 的两个要点:一是 DATE_FORMAT 把日期字段格式化成 'YYYY-MM' 和传入参数做匹配,注意如果 work_date 列有索引,DATE_FORMAT 会导致索引失效,数据量大了之后建议改成范围查询 BETWEEN '2024-01-01' AND '2024-01-31';二是 SUM(CASE WHEN ...) 这种写法把迟到和早退天数一次算完,避免多次扫表。
参数说明:迟到阈值 09:00:00 和早退阈值 18:00:00 是写死的,不同公司上下班时间不同,改这两个字符串即可,但格式必须是 HH:mm:ss,少一位冒号都会导致比较失效。
分组这里有个细节:GROUP BY 后面写的是 e.id, e.emp_no, e.emp_name 三个字段。理论上按 e.id 分组就够了,但因为 SELECT 里查了 emp_no 和 emp_name,MySQL 在 ONLY_FULL_GROUP_BY 模式下要求这些字段都出现在 GROUP BY 里,否则直接报错。很多新手把 GROUP BY 缩写成只按 e.id 分组,然后在别人的机器上能跑、自己机器上报错,就是 MySQL 的 sql_mode 配置不同导致的。
统计结果在页面上的展示,常见做法是循环 ResultSet 拼 HTML 表格,或者用 JSTL 标签遍历 List。如果资源里用的是后者,你会发现代码量明显更少,这也是 JSP 页面里比 scriptlet 更推荐的写法。
5. 避坑排查:数据库连不上、乱码、时间错位的五个现场
老项目跑不起来,九成问题出在环境配置而不是代码本身。我把这类考勤系统最常见的五个报错现场整理出来,每个都按"现象、原因、解决"给出处理步骤,照着排查比瞎试快得多。
5.1 现象一:启动 Tomcat 报 ClassNotFoundException
现象:第一次启动 Tomcat 或访问任意页面时,控制台报 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,紧接着是一大串 Caused by,最后页面显示 500。报错位置通常定位在 DBUtil 工具类的静态代码块里 Class.forName(driver) 那一行。
原因:MySQL 驱动 jar 没有放进 WebContent/WEB-INF/lib 目录。IDE 的 build path 里加了 jar,编译能通过,但 Tomcat 运行时加载的是 WEB-INF/lib 下的 jar,两边不一致就会出现"编译过、运行炸"的迷惑现象。
解决:分三步走。第一步,确认 lib 目录下有没有 mysql-connector-java 的 jar,没有就下载对应版本放进去;第二步,右键项目 Clean,让 IDE 重新打包部署;第三步,检查 Project Structure 里的 Artifacts 配置,确认 lib 目录被标记为 jar 目录。多数情况做完第一步就好,后两步是防止 IDE 缓存捣乱。
5.2 现象二:页面中文全部变成问号
现象:登录页面正常,但员工姓名、部门名称等中文数据显示成 ????,或者向数据库插入中文后查出来是乱码。有些是页面显示乱码但库里正常,有些是页面正常但库里是乱码,两种情况原因不同。
原因:三个环节至少有一个字符集没对齐——数据库表字符集、JDBC 连接串、JSP 页面编码。页面乱码而库正常,问题出在 JSP 编码或 response 编码;库乱码则基本是 JDBC 连接串缺了 characterEncoding。
解决:按顺序检查,先把 jdbc.url 加上 characterEncoding=utf8,再确认建表时用了 utf8mb4,最后在 JSP 页面顶部写 <%@ page contentType="text/html; charset=UTF-8" %>。三个地方统一成 UTF-8 之后重启,乱码问题基本消失。还有一个冷门原因:properties 文件保存时被 IDE 转成了 GBK 编码,读出来的连接串本身就带乱码,这种情况把文件重新保存为 UTF-8 即可。
5.3 现象三:打卡时间比本地时间慢 8 小时
现象:用户上午 9 点打卡,数据库里 check_in 显示 01:00:00,整个系统的打卡时间全部偏移 8 小时。
原因:服务器或 MySQL 的时区设置是 UTC,而用户在东八区。MySQL 8.0 的驱动默认读取 serverTimezone 参数,不指定就按 JVM 默认时区算,两边不一致就出现 8 小时偏移。
解决:在 jdbc.url 后面追加 serverTimezone=Asia/Shanghai,同时检查操作系统时区。如果项目里取时间用的是 new Date(),取到的是 JVM 时区的时间,还要看 Tomcat 启动的 JVM 参数有没有 -Duser.timezone=Asia/Shanghai。改完配置必须重启 Tomcat,光改配置文件不重启等于白改。
5.4 现象四:导出报表 Excel 打开是乱码
现象:管理员导出月度考勤报表,用 Excel 打开之后姓名列全是乱码,或者干脆打不开提示格式错误。
原因:很多老项目导出 Excel 用的是往 response 里直接写 HTML 表格的伪 Excel 方案,文件后缀是 .xls 但内容其实是 HTML。如果 response 没设置正确编码,浏览器按默认编码解析,中文就乱码。
解决:在导出 Servlet 里显式设置 response.setCharacterEncoding("UTF-8"),并且给 Content-Type 加 charset。更稳妥的方案是把伪 Excel 换成真正的 Excel 导出组件,但课程设计阶段先调编码就够了。注意导出的文件名如果是中文,要对文件名做 URL 编码处理,否则浏览器端显示文件名乱码。
5.5 现象五:8080 端口被占用导致启动失败
现象:启动 Tomcat 时控制台报 Port 8080 required by Tomcat at localhost is already in use,IDE 弹窗提示端口冲突,项目根本起不来。
原因:要么之前启动的 Tomcat 实例没关闭干净,要么其他程序占用了 8080 端口。常见的是前一次异常退出,IDE 里的 Tomcat 进程还挂在后台。
解决:先关掉 IDE 里所有 Tomcat 实例,再在命令行执行 netstat -ano | findstr 8080 查占用进程的 PID,然后 taskkill /PID 对应PID /F。如果端口是被别的服务固定占用,也可以直接改 Tomcat 的 server.xml 里的 Connector port,把 8080 改成 8081,但要记得访问 URL 里的端口号同步改掉。
提示:排查顺序建议固定为"端口、驱动、字符集、时区"。这个顺序覆盖了考勤系统九成以上的启动和运行报错,每次照着走一遍,比东查一下西查一下省时间。
6. 二次开发方向:把考勤系统改造成 Spring Boot 的迁移路线
6.1 从 Servlet 到 Controller:先跑通再改造
如果你不想停留在"能跑"的阶段,最值得做的二次开发是把它迁移到 Spring Boot。迁移的思路不是重写,而是逐层替换。先建一个 Spring Boot 工程,引入 web 和 mybatis 两个 starter,然后把 LoginServlet 里的逻辑原样搬进 @Controller:
@PostMapping("/login") public String login(String empNo, String password, HttpSession session) { Employee emp = employeeMapper.findByEmpNoAndPassword(empNo, password); if (emp != null) { session.setAttribute("user", emp.getEmpNo()); session.setAttribute("role", emp.getRole()); return "redirect:/index"; } return "redirect:/login?msg=error"; }这里的 employeeMapper 是 MyBatis 的 Mapper 接口,SQL 用注解直接写在方法上。迁移时注意两点:一是 HttpSession 的用法不变,Spring MVC 可以直接注入 HttpSession;二是原来 Filter 里的权限判断,改造成 Spring 的 HandlerInterceptor,逻辑几乎一模一样。
6.2 用接口清单验证迁移结果
迁移完别急着收工。我的习惯是列一份接口清单:登录、打卡、统计、导出,逐个对比迁移前后的输出。从那次以后,我每次拿到这类老项目,都先花十分钟把数据库脚本和配置参数过一遍,再启动项目。"先跑通、再读码、后改造"这个顺序帮我省了大量查错时间。希望帮到你。
本文还有配套的精品资源,点击获取