☰
JavaWeb运动健康管理系统源码与数据库落地实战
2026/10/29 11:33:42 网站建设 项目流程

简介:这份基于JavaWeb的运动与健康管理系统源代码与数据库包,面向JavaWeb课程设计、毕业设计及入门进阶学习者,围绕运动记录、健康监测与数据分析等需求提供可运行的项目参考。压缩包为rar格式,共707个文件、约9.59MB,以jsp页面、java源码、class编译文件、js与css静态资源为主,并包含sql数据库脚本和jar依赖,能够支撑Web层、业务层与数据层的对照阅读。已有2020人学习,说明其作为教学案例具有一定参考价值。读者可从中了解用户管理、运动数据录入、健康指标跟踪、趋势统计与社区互动等模块的实现思路,并对照用户表、运动记录表、健康指标表等设计理解数据库建模与初始化脚本的用法,适合用作课程实践、毕业设计原型及JavaWeb综合练习的参考资料。

1. 先看能跑的那条链路:JavaWeb 运动健康管理系统源码与数据库到底给了什么

把 javaweb 运动与健康管理系统源代码和数据库拿到手,第一件事不是急着改页面,而是确认它能不能在本机跑出一条完整链路:登录、录入一次运动记录、看到健康指标变化、再在统计页对上数。这个资源的价值不在功能堆得多,而在于它把用户、运动记录、健康指标、计划和统计这些模块,用一套能拆改的 JavaWeb 工程串了起来,数据库脚本也一并给出。适合课程设计收尾、项目底稿、或者小团队做健康类后台原型的人;不适合想直接拿成品上线的人,权限、审计、并发和数据口径都要自己补。

2. 从压缩包到登录页:环境、目录和数据库连接的落地顺序

JavaWeb 工程对版本和路径比一般项目敏感,Servlet API、JDBC 驱动、数据库字符集三者只要有一个不对,表现就是 404、500 或者登录后空白页。拿到源码包后,我一般按“先反推环境,再扫目录,再导库改连接,最后做首登验证”的顺序走,不先动业务代码。这样做的原因是:业务代码可以慢慢改,环境链路必须先闭环,否则后面每改一个页面都像在黑匣子里猜。

2.1 先定运行环境:JDK、容器和数据库按源码包反推

不要凭经验直接装一套最新环境。先看工程里的编译配置和依赖 jar,反推它需要的 JDK 范围、Servlet 容器类型和数据库驱动。常见做法是先找pom.xml、build.gradle、WEB-INF/web.xml和WEB-INF/lib,这四个位置基本能把技术栈交代清楚。

# 先找构建文件和 Web 描述文件,确认工程是 Maven 还是普通 Web 目录 find . -maxdepth 4 \( -name "pom.xml" -o -name "build.gradle" -o -name "web.xml" \) -print # 列出 WEB-INF/lib 下的 jar,按文件名判断数据库驱动和框架 find . -maxdepth 5 -path "*WEB-INF/lib*" -name "*.jar" -print | sed 's#.*/##' | sort

第一段命令的-maxdepth 4是防止在依赖缓存里翻太久,\( ... \)把多个条件合成一组。第二段命令用sed去掉路径只留 jar 名,方便看mysql-connector、servlet-api、spring-web这类关键词。参数上不需要记太多,关键是看 jar 名和pom.xml里的编译插件;如果只有WEB-INF/lib没有构建文件,就按普通 Web 项目处理,编译输出放到WEB-INF/classes。JDK 不要盲目追高,老工程用高版本 JDK 编译时,泛型、字节码和 Servlet 包名都可能出问题,先按源码能接受的版本跑通,再考虑升级。

数据库方面,这套资源带的是数据库脚本,常见搭配是 MySQL。先确认本机服务能连上,再决定是新建库还是复用已有库。容器方面,JavaWeb 项目常见部署方式是打 war 包放进 Servlet 容器,或者用 Maven 插件启动。不要一上来就改端口和上下文路径,先按默认跑一次,把错误暴露出来。

2.2 目录结构扫一遍:web.xml、pom.xml、lib 和 sql 各看什么

