news 2026/9/14 4:16:30

基于Java的体重记录APP源码设计:从数据模型到趋势算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Java的体重记录APP源码设计:从数据模型到趋势算法

简介:这是一份基于Java开发的体重记录APP完整设计源码,面向移动应用开发者、Java学习者及健康管理类产品设计人员,用于掌握从数据存储、界面交互到图表展示的完整实现思路。资源包共257个文件,主要包括175个Java源文件、32个XML配置文件、12个PNG图片、10个字体文件及Gradle构建脚本等,另附属性文件、Markdown说明与LICENSE,项目结构清晰,便于按模块阅读与二次开发。压缩包大小约3.9MB,轻量易部署。作者xyq2024,已有313人浏览学习。源码不仅覆盖体重数据的新增、历史查询与目标管理,还涉及多设备数据同步思考、隐私安全设计等扩展方向;通过阅读Java核心逻辑与XML布局配置,可快速理解该APP项目的分层架构与工程组织方式,项目对异常输入、数据校验与界面刷新等细节均有处理,适合作为课程设计、毕业设计或商业产品原型的参考基础。

1. 基于Java开发的专业体重记录APP设计源码:先立住边界

看到「基于Java开发的专业体重记录APP设计源码」这个标题,第一反应容易走偏成「写个计步器」或「套个Android壳」。实际上,体重记录APP的核心难度不在录入界面,而在数据模型和趋势计算:一个人一天可能称三次体重,早晚差出两公斤,直接存原始值画折线图,得到的是锯齿而不是趋势;不处理生理性波动,提醒和目标预测全是噪音。这套方案把技术栈钉在Java上,后端用Spring Boot提供HTTP接口,前端只做展示,数据全部落MySQL,源码按「测试、接口、算法、数据表」四层拆开。适合想自己掌握全链路、或者拿来做课程设计和面试项目的人。

2. 体重记录APP的Java后端选型:把「记录」变成可维护的数据模型

2.1 功能拆解:记录、趋势、目标、提醒四件事

体重记录APP的「专业」体现在这里:不是把数字存下来,而是让数字产生决策。功能上拆成四块。

记录是唯一入口,用户在任意时间点提交体重和体脂率,后端做合法性校验后入库;趋势是把散点变成可读信息,常见做法是同时算移动平均和每日变化量;目标是从「当前值」到「目标值」的路径管理,需要拆成每周建议减重速率;提醒基于规则触发,体脂率异常、久未记录、连续N天趋势向上,分别对应不同文案。

这四个功能决定了两件事:第一,数据表必须区分「原始记录」和「统计结果」,不能在业务代码里临时聚合大表;第二,算法要放在Service层并配单元测试,因为体重数据噪音大,公式错了界面上是看不出来的。多数刚起步的Java项目死在第三步,以为数据库里有一张表就够了,结果业务逻辑越写越厚,最后Controller里全是循环和if。

2.2 体重数据表设计:从一天一条到一天多条

先给建表语句,这是整套源码的地基。以MySQL为例。

CREATE TABLE user ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, nickname VARCHAR(32) NOT NULL, height_cm DECIMAL(5,2) NOT NULL COMMENT '身高单位厘米,保留两位', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE measurement ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, weight_kg DECIMAL(5,2) NOT NULL COMMENT '体重单位公斤', body_fat_rate DECIMAL(4,2) NULL COMMENT '体脂率可选', note VARCHAR(255) NULL COMMENT '备注:如早晨空腹、运动后', measured_at DATETIME NOT NULL COMMENT '称重时间,APP端传入,不用服务器时间', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_time (user_id, measured_at), CONSTRAINT fk_measurement_user FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体重原始记录表';

几个参数设计的理由。

体重和体脂率必须用DECIMAL而不是FLOAT,体重计输出一位小数,DECIMAL(5,2)足够,避免浮点误差在趋势计算里被放大。measured_at用APP端传入的时间而不是数据库的CURRENT_TIMESTAMP,这是称重数据的特殊性:用户可能补录昨天早上的体重,如果统一落服务器时间,趋势曲线排序就错了。联合索引(user_id, measured_at)保证查询某个用户时间范围内的原始记录走索引,这是列表页和趋势计算最频繁的查询路径。

