作为一个长期关注家庭财务管理的开发者,我发现市面上大多数记账工具要么功能过于复杂,要么无法满足家庭成员协同记账的需求。这正是我决定开发这款基于微信小程序的家庭记账本系统的初衷。微信小程序无需安装、即用即走的特性,加上Java后端的稳定性,构成了这个项目的技术基础。
这个系统主要解决三个核心痛点:
系统采用B/S架构,前端使用微信小程序技术栈(WXML+WXSS+JS),后端采用Spring Boot框架,数据库选用MySQL 8.0。这种技术组合既保证了开发效率,又能承载一定的用户量级。
提示:选择微信小程序作为前端载体时,需要注意微信官方对个人开发者和小程序类目的限制。家庭记账类小程序目前属于工具类目,个人开发者可以正常上架。
系统采用典型的三层架构:
这种分层架构的优势在于:
我在架构设计时特别考虑了微信生态的特点:
数据库设计遵循第三范式,主要包含以下核心表:
sql复制CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`openid` varchar(50) NOT NULL COMMENT '微信openid',
`nickname` varchar(50) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `consumption` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`title` varchar(100) NOT NULL,
`category` tinyint NOT NULL COMMENT '1-食物 2-购物 3-交通 4-学习 5-其他',
`amount` decimal(10,2) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
`record_date` date NOT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_date` (`user_id`,`record_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `income` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`title` varchar(100) NOT NULL,
`category` tinyint NOT NULL COMMENT '1-工资 2-奖金 3-其他',
`amount` decimal(10,2) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
`record_date` date NOT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_date` (`user_id`,`record_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:金额字段统一使用DECIMAL(10,2)类型,避免浮点数精度问题。record_date字段单独存储日期(不含时间),便于按日统计。
前端技术栈选择微信小程序的原因:
后端选择Java+Spring Boot的考虑:
数据库选择MySQL 8.0的决策依据:
微信登录是系统的核心入口,实现流程如下:
关键代码示例(Spring Boot端):
java复制@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Value("${wechat.appid}")
private String appid;
@Value("${wechat.secret}")
private String secret;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
// 调用微信接口获取openid
String url = "https://api.weixin.qq.com/sns/jscode2session?" +
"appid=" + appid +
"&secret=" + secret +
"&js_code=" + dto.getCode() +
"&grant_type=authorization_code";
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
JSONObject json = JSON.parseObject(response.getBody());
String openid = json.getString("openid");
if (StringUtils.isEmpty(openid)) {
return Result.error("登录失败");
}
// 查询或创建用户
User user = userService.findByOpenid(openid);
if (user == null) {
user = new User();
user.setOpenid(openid);
userService.save(user);
}
// 生成token并返回
String token = JwtUtil.generateToken(user.getId());
return Result.success(token);
}
}
实操心得:微信登录的session_key需要妥善保管但不要传到前端,它是解密用户加密数据的关键。建议设置合理的过期时间(如7天),并实现token自动续期机制。
账单记录包括消费和收入两大模块,采用相似的数据结构但不同的业务逻辑。以消费记录为例:
xml复制<view class="form-item">
<text>消费类型:</text>
<picker range="{{categories}}" bindchange="onCategoryChange">
<view>{{categories[currentCategory]}}</view>
</picker>
</view>
<view class="form-item">
<text>金额:</text>
<input type="digit" placeholder="请输入金额" bindinput="onAmountChange"/>
</view>
<view class="form-item">
<text>备注:</text>
<input placeholder="选填" bindinput="onRemarkChange"/>
</view>
java复制@RestController
@RequestMapping("/api/consumption")
public class ConsumptionController {
@Autowired
private ConsumptionService consumptionService;
@PostMapping
public Result add(@RequestBody ConsumptionDTO dto,
@RequestHeader("Authorization") String token) {
Integer userId = JwtUtil.getUserId(token);
Consumption consumption = new Consumption();
BeanUtils.copyProperties(dto, consumption);
consumption.setUserId(userId);
consumptionService.save(consumption);
return Result.success();
}
@GetMapping("/summary")
public Result getSummary(@RequestParam String period,
@RequestHeader("Authorization") String token) {
Integer userId = JwtUtil.getUserId(token);
Map<String, Object> summary = consumptionService.getSummary(userId, period);
return Result.success(summary);
}
}
java复制public Map<String, Object> getSummary(Integer userId, String period) {
Map<String, Object> result = new HashMap<>();
// 按日/周/月统计
LocalDate endDate = LocalDate.now();
LocalDate startDate = period.equals("month") ?
endDate.minusMonths(1) :
period.equals("week") ?
endDate.minusWeeks(1) :
endDate.minusDays(1);
// 查询时间段内的记录
List<Consumption> records = consumptionMapper.selectByUserAndDate(
userId, startDate, endDate);
// 按分类统计
Map<Integer, BigDecimal> byCategory = records.stream()
.collect(Collectors.groupingBy(
Consumption::getCategory,
Collectors.reducing(
BigDecimal.ZERO,
c -> c.getAmount(),
BigDecimal::add)));
// 构造返回结果
result.put("total", byCategory.values().stream()
.reduce(BigDecimal.ZERO, BigDecimal::add));
result.put("byCategory", byCategory);
result.put("startDate", startDate);
result.put("endDate", endDate);
return result;
}
考虑到家庭成员可能同时记账,系统实现了基于时间戳的乐观锁机制:
关键冲突检测代码:
java复制@Transactional
public Result updateConsumption(ConsumptionDTO dto) {
Consumption existing = consumptionMapper.selectById(dto.getId());
if (!existing.getUpdateTime().equals(dto.getUpdateTime())) {
return Result.error("记录已被修改,请刷新后重试");
}
Consumption update = new Consumption();
BeanUtils.copyProperties(dto, update);
update.setUpdateTime(new Date());
consumptionMapper.updateById(update);
return Result.success();
}
注意事项:对于财务类应用,所有更新操作必须放在事务中执行,确保数据一致性。建议使用@Transactional注解,并设置合适的隔离级别和传播行为。
推荐使用以下服务器配置:
部署步骤:
bash复制# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装JDK
sudo apt install openjdk-11-jdk -y
# 安装MySQL
sudo apt install mysql-server -y
sudo mysql_secure_installation
# 安装Nginx
sudo apt install nginx -y
bash复制# 创建数据库和用户
mysql -u root -p
CREATE DATABASE family_account DEFAULT CHARACTER SET utf8mb4;
CREATE USER 'account_user'@'%' IDENTIFIED BY 'StrongPassword123';
GRANT ALL PRIVILEGES ON family_account.* TO 'account_user'@'%';
FLUSH PRIVILEGES;
bash复制# 上传jar包
scp target/family-account.jar user@server:/opt/app/
# 创建systemd服务
sudo vi /etc/systemd/system/account.service
[Unit]
Description=Family Account Service
After=syslog.target
[Service]
User=appuser
ExecStart=/usr/bin/java -jar /opt/app/family-account.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
# 启动服务
sudo systemctl daemon-reload
sudo systemctl start account
sudo systemctl enable account
避坑指南:微信对小程序内容有严格审核标准,特别是涉及金融、交易类功能时。家庭记账本属于工具类目,需确保不包含虚拟支付功能,否则可能无法过审。
问题1:账单列表加载缓慢,特别是数据量较大时
解决方案:
sql复制ALTER TABLE consumption ADD INDEX idx_user_date (user_id, record_date);
问题2:微信登录偶尔超时
解决方案:
错误现象:账单统计结果不准确
排查步骤:
错误现象:微信小程序真机预览空白
排查步骤:
在实际使用过程中,我收集到一些用户反馈,总结出几个有价值的扩展方向:
实现思路:
实现方案:
技术方案:
关键技术点:
这个项目从技术选型到最终上线,我最大的体会是:一个好的工具类产品应该做到"简单在前,复杂在后"。用户界面要足够简洁易用,而背后的业务逻辑和数据架构则需要考虑周全。特别是在处理财务数据时,必须确保数据的准确性和安全性,任何一个小错误都可能导致用户信任的丧失。