目录扫读不是看热闹,而是找四个东西:入口、配置、依赖和建库脚本。web.xml里看welcome-file、servlet-mapping、filter和context-param;pom.xml里看打包方式、编译级别和依赖范围;WEB-INF/lib里看驱动版本;sql或db目录里看库名、表名和初始账号。很多启动失败不是代码错,而是web.xml里配的 Servlet 类名和实际包名对不上,或者welcome-file指向了一个不存在的页面。

# 先扫 SQL 里的库名、表名和初始数据,别直接无脑导入 grep -n -E "CREATE DATABASE|USE |CREATE TABLE|INSERT INTO" db/*.sql | head -n 60

这段命令用grep -E同时匹配建库、切库、建表和插入语句,head -n 60只先看前 60 行,避免大脚本刷屏。参数上,-n显示行号,方便回到文件里定位。看到CREATE DATABASE就记下库名,看到USE就确认脚本会不会自己切库,看到INSERT INTO就找初始账号和角色数据。这里有个血泪经验:有些脚本默认库名和配置文件里的库名不一致,导入时没报错,启动后却一直提示表不存在。先把库名、表名、初始账号这三样抄到一张纸上,后面改配置会少走很多弯路。

还要看过滤器配置。运动健康系统通常有登录过滤器和编码过滤器,如果filter-mapping的url-pattern写成/*,但过滤器类没编译进WEB-INF/classes,启动就会 500。遇到这种问题不要先改 Java 代码,先看容器日志里第一个Caused by,它通常会把“类找不到”还是“配置解析失败”说得很清楚。

2.3 导入数据库与改连接串:一次把库、账号、编码对齐

导库这一步最容易出现“脚本执行成功,但系统读出来是乱码”或者“本地能连,部署后连不上”。我一般先建库并指定字符集,再导入脚本,最后用一条查询确认表数量和数据量。库名按 SQL 脚本里的名字改,不要自己另起一个然后忘了改连接串。

# 创建库并指定字符集,库名按 sql 脚本里的名字改 mysql -uroot -p -e "CREATE DATABASE sport_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入表结构和初始数据,路径换成解压后的 sql 文件 mysql -uroot -p sport_health < ./db/sport_health.sql # 校验表数量和数据量,确认脚本真的进去了 mysql -uroot -p -e "USE sport_health; SHOW TABLES; SELECT COUNT(*) AS user_count FROM sys_user;"

第一段命令里的utf8mb4是为了兼容中文和特殊字符,utf8mb4_general_ci是常见排序规则。第二段命令把脚本导入指定库,<是输入重定向,路径要换成实际文件。第三段命令先切库再看表和用户数,sys_user是示例表名,实际按脚本里的表名替换。参数上,-uroot只是本地示例,真实环境不要长期用 root 跑应用;导入前最好确认脚本里没有DROP DATABASE这类破坏性语句。

连接串要跟驱动匹配。驱动 jar 是较新的mysql-connector-j时,类名常见是com.mysql.cj.jdbc.Driver;老驱动则是com.mysql.jdbc.Driver。不要凭感觉写,打开WEB-INF/lib看 jar 名,或者看原项目配置文件里已经写好的类名。下面是一份常见的jdbc.properties写法,参数按本机改。

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/sport_health?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=换成你的密码

jdbc.url里的useUnicode和characterEncoding负责字符编码,serverTimezone负责时间字段的时区解释,useSSL=false只适合本地或内网调试,公网部署要按实际证书配置。allowPublicKeyRetrieval=true是部分新驱动连接本地库时的兼容参数,不是所有环境都需要。连接池参数如果原项目有initialSize、maxActive、maxWait,先不要调大,按默认跑通后再根据并发改。> 注意:配置文件可能不止一份,src下和WEB-INF/classes下各有一份时,以实际加载到的那份为准,改完记得重新编译或重新部署。

2.4 启动与首登验证:别只看首页能不能开

启动后首页能打开,不代表系统能用。首登验证至少要走四步:用初始账号登录、打开个人档案、录入一条运动记录、回到统计页看有没有数。首页可能只是静态页,真正的过滤器、数据库连接和业务逻辑在登录后才触发。

# 有 Maven 构建文件时先编译打包,产物通常在 target 下 mvn -q clean package -DskipTests # 没有 Maven 时,确认编译输出和 webapp 目录的对应关系 find . -maxdepth 4 -type d \( -name "classes" -o -name "webapp" -o -name "WebContent" \) -print

第一段命令用-q减少日志噪音,-DskipTests先跳过测试,目的是尽快拿到可部署产物。第二段命令找编译输出和 Web 根目录,普通 Web 项目常见是WebContent或webapp,编译后的类要放到WEB-INF/classes。如果访问路径是http://127.0.0.1:端口/项目名/,注意末尾斜杠和上下文路径;war 包名通常会变成上下文路径,别只访问根路径然后怀疑代码坏了。

首登时如果提示账号密码错误,先回 SQL 里看初始账号是不是明文、是不是被脚本注释掉了、密码字段是不是哈希后写入。不要直接改登录逻辑绕过校验,那会把后面的权限问题一起掩盖掉。

3. 数据模型别急着改:运动记录、健康指标和计划表的字段边界

这套系统的数据库不是“能存就行”,运动健康数据有明确的时间属性和单位属性。运动记录是明细,健康指标是档案快照,计划是目标约束,统计是从明细算出来的结果。选型上,我倾向于把明细和聚合分开:明细表保证可追溯,统计查询实时算或者按需落日报表,不要把“本周总时长”直接写回运动记录表。先读 SQL 再改 Java,能避免很多“页面显示对、导出对不上”的翻车。

3.1 用户、角色与档案表:先把权限和一对一关系定住

用户表是权限边界,档案表是健康数据边界。用户表里不要存明文密码,角色用编码而不是中文名称;档案表和用户表是一对一关系,用user_id做主键可以天然防重复。下面是一组常见建表写法,表名按源码里的实际名字替换。

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(100) NOT NULL, role_code VARCHAR(20) NOT NULL DEFAULT 'USER', status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE health_profile ( user_id BIGINT PRIMARY KEY, height_cm DECIMAL(5,2) NOT NULL, gender TINYINT NOT NULL, birth_year SMALLINT, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_profile_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

password_hash长度给到 100,是为了兼容常见哈希串;role_code用USER、ADMIN这类编码,避免中文角色名在权限判断时出现编码问题。height_cm用DECIMAL(5,2),最大能存 999.99,实际业务限制在 80 到 250 厘米即可。gender和birth_year是档案字段,不要塞进运动记录表。外键fk_profile_user能防孤儿档案,但有些部署环境会关闭外键检查,常见做法是保留外键用于开发期校验,同时在服务层再做一次用户存在性判断。

3.2 运动记录表:时间、类型、时长、消耗怎么落字段

运动记录表的关键是“开始时间”和“用户 + 时间”的索引。时长用整数分钟,热量用两位小数,运动类型先用文本或编码。索引要支持按用户和时间范围查,因为统计页最常见的就是“查某个用户某段时间的记录”。

CREATE TABLE sport_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, sport_type VARCHAR(30) NOT NULL, duration_min INT NOT NULL, calorie DECIMAL(8,2) NOT NULL DEFAULT 0, start_time DATETIME NOT NULL, note VARCHAR(255), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_record_user_time (user_id, start_time), KEY idx_record_type_time (sport_type, start_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

duration_min用INT存分钟,避免用小数秒再换算;calorie用DECIMAL(8,2),单位是千卡,不要一会儿存千卡一会儿存焦耳。start_time是运动开始时间,不是记录创建时间,统计按它分组。idx_record_user_time是联合索引,顺序是“用户在前、时间在后”,适合WHERE user_id = ? AND start_time >= ?这种查询。sport_type如果后续要做字典管理,可以再加一张sport_type表,把文本换成编码;源码包常用文本,二次开发时再换也不迟。

3.3 健康指标表:体重、体脂、BMI 的精度和单位

健康指标表最容易出问题的是单位和精度。体重用千克,体脂率用百分比数值,BMI 用两位小数,日期用DATE保证一天一条。唯一键user_id + metric_date可以防重复录入,避免统计时同一人同一天出现两条体重。

CREATE TABLE health_metric ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, metric_date DATE NOT NULL, weight_kg DECIMAL(5,2) NOT NULL, body_fat_pct DECIMAL(4,1), bmi DECIMAL(4,2), waist_cm DECIMAL(5,1), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_metric_user_date (user_id, metric_date), KEY idx_metric_user_date (user_id, metric_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

weight_kg的DECIMAL(5,2)最大 999.99,实际代码里限制在 20 到 300 更合理;body_fat_pct存 18.5 表示 18.5%,不要存 0.185;bmi存 22.35 这种两位小数。metric_date用DATE而不是DATETIME,是为了让“一天一条”的约束成立。如果业务需要一天多条快照,把唯一键改成user_id + measured_at,并在页面上明确展示最新一条。> 提示:改表结构前先备份,尤其是已经录入过数据的库;精度从低改高通常安全,从高改低会截断小数。

3.4 计划与统计表:别把聚合结果当明细存

计划表存的是目标,不是结果。目标类型可以是“每周时长”“每日步数”“体重目标”,目标值用DECIMAL,起止日期用DATE。统计查询从运动记录和健康指标实时聚合,除非报表数据量大到需要缓存,否则不要先建一堆汇总表。

CREATE TABLE sport_plan ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, plan_name VARCHAR(80) NOT NULL, target_type VARCHAR(20) NOT NULL, target_value DECIMAL(10,2) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1, KEY idx_plan_user_date (user_id, start_date, end_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

统计查询按用户和时间范围聚合,时间条件用左闭右开,避免跨天重复计算。下面这条 SQL 返回每日总时长和总热量,参数依次是用户 ID、开始时间、结束时间。

SELECT r.user_id, DATE(r.start_time) AS stat_day, SUM(r.duration_min) AS total_min, SUM(r.calorie) AS total_calorie FROM sport_record r WHERE r.user_id = ? AND r.start_time >= ? AND r.start_time < ? GROUP BY r.user_id, DATE(r.start_time) ORDER BY stat_day DESC;

DATE(r.start_time)会把时间截断到天,适合按天展示;如果数据量大,GROUP BY里对时间做函数可能用不上索引,但WHERE里的范围条件仍然能先过滤。真到性能瓶颈时,再加report_daily汇总表,并记录生成时间,不要直接把汇总值写回明细表。计划表和统计查询之间是“目标 vs 实际”的关系,页面上做对比即可,别把目标值当成实际完成值存。

4. 功能闭环怎么做:录入校验、BMI 计算和统计查询

能登录只是第一步,运动健康系统的闭环是“录入一条记录 → 算出消耗和 BMI → 在统计页看到变化 → 回看历史”。这条链路里,前端校验只能算提示,后端校验才是边界;计算类代码最怕单位混用;统计接口最怕时间范围写错。下面按录入、计算、统计三段落地。

4.1 录入链路:前端参数、后端校验和事务边界

录入接口不要信任前端传来的用户 ID、热量和时长。用户 ID 从会话里取,热量由后端算,时长做范围校验,开始时间不能晚于当前时间。插入运动记录和更新当天健康指标要在同一个事务里,避免记录进去了、指标没更新。

public class SportRecordService { private final SportRecordDao recordDao; private final HealthMetricDao metricDao; public void addRecord(Long sessionUserId, SportRecordForm form) throws BizException { if (sessionUserId == null) { throw new BizException("用户未登录"); } if (form.getSportType() == null || form.getSportType().trim().isEmpty()) { throw new BizException("运动类型不能为空"); } if (form.getDurationMin() == null || form.getDurationMin() <= 0 || form.getDurationMin() > 600) { throw new BizException("时长需在 1 到 600 分钟之间"); } if (form.getStartTime() == null || form.getStartTime().after(new Date())) { throw new BizException("运动时间不能晚于当前时间"); } SportRecord record = new SportRecord(); record.setUserId(sessionUserId); record.setSportType(form.getSportType().trim()); record.setDurationMin(form.getDurationMin()); record.setCalorie(CalorieCalculator.byMet(form.getMet(), form.getWeightKg(), form.getDurationMin())); record.setStartTime(form.getStartTime()); recordDao.insert(record); metricDao.upsertDailyMetric(sessionUserId, DateUtils.dayOf(form.getStartTime())); } }

这段代码的边界很明确:sessionUserId不从前端表单取,防止越权;durationMin限制在 1 到 600,超出就拒绝;startTime不能晚于当前时间,避免录入未来数据。CalorieCalculator.byMet的参数是 MET 系数、体重千克和分钟数,热量由后端统一算,前端传的热量字段只做展示参考。recordDao.insert和metricDao.upsertDailyMetric应该放在同一个事务里,如果用 Spring 就在服务方法上加事务注解,如果用普通 JDBC 就手动setAutoCommit(false)、提交、回滚。> 注意:upsertDailyMetric不是必须更新体重,它只是标记当天有运动数据;如果业务不需要,删掉这一步,别为了“看起来完整”引入不必要的写操作。

4.2 BMI 与热量计算:公式、单位和边界值

BMI 的公式是体重千克除以身高米的平方,身高在页面上通常填厘米,进计算前要除以 100。热量常见估算方式是 MET × 体重千克 × 小时数,时长从分钟换成小时。两个计算都要用BigDecimal,不要用float直接除,不然小数位会出现 22.349999 这种结果。

public final class BmiCalculator { private BmiCalculator() {} public static BigDecimal bmi(BigDecimal heightCm, BigDecimal weightKg) { if (heightCm == null || weightKg == null) { throw new IllegalArgumentException("身高体重不能为空"); } if (heightCm.compareTo(BigDecimal.valueOf(80)) < 0 || heightCm.compareTo(BigDecimal.valueOf(250)) > 0) { throw new IllegalArgumentException("身高单位是厘米,范围 80 到 250"); } if (weightKg.compareTo(BigDecimal.valueOf(20)) < 0 || weightKg.compareTo(BigDecimal.valueOf(300)) > 0) { throw new IllegalArgumentException("体重单位是千克,范围 20 到 300"); } BigDecimal heightM = heightCm.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP); return weightKg.divide(heightM.multiply(heightM), 2, RoundingMode.HALF_UP); } }

heightCm的单位是厘米,weightKg的单位是千克,divide的第二个参数是保留小数位,第三个参数是舍入方式。先做范围校验再计算,能挡住大部分脏数据。BMI 结果只做展示和趋势对比,不要在前端写死分类结论;如果页面要显示偏瘦、正常、偏重,用一个配置表或枚举维护,别散落在 JSP 和 Java 里。

热量计算同理,MET 系数从运动类型配置里取,不要把所有运动硬编码在工具类里。byMet返回千卡,保留两位小数;如果页面要显示“约消耗”,可以在前端再格式化,但数据库里存计算后的值。

4.3 统计接口:分页、时间范围和空数据

统计接口最常见的错误是时间范围写成>= 开始 AND <= 结束,导致结束当天多算一秒;或者分页参数没校验,LIMIT后面拼了负数。下面这条 SQL 按天聚合,时间条件左闭右开,分页参数用占位符。

SELECT DATE(start_time) AS stat_day, COUNT(*) AS times, SUM(duration_min) AS total_min FROM sport_record WHERE user_id = ? AND start_time >= ? AND start_time < ? GROUP BY DATE(start_time) ORDER BY stat_day DESC LIMIT ? OFFSET ?;

四个参数依次是用户 ID、开始时间、结束时间、每页条数、偏移量。结束时间传“下一天零点”而不是“当天 23:59:59”,这样能把当天全部记录包进去,又不会跨天重复。LIMIT ? OFFSET ?在数据量大时深分页会变慢,可以改成按时间游标翻页;源码包如果已经写了PageHelper之类的分页,先按原方式跑通,不要为了优化把接口改乱。

空数据时返回空数组,不要返回null,前端图表拿到null容易直接报错。统计页还要处理“有记录但没有健康指标”的情况,BMI 趋势图可以断点显示,不要用 0 填充,0 和“没测”是两回事。

5. 避坑排查:404、乱码、越权和统计对不上的五条记录

这套系统真正让人翻车的地方,往往不是业务代码写得多复杂,而是环境、编码、权限和口径。下面五条是我在类似 JavaWeb 运动健康项目里踩过的典型坑,每条按“现象 → 原因 → 解决”写,遇到问题时可以按顺序对照。

5.1 启动与连接类:404、500、连接池耗尽

现象一:首页或登录页 404,容器日志里没有业务异常。
原因通常不是代码没编译,而是访问路径不对。war 包名会变成上下文路径,web.xml里的welcome-file可能指向login.jsp,但你访问的是根路径;或者项目部署到了容器的webapps下,实际路径多了项目名。解决时先看容器日志里有没有“Deployment of web application archive”这类记录,确认上下文路径,再访问/项目名/。如果是 Maven 插件启动,看控制台打印的端口和 contextPath。不要急着改web.xml,先把路径对准。

现象二:启动报 500,日志最后一行是No suitable driver或连接超时。
原因可能是驱动类名和 jar 不匹配、连接串库名写错、数据库没启动、连接池最大连接数太小。排查时不要从日志最后一行倒着猜,先找第一个Caused by。

# 看容器日志里第一个 Caused by,不要从最后一行倒着猜 tail -n 300 logs/catalina.out | grep -n -A 6 "Caused by"

tail -n 300取最近 300 行,grep -n -A 6显示行号和后面 6 行上下文。第一个Caused by往往才是根因。驱动问题就看WEB-INF/lib里的 jar 和jdbc.driver是否对应;连接超时就看数据库服务、端口、防火墙和连接串;连接池耗尽就看有没有连接没关闭,先临时调大maxActive只能缓解,不能替代关闭连接。

5.2 数据与权限类:乱码、越权、统计偏差

现象三:表单提交中文变成问号,数据库里也是乱码。
原因通常是三处编码不一致:页面请求编码、数据库和表字符集、JDBC 连接串。解决时先确认库和表是utf8mb4,再确认连接串带useUnicode=true&characterEncoding=utf8,最后看有没有编码过滤器。过滤器要放在登录过滤器前面,并且只对请求做一次setCharacterEncoding。如果页面是 JSP,确认pageEncoding和contentType一致。改完要重启容器,仅刷新页面不一定生效。

现象四:把 URL 里的记录 ID 改成别人的,能看到别人的运动记录。
原因是查询只按id查,没有带当前登录用户条件;或者更新时只校验了记录存在,没校验归属。解决时在 SQL 的WHERE里同时带上user_id,在服务层再做一次归属校验。

// 不要只按前端传来的 id 更新,先按会话用户过滤 Long sessionUserId = (Long) request.getSession().getAttribute("userId"); SportRecord record = recordDao.findByIdAndUser(recordId, sessionUserId); if (record == null) { throw new BizException("记录不存在或无权操作"); }

findByIdAndUser的 SQL 要写成WHERE id = ? AND user_id = ?,两个参数都传。只在 Java 里查出来再if判断也可以,但 SQL 层过滤更稳,能避免后续有人绕过服务层直接调 DAO。列表页、详情页、导出接口都要走同一套归属校验,不要只修一个入口。

现象五:统计页总时长比明细多,或者跨天记录被算重。
原因可能是同一条记录被重复插入、时间范围用了<= 结束时间、时区导致日期偏移、时长单位有的是分钟有的是小时。解决时先对账:查明细总数和统计总数,看差在哪一天;再查时间字段的时区配置;最后看录入接口有没有防重复提交。统计 SQL 统一用左闭右开,结束时间传下一天零点。如果业务允许同一天多次录入,唯一键不要加在start_time上,而是加在“用户 + 运动类型 + 开始时间”上,或者用前端一次性 token 防重复提交。> 提示:排查统计问题时,先把时区、单位、重复记录三件事排除,再怀疑聚合 SQL。

6. 交付前的一轮回放:用三条数据验证源码和数据库是否真能改

最后一章落到验收。不要只看“能启动”,用三条数据回放一遍:新建一个用户,录入两条不同时间的运动记录,修改一次体重,再查统计和档案。回放时把每一步的预期结果写下来,失败先看对应日志和 SQL,不要直接改业务代码。

回放动作预期结果失败先看
新建用户并登录能进入首页,角色菜单正确用户表初始数据、密码字段、登录过滤器
录入两条运动记录列表按时间倒序,热量有值录入校验、热量计算、事务
修改体重并查看 BMI档案更新,BMI 按新体重重算档案表唯一键、BMI 计算单位
查按天统计总时长等于两条记录之和时间范围、时区、重复记录
导出或刷新统计页数据不丢,空数据不报错分页参数、空值处理

回放通过后,再谈二次开发。小技巧是:每次改表结构前先跑一遍这三条数据,改完再跑一遍,比只看编译通过靠谱。从那以后我每次拿到这类 JavaWeb 运动与健康管理系统源代码和数据库,都先按三条数据回放一遍,再谈改功能。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询