这张表刻意不做「一天一条」的唯一约束,同一个用户一天多次称重是合理行为,去重和过滤交给算法层,不在数据库层硬卡。

2.3 为什么后端选Spring Boot + MyBatis而不是别的

技术选型直接决定源码的交接成本和面试说服力。这个项目用Spring Boot 2.x + MyBatis,理由有三条。

Java生态里Spring Boot的自动配置能省掉大量XML,一个spring-boot-starter-web起步,内嵌Tomcat,java -jar就能跑,这对「设计源码」类项目最重要,别人拿到代码五分钟内能启动,才谈得上读源码。持久层选MyBatis而不是JPA,看中的是SQL可控:体重趋势、周统计、连续未记录天数这类查询,SQL会越来越复杂,MyBatis里可以逐条调优,换成JPA的自动JPQL反而不容易定位问题。

第三点容易被忽略:MyBatis的源码规模在Java持久层框架里相对可读,Mapper代理、SqlSession、插件机制三个核心链路能单独拆出来读。面试谈项目时,从「用了MyBatis」引申到「MyBatis怎么给Mapper接口生成代理类」,比背八股文有说服力得多。

与之相对,JPA在表关系复杂的ERP系统里优势明显,但体重记录只有两张主表和几个聚合查询,用JPA的实体关系映射属于给自己加戏。记住这个判断标准:实体关系多、写多读少用JPA;聚合查询多、SQL要精调用MyBatis。

3. 用Spring Boot把体重记录APP跑起来:最小可运行源码

3.1 项目结构与Maven依赖

源码工程建议按功能分包,不按技术层分包。

com.example.weight ├── WeightApplication.java ├── controller │ └── MeasurementController.java ├── service │ ├── MeasurementService.java │ └── TrendService.java ├── mapper │ ├── UserMapper.java │ └── MeasurementMapper.java ├── entity │ ├── User.java │ └── Measurement.java └── dto ├── MeasurementRequest.java └── TrendResponse.java

controllerservicemapperentitydto五层对应「接口、业务、数据访问、表映射、传输对象」五个职责。需要说明的是,entity和dto分开是刻意的:实体类字段跟数据库列一一对应,dto则裁剪字段并做参数校验,避免把数据库结构直接暴露给APP端。

pom.xml里核心依赖只需要四个。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.43</version> </dependency> </dependencies>

Spring Boot版本锁在2.7.x而不是3.x,原因是3.x要求Java 17,而不少使用这套源码的环境还是Java 8。mybatis-spring-boot-starter必须在Spring Boot的依赖管理之外显式声明版本,因为Spring Boot官方没有维护它的版本映射。

3.2 实体类与Mapper:一张表一个接口

实体类字段与数据库列一一对应,直接贴Measurement

