1. 项目概述:基于Spring Boot的Android个人理财系统开发实录
作为一名长期奋战在一线的Java全栈开发者,最近指导了几位应届生完成他们的毕业设计项目。其中这个基于Spring Boot后端和Android客户端的个人理财管理系统,让我看到了年轻开发者对实际业务需求的敏锐洞察。本文将完整还原该系统的技术实现路径,包含从架构设计到代码落地的全流程细节,特别适合需要完成类似课程设计或毕业项目的同学参考。
传统个人理财的痛点非常明确:手工记账易遗漏、分类统计耗时、预算控制滞后。这个系统通过三个角色(管理员、普管、普通用户)的分工协作,实现了收支记录自动化、分类统计可视化和预算预警实时化三大核心能力。在技术栈选择上,采用Spring Boot 2.7 + MySQL 8.0 + Android的组合,既保证了系统稳定性,又兼顾了移动端便捷性。
关键提示:在开始编码前,务必明确各模块的实体关系。我们团队在初期就因"收入/支出分类"与"登记记录"的关联关系定义不清晰,导致后续大量返工。
2. 系统架构设计与技术选型
2.1 整体技术栈解析
后端核心组件:
- Spring Boot 2.7.3(稳定版)
- Spring Security(权限控制)
- MyBatis-Plus 3.5.1(数据持久化)
- Hutool 5.8.8(工具包)
- Lombok(代码简化)
前端技术方案:
- Android SDK 31
- Retrofit 2.9.0(网络请求)
- MPAndroidChart 3.1.0(数据可视化)
- Material Design组件库
数据库环境:
- MySQL 8.0.28(开发环境)
- 阿里云RDS MySQL 5.7(生产环境)
- Navicat Premium 15(数据库管理)
这个技术组合经过了我们团队的严格验证:
- Spring Boot的自动配置特性大幅减少了XML配置
- MyBatis-Plus的代码生成器可快速构建CRUD接口
- Android采用MVVM架构,与后端保持松耦合
2.2 系统分层架构
采用经典的B/S架构,分为表现层、业务层和持久层:
code复制表现层
├─ Android客户端 (MVVM)
│ ├─ View: Activity/Fragment
│ ├─ ViewModel: LiveData驱动
│ └─ Model: 实体类
└─ Web管理端 (Thymeleaf)
业务层
├─ Service接口
├─ ServiceImpl实现
└─ 自定义异常处理
持久层
├─ MyBatis-Plus Mapper
├─ 实体类注解
└─ 动态SQL生成
这种分层带来的直接好处是:
- 开发人员可以并行工作(前端与后端分离)
- 单元测试更容易实施(各层可独立测试)
- 系统扩展性强(新增模块不影响现有结构)
3. 数据库设计与核心表结构
3.1 E-R图与关键实体关系
经过三次迭代优化,最终确定的数据库包含12张核心表,主要实体关系如下:

重点表结构说明:
- 用户体系表
sql复制CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT 'MD5加密密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`role_type` tinyint NOT NULL COMMENT '1-管理员 2-普管 3-用户',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`status` tinyint DEFAULT '1' COMMENT '状态 0-禁用 1-正常',
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 收支记录表(核心业务表)
sql复制CREATE TABLE `finance_record` (
`record_id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '关联用户ID',
`type` tinyint NOT NULL COMMENT '1-收入 2-支出',
`category_id` int NOT NULL COMMENT '分类ID',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
`record_date` date NOT NULL COMMENT '记录日期',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`record_id`),
KEY `idx_user_date` (`user_id`,`record_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引优化实践
在高频查询场景下,我们特别优化了以下索引:
- 用户ID+记录日期联合索引(加速按日期范围查询)
- 分类ID单列索引(加速分类统计)
- 金额字段的降序索引(TOP N查询优化)
血泪教训:初期没有为record_date字段建立索引,当用户数据量超过1万条时,按月统计查询耗时从200ms飙升到2s。通过EXPLAIN分析后发现是全表扫描导致,添加索引后性能立即恢复。
4. 核心功能模块实现细节
4.1 多角色权限控制系统
采用Spring Security + JWT实现的三级权限控制:
java复制// 安全配置核心代码
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/manager/**").hasAnyRole("MANAGER","ADMIN")
.antMatchers("/user/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
// JWT令牌生成工具类
public class JwtUtils {
private static final String SECRET = "your-256-bit-secret";
private static final long EXPIRATION = 86400000L; // 24小时
public static String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
}
权限控制要点:
- 管理员可访问所有API(/admin/**)
- 普管可访问管理接口(/manager/**)
- 普通用户只能操作用户数据(/user/**)
- JWT令牌包含角色信息,避免频繁查库
4.2 收支记录与统计功能
Android端采用MVVM模式实现数据绑定:
kotlin复制// ViewModel层
class RecordViewModel : ViewModel() {
private val _records = MutableLiveData<List<FinanceRecord>>()
val records: LiveData<List<FinanceRecord>> = _records
fun loadRecords(userId: Long, startDate: String, endDate: String) {
viewModelScope.launch {
try {
val response = financeService.getRecords(userId, startDate, endDate)
_records.value = response.data
} catch (e: Exception) {
// 错误处理
}
}
}
}
// 使用MPAndroidChart实现统计图表
private fun setupPieChart(data: List<CategorySum>) {
val entries = data.map {
PieEntry(it.amount.toFloat(), it.categoryName)
}
val dataSet = PieDataSet(entries, "支出分类").apply {
colors = ColorTemplate.MATERIAL_COLORS.toList()
valueTextSize = 12f
}
pieChart.data = PieData(dataSet)
pieChart.invalidate()
}
关键实现细节:
- 使用Kotlin协程处理异步请求
- LiveData实现数据驱动UI更新
- 图表库支持动态数据刷新
- 后端接口采用分页返回(PageHelper)
5. 典型问题排查与性能优化
5.1 并发修改异常处理
在压力测试时发现,当多个设备同时修改同一用户的预算数据时,会出现数据不一致。解决方案:
java复制@Transactional
public void updateBudget(Long userId, BigDecimal amount) {
// 使用悲观锁确保数据一致性
FinanceBudget budget = budgetMapper.selectForUpdate(userId);
if (budget == null) {
throw new BusinessException("预算记录不存在");
}
budget.setAmount(amount);
budgetMapper.updateById(budget);
}
对应Mapper的SQL注解:
java复制@Select("SELECT * FROM finance_budget WHERE user_id=#{userId} FOR UPDATE")
FinanceBudget selectForUpdate(@Param("userId") Long userId);
5.2 接口响应优化
通过以下措施将平均响应时间从800ms降低到200ms内:
- 添加二级缓存(Redis)
java复制@Cacheable(value = "recordCache", key = "#userId+'-'+#month")
public List<FinanceRecord> getMonthlyRecords(Long userId, String month) {
// 数据库查询逻辑
}
- 启用Gzip压缩
properties复制# application.properties
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,application/json
- 静态资源CDN加速
html复制<!-- 修改Android端资源地址 -->
<img src="https://cdn.yourdomain.com/images/${record.categoryImage}"/>
6. 项目部署与上线要点
6.1 后端部署流程
使用Docker-compose实现一键部署:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: yourpassword
volumes:
- ./mysql-data:/var/lib/mysql
ports:
- "3306:3306"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/finance?useSSL=false
6.2 Android应用打包技巧
- 开启代码混淆(proguard-rules.pro)
code复制-keep class com.finance.** { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
- 资源压缩配置(build.gradle)
groovy复制android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
7. 开发心得与建议
-
实体设计要先于编码:我们曾因早期没理清"预算"与"记录"的关系,导致后期数据库结构调整困难。建议先用Excel模拟各种业务场景的数据关系。
-
接口文档要同步维护:使用Swagger UI自动生成API文档,减少前后端沟通成本。我们中途因接口变更导致Android端多次调整,教训深刻。
-
测试数据要真实:初期使用随机生成的测试数据,掩盖了金额计算时的精度问题。后来改用真实用户账本数据,才发现BigDecimal的除法必须指定精度模式。
这个项目让我深刻体会到,即使是毕业设计级别的系统,也需要用工程化的思维来开发。现在回看那些熬夜调试的日子,最大的收获不是完成了多少功能,而是培养了面对复杂系统时的拆解能力和解决问题的韧性。