public class Measurement { private Long id; private Long userId; private BigDecimal weightKg; private BigDecimal bodyFatRate; private String note; private LocalDateTime measuredAt; private LocalDateTime createdAt; // getter / setter 略,实际源码中自动生成 }

注意weightKg的类型是BigDecimal而不是double。MyBatis的映射会自动把数据库的DECIMAL(5,2)转成BigDecimal,后续趋势计算全部用BigDecimal运算,最后再转换为double输出,避免精度丢失。

Mapper接口只需要三个方法。

public interface MeasurementMapper { int insert(Measurement measurement); List<Measurement> selectByUserAndTimeRange(@Param("userId") Long userId, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end); List<Measurement> selectLatestByUser(@Param("userId") Long userId, @Param("limit") int limit); }

对应的MeasurementMapper.xml里,selectByUserAndTimeRange是趋势计算的核心查询。

<select id="selectByUserAndTimeRange" resultType="com.example.weight.entity.Measurement"> SELECT id, user_id AS userId, weight_kg AS weightKg, body_fat_rate AS bodyFatRate, note, measured_at AS measuredAt, created_at AS createdAt FROM measurement WHERE user_id = #{userId} AND measured_at &gt;= #{start} AND measured_at &lt; #{end} ORDER BY measured_at ASC </select>

这里必须写AS userId做别名映射,否则MyBatis默认开启的驼峰映射会把user_id映射到userId,一旦数据库字段用了下划线而实体类用驼峰,没有别名就会全字段为null。排序用ASC保证时间升序,趋势算法依赖有序数据,查询层不排序,算法层再排就是重复劳动。

3.3 Controller:按APP端的习惯返回JSON

APP端的诉求是「一次请求拿到完整数据」,不要拆成五次调用。提供三个接口,覆盖录入和查询全部场景。

@RestController @RequestMapping("/api/v1/measurements") public class MeasurementController { private final MeasurementService measurementService; private final TrendService trendService; @PostMapping public Result<Long> create(@RequestBody @Valid MeasurementRequest request) { return Result.ok(measurementService.create(request)); } @GetMapping("/history") public Result<List<Measurement>> history(@RequestParam Long userId, @RequestParam String startDate, @RequestParam String endDate) { return Result.ok(measurementService.listByRange(userId, startDate, endDate)); } @GetMapping("/trend") public Result<TrendResponse> trend(@RequestParam Long userId, @RequestParam(defaultValue = "30") int days) { return Result.ok(trendService.computeTrend(userId, days)); } }

Result<T>是统一响应包装,包含codemessagedata三个字段,code=0表示成功。日期参数用String接收再在Service里解析成LocalDate,不要直接用Date类型接收JSON,因为APP端传过来的格式可能是yyyy-MM-dd也可能是yyyy-MM-dd HH:mm:ssString解析可控性更强。

3.4 本地启动与接口自测命令

编译启动。

# 先改 application.yml 里的数据库连接,再启动 mvn spring-boot:run

接口自测。

# 新增一条记录 curl -X POST http://localhost:8080/api/v1/measurements \ -H "Content-Type: application/json" \ -d '{"userId":1,"weightKg":72.5,"bodyFatRate":21.3,"measuredAt":"2024-11-20 07:30:00","note":"早晨空腹"}' # 查询最近30天趋势 curl "http://localhost:8080/api/v1/measurements/trend?userId=1&days=30"

application.yml三个必配项。

配置项示例值作用
spring.datasource.urljdbc:mysql://localhost:3306/weight_db?useUnicode=true&characterEncoding=utf8连接串带UTF8编码
spring.datasource.password占位符密码放环境变量,不放源码库
mybatis.mapper-locationsclasspath:mapper/*.xml告诉MyBatis去哪里找XML文件

自测时mvn spring-boot:run打印出Tomcat started后,流程是POST一条假数据,再GET查询历史,重点看JSON里weightKg是否带两位小数、measuredAt是否和传参一致。如果measuredAt变成null,八成是Fastjson或Jackson没识别到yyyy-MM-dd HH:mm:ss格式,在application.yml里加一行spring.jackson.date-format即可。

提示:如果连本机调试都不想装MySQL,H2数据库开着MySQL兼容模式也能把这套源码跑起来,但建表语法里的ENGINE=InnoDB要删掉。

4. 体重记录里的「专业」在哪:波动容忍、趋势算法与提醒源码

4.1 为什么体重数据要处理而不是直接展示

体重是典型的带噪生理信号。一天内水分摄入、盐分高低、排便时间差,就能让同一个人的体重波动±1.5公斤。如果APP把每一条原始记录都画在折线图上,趋势线会被噪声完全淹没,用户看到的是一根上下乱跳的曲线,不仅没有指导意义,还会因为「昨天重了0.8公斤」产生焦虑。

处理思路分两层。第一层是「过滤」,对原始值做平滑,常见做法是移动平均或指数加权平均;第二层是「归约」,在同一天有多条记录时,取早晨空腹那一条作为当日标准值,没有早晨记录就取当日均值。

原始记录永远保留,平滑结果只用于展示和判断,这是数据可追溯性的底线。删原始数据、只留处理结果的做法,是这类APP最常踩的坑,一旦算法调优,历史数据全废了。

4.2 用指数加权平均过滤波动:EWMA的Java实现与alpha取值

指数加权平均(EWMA)比简单移动平均更适合体重场景:它对近期数据敏感,能快速反映真实趋势变化,而且只需要保留前一个平滑值,计算成本O(1)。公式是S(t) = alpha * x(t) + (1 - alpha) * S(t-1)x(t)是当前测量值,S(t)是当前平滑值。

public class EwmaFilter { private final double alpha; public EwmaFilter(double alpha) { this.alpha = alpha; } public List<BigDecimal> smooth(List<BigDecimal> rawValues) { List<BigDecimal> result = new ArrayList<>(rawValues.size()); if (rawValues.isEmpty()) { return result; } double prev = rawValues.get(0).doubleValue(); result.add(BigDecimal.valueOf(prev)); for (int i = 1; i < rawValues.size(); i++) { double x = rawValues.get(i).doubleValue(); prev = alpha * x + (1 - alpha) * prev; result.add(BigDecimal.valueOf(prev).setScale(2, RoundingMode.HALF_UP)); } return result; } }

alpha取值直接决定平滑强度。alpha=1时输出等于输入,完全不平滑;alpha=0时输出恒等于第一个值,过度平滑。体重场景alpha=0.3是常用的经验值,它表示最新一次测量对平滑值贡献30%的权重,大约7次测量后旧数据的影响降到10%以下。如果用户每天固定早晨测一次,alpha=0.3大概一周后曲线开始贴合真实趋势。

配合EWMA还要算一个delta字段,即最新平滑值减去7天前的平滑值。delta > 0.2表示一周净增重超过0.2公斤,delta < -0.2表示减重有效,Math.abs(delta) <= 0.2视为平台期。这个判断逻辑放在Service层,返回值里带上trendDirection字段,前端直接显示「上升 / 下降 / 平稳」三个文案,不在APP端计算。

4.3 目标分解与提醒触发:把每周速度算给用户看

目标功能的核心是一个数学问题:距离目标日期还有N周,目标是T公斤,当前平滑值是C公斤,每周需要减多少。代码把时间单位精确到周,避免「每天都要掉秤」这种不合理预期。

public BigDecimal weeklyTargetRate(BigDecimal currentSmoothedWeight, BigDecimal targetWeight, LocalDate today, LocalDate targetDate) { long daysLeft = ChronoUnit.DAYS.between(today, targetDate); if (daysLeft <= 0) { return BigDecimal.ZERO; } BigDecimal totalToLose = currentSmoothedWeight.subtract(targetWeight); double weeksLeft = daysLeft / 7.0; return totalToLose.divide(BigDecimal.valueOf(weeksLeft), 2, RoundingMode.HALF_UP); }

divide必须传RoundingMode,否则当除不尽时抛ArithmeticExceptionBigDecimal.valueOf(weeksLeft)确保分子分母都是BigDecimal,避免double参与除法后精度漂移。

提醒逻辑基于这个速率生成。当weeklyTargetRate绝对值超过1.5公斤(周减重速率超过体重的1%,不安全),Service返回TOO_FAST提醒;连续3天delta > 0返回TREND_UP提醒;超过72小时没有新记录返回NO_RECORD提醒。这三种提醒用策略接口实现更干净。

public interface ReminderStrategy { String check(MeasurementContext context); boolean supports(ReminderType type); }

每个策略一个类,ReminderService里注入List<ReminderStrategy>并按需调用。这个设计的价值在于提醒规则是业务上最常变的部分——运营可能会加「连续一个月未记录发两次推送」——策略模式让每改动一个规则只动一个类,测试也只测一个类。

5. 源码交付形态:可读性、可迁移与两个常踩的坑

5.1 让源码能交接也能面试:分层与README的写法

源码工程做到「clone下来能启动、看目录能定位逻辑」就及格了一半。README里必须写清楚三件事:环境要求(Java 8+、Maven 3.6+、MySQL 5.7+)、建库建表脚本路径、默认接口的请求和响应示例。注意不要在这里堆接口文档,那应该由Swagger或springdoc-openapi自动生成,README只解决「启动问题」。

每个Controller的类头注释写「这个接口服务于APP的哪个页面」,比如MeasurementController的注释是「体重录入页 + 历史列表页」。后端代码容易在两个月后被投入新需求的人看,而唯一记得页面长什么样的是写APP的人,所以务必保证接口、页面、注释三者对应。

5.2 坑一:时间与时区乱套导致趋势曲线错位

这是体重记录APP最隐蔽的坑。APP端和服务器端在不同时区时,measured_at存的是「用户称重的本地时间」,但数据库连接串没指定时区,MySQL默认用服务器时区转换。结果是用户在UTC+8早上7点称重,数据库存成了UTC时间,查询回来时一次性偏移8小时,如果同一天早晚各测一次,早晚顺序直接互换,EWMA算出来的趋势方向是反的。

解决方法是统一约定:数据库连接串加上serverTimezone=Asia/Shanghai,实体类用LocalDateTime而不是Date,JSON序列化格式固定成yyyy-MM-dd HH:mm:ss。这三处统一后,无论APP端在哪个时区,传进来的时间戳都先转成这个格式再入库,查询时原样返回。

5.3 坑二:浮点精度在目标计算里反复出问题

接口返回趋势数据时,deltaweeklyTargetRate如果直接返回BigDecimal,Fastjson序列化后输出的是科学计数法,APP端doubleValue()再接可能变成0.30000000000000004。格式化必须在后端完成,用setScale(2, RoundingMode.HALF_UP)输出两位小数,别再往上传原始值。

验证这个坑是否修复的办法:用同一份测试数据跑两次/trend接口,对比两次JSON字符串是否完全一致。如果存在随机尾数差异,就是序列化层没有统一四舍五入。把格式化逻辑集中在TrendResponsesetter里,而不是散落在Service各处,排查时只需要看一个文件。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 4:14:42

OpenHarmony分类选择器开发差异与适配方案

1. 项目背景与核心问题在OpenHarmony应用开发中&#xff0c;分类选择器是一个常见但容易被忽视的组件差异点。项目页面和体系页面虽然都使用分类选择器&#xff0c;但实际开发中会发现两者在交互逻辑、数据绑定和UI表现上存在显著差异。这些差异往往导致开发者直接复用组件时出…

作者头像 李华
网站建设 2026/9/14 4:12:14

大模型能力清醒指南:识别伪升级与真实技术边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 4:11:25

SpringBoot整合XXL-JOB实现分布式任务调度实战

1. 项目概述&#xff1a;SpringBoot与XXL-JOB的强强联合在分布式系统架构中&#xff0c;定时任务调度一直是个棘手的问题。传统的Scheduled注解方案在单机环境下运行良好&#xff0c;但面对集群部署时就会出现任务重复执行、负载不均等问题。XXL-JOB作为一款轻量级分布式任务调…

作者头像 李华
网站建设 2026/9/14 4:08:13

pot-desktop 使用指南:免费划词翻译与截图 OCR 快速上手

pot-desktop 使用指南&#xff1a;免费划词翻译与截图 OCR 快速上手 【免费下载链接】pot-desktop &#x1f308;一个跨平台的划词翻译和OCR软件 | A cross-platform software for text translation and recognition. 项目地址: https://gitcode.com/GitHub_Trending/po/pot-…

作者头像 李华
网站建设 2026/9/14 4:06:17

MuJoCo中Cassie双足机器人仿真库的部署、控制与强化学习实践

简介&#xff1a;面向机器人控制与仿真研究者的Cassie双足机器人MuJoCo仿真库资源&#xff0c;基于Agility Robotics官方开源仓库整理&#xff0c;专用于多关节接触动力学建模、行走与平衡控制算法验证。压缩包共62个文件、约1.47MB&#xff0c;以XML模型描述、STL网格、C/Pyth…

作者头像 李华