基于Java SSM与微信小程序的家庭记账本全栈开发实践
2026/9/2 7:30:00 网站建设 项目流程

简介:这是一套面向计算机专业本科生的高分毕业设计级家庭记账本系统,融合Java后端开发与微信小程序前端,解决家庭财务数字化管理痛点,适用于毕设答辩、课程设计及期末大作业实战。资源包共775个文件,含90个Java核心业务类、123个Vue与微信小程序页面(wxml/wxss/js)、30个数据库SQL脚本及配置文件,涵盖用户认证、账目录入/查询/统计、可视化报表等完整模块;SVG/PNG图标、CSS样式、JSON配置及bat一键部署脚本一应俱全,压缩包大小68.44MB。已有93人学习下载,资源经导师指导并实际通过答辩,提供可直接运行的SSM框架源码、MySQL 5.7+兼容数据库脚本、Navicat建库说明及IDEA+微信开发者工具部署指南,结构清晰、注释规范,附带论文文档与软件工具清单,开箱即用,无需二次修改即可完成本地调试与功能验证。

1. 项目概述与核心价值

最近在整理过往的项目资料,翻到了几年前带学生做的一个毕业设计,一个基于Java SSM框架和微信小程序的家庭记账本系统。这个项目当时拿了高分,源码、数据库和论文都打包得整整齐齐。今天拿出来重新梳理一下,发现它不仅仅是一个合格的毕业设计,其技术选型和架构思路,对于想入门全栈开发,特别是想打通后端Java和前端小程序的新手来说,依然有很高的参考价值。它完整地覆盖了从需求分析、技术选型、数据库设计、后端接口开发到前端小程序实现的全流程,麻雀虽小,五脏俱全。

这个系统的核心目标很明确:为家庭用户提供一个便捷、直观的账目管理工具。用户可以通过微信小程序随时随地上传收入、支出记录,系统会自动按类别、时间进行统计和分析,生成可视化的图表,帮助家庭清晰掌握财务状况。为什么选择这个组合?Java+SSM提供了稳定、成熟且易于教学的后端解决方案,MySQL是关系型数据库的经典选择,而微信小程序则拥有巨大的用户基础和无需安装的便利性。对于学习者而言,搞定这个项目,相当于串起了Web应用开发的一条标准技术链。

2. 技术栈选型与架构设计思路

2.1 后端技术栈:为什么是Java+SSM?

在项目启动时,我们面临多种后端技术选择,比如Spring Boot、PHP Laravel或Python Django。最终选定Java搭配SSM(Spring + Spring MVC + MyBatis)框架,主要基于以下几点考量:

稳定性与生态成熟度:Java在企业级开发中经受了长期考验,拥有极其丰富的类库和社区资源。SSM框架组合在当时的教学和中小型项目实践中非常流行,资料齐全,遇到问题几乎都能找到解决方案。这对于确保毕业设计顺利推进、降低技术风险至关重要。

清晰的分层架构:SSM天然支持MVC(Model-View-Controller)模式,但在这个前后端分离的项目中,View层被微信小程序替代,后端专注于提供Restful API。Spring负责IoC(控制反转)和AOP(面向切面编程),管理Bean的生命周期和事务;Spring MVC作为Web框架,处理HTTP请求和响应;MyBatis作为ORM框架,负责数据库操作。这种分层使得代码结构清晰,职责分明,非常利于教学和团队协作理解。

与MySQL的默契配合:MyBatis的半自动化特性,允许开发者编写灵活的SQL语句,同时又通过映射文件或注解将结果集与Java对象(POJO)绑定。对于家庭记账这种业务逻辑相对直接,但对查询统计有定制化需求的应用,MyBatis比全自动化的Hibernate更具操控感。我们可以精细地控制JOIN查询来关联用户、账目类型、账目明细,优化统计报表的查询性能。

实操心得:很多新手在配置SSM时会被大量的XML文件搞晕。我的经验是,理解每个配置文件的核心作用比死记硬背更重要。spring.xml管Bean和事务,spring-mvc.xml管控制器和视图解析(本项目主要配拦截器和JSON转换器),mybatis-config.xml管数据库环境和全局设置,SQL映射则写在单独的Mapper.xml文件里。先搭一个最小可运行的结构,再逐步添加功能。

2.2 数据库设计:MySQL表结构规划

数据库设计是系统的基石,设计好坏直接影响到后续开发的复杂度和系统性能。家庭记账本的核心实体包括:用户、账目类别、账目记录。

核心表结构设计

  1. 用户表 (user):存储微信小程序用户的基本信息。主要字段包括用户ID(主键)、微信OpenID(唯一标识,用于关联微信用户)、昵称、头像URL、注册时间等。这里特别注意,我们不应该存储用户的微信密码,而是通过微信官方登录流程获取OpenID进行身份绑定。
  2. 账目类别表 (category):这是一个可扩展的维度表。字段包括类别ID、类别名称(如“餐饮”、“交通”、“工资”)、类型(1-收入, 2-支出)、图标标识、所属用户ID(支持用户自定义类别)。预置一些常用类别,提升用户体验。
  3. 账目记录表 (record):这是最核心的业务表。每条记录包含:记录ID、用户ID(外键)、类别ID(外键)、金额、备注、记录时间(精确到日或时分秒,根据需求)、创建时间。金额字段建议使用DECIMAL(10,2)类型,确保计算精度。

设计要点与优化

  • 关联关系record表通过user_idcategory_id外键关联到usercategory表。这样在查询时可以通过JOIN一次性获取用户昵称、类别名称等信息。
  • 索引策略:为了加速查询,通常在record表的user_idrecord_time字段上建立复合索引,因为最常见的查询场景就是“查询某个用户在某一时间段的账目”。category_id上也可以单独建立索引。
  • 数据归档考虑:家庭账目数据会随时间积累。在设计初期可以考虑按年或按月分表的策略,或者在record表中增加一个“年份-月份”的冗余字段,用于快速按时间范围筛选,避免对record_time字段进行复杂的函数计算。

2.3 前端技术栈:微信小程序开发要点

选择微信小程序作为前端,看中的是其即用即走、跨平台的特性,以及丰富的原生API(如网络请求、本地存储、位置等)。对于记账这个高频但单次操作简单的场景,小程序体验比H5更好,比独立App开发成本更低。

小程序核心页面与组件

  • 主页 (index):展示本月/本日的收支概览(总额、笔数)、快速添加记录的入口、最近的几条交易流水。可以使用<scroll-view>实现列表滚动。
  • 记账页 (add-record):一个表单页面,包含金额输入框、类别选择器(使用<picker>组件)、日期时间选择器、备注输入框。类别选择器最好做成弹出式单选,体验更佳。
  • 统计页 (statistics):这是展示价值的页面。使用微信小程序的图表库(如ec-canvas,即ECharts的微信小程序版本)绘制饼图(支出类别分布)、折线图(月度趋势)。数据通过调用后端统计接口获取。
  • 我的页 (profile):展示用户信息,提供类别管理、数据导出、关于我们等设置入口。

前后端数据交互: 小程序通过wx.request()API调用后端SSM提供的Restful接口。这里的关键是身份认证。标准流程是:小程序端调用wx.login()获取code,将此code发送到我们自己的后端。后端再用这个code、小程序的AppID和AppSecret,请求微信接口服务换取该用户的openidsession_key。之后,后端可以生成一个自定义的登录态(例如一个Token)返回给小程序,小程序后续请求都在Header中携带此Token,后端通过拦截器验证Token有效性。绝对不要在前端存储或传输AppSecret。

3. 核心功能模块实现详解

3.1 用户系统与微信登录集成

这是系统安全的第一道门。实现无缝的微信登录,能让用户免去注册烦恼,极大提升体验。

后端接口实现步骤

  1. 创建AuthController:提供一个/api/auth/login的POST接口。
  2. 接收Code:接口接收小程序端传来的js_code
  3. 调用微信接口:在服务层(Service),使用HttpClient或RestTemplate,向https://api.weixin.qq.com/sns/jscode2session发送请求,参数包括appid,secret,js_code,grant_type=authorization_code
  4. 处理响应:微信返回openidsession_keyopenid是用户的唯一标识。检查本地数据库user表,如果此openid不存在,则创建一条新用户记录;如果存在,则更新最后登录时间。
  5. 生成自定义会话:为了安全和管理,我们不直接把session_key给前端。而是生成一个随机的Token(如UUID),将openid和Token的对应关系存入Redis(设置过期时间,如7天),或者加密后直接返回给前端。同时将Token返回给小程序。
  6. 创建拦截器:定义一个Spring MVC拦截器,对所有需要认证的API请求(如/api/record/**)进行拦截。从请求Header中取出Token,去Redis验证或解密,获取对应的openid,并将openid存入当前请求的上下文中(如ThreadLocal),方便后续业务逻辑获取当前用户。
// 示例:拦截器核心代码片段 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("X-Token"); if (StringUtils.isEmpty(token)) { // 返回未授权错误 return false; } String openid = redisTemplate.opsForValue().get(token); if (StringUtils.isEmpty(openid)) { // Token无效或已过期 return false; } // 将openid存入请求属性,后续Controller可通过@RequestAttribute获取 request.setAttribute("currentOpenid", openid); // 可选:刷新Token过期时间 redisTemplate.expire(token, 30, TimeUnit.MINUTES); return true; }

注意事项session_key是微信端的会话密钥,用于解密小程序获取的加密数据(如手机号)。它不应该通过网络传输到不信任的环境。我们的后端在获取到session_key后,应妥善保存在服务端(如与openid一起存入Redis),当小程序需要解密数据时,将加密数据传到后端,由后端用存储的session_key进行解密。

3.2 账目CRUD与业务逻辑

这是系统的业务核心,主要涉及账目记录的增删改查。

数据模型与Mapper: 首先创建Record实体类,属性与数据库record表字段对应。然后创建RecordMapper.java接口和对应的RecordMapper.xml文件。

<!-- RecordMapper.xml 片段 --> <insert id="insert" parameterType="Record" useGeneratedKeys="true" keyProperty="id"> INSERT INTO record (user_id, category_id, amount, remark, record_time) VALUES (#{userId}, #{categoryId}, #{amount}, #{remark}, #{recordTime}) </insert> <select id="selectByUserIdAndDateRange" resultType="map"><!-- 使用map或扩展的DTO接收复杂结果 --> SELECT r.*, c.name as category_name, c.type as category_type FROM record r LEFT JOIN category c ON r.category_id = c.id WHERE r.user_id = #{userId} AND r.record_time BETWEEN #{startDate} AND #{endDate} ORDER BY r.record_time DESC </select>

Service层业务逻辑: 在RecordService中,除了基本的增删改查,还会包含一些业务规则校验。例如,在添加记录时,需要检查传入的category_id是否属于当前用户(防止越权),金额是否为正数等。

Controller层设计: 设计Restful风格的API。

  • POST /api/records- 创建一条账目记录
  • GET /api/records- 获取账目列表,支持分页和按时间、类别筛选
  • GET /api/records/{id}- 获取单条记录详情
  • PUT /api/records/{id}- 更新一条记录
  • DELETE /api/records/{id}- 删除一条记录

在Controller的方法中,通过拦截器存入的request attribute获取当前用户的openid,进而查询到user_id,确保所有操作都限定在当前用户自己的数据范围内。

@PostMapping public Result addRecord(@RequestBody Record record, @RequestAttribute String currentOpenid) { // 1. 根据currentOpenid获取userId User user = userService.getUserByOpenid(currentOpenid); record.setUserId(user.getId()); // 2. 业务校验(如类别归属) if(!categoryService.belongsToUser(record.getCategoryId(), user.getId())){ return Result.error("非法操作:类别不存在或不属于你"); } // 3. 调用Service插入记录 recordService.addRecord(record); return Result.success(record.getId()); }

3.3 数据统计与分析模块实现

统计功能是体现系统价值的关键。常见的需求包括:月度收支趋势、支出类别占比、年度对比等。

后端统计接口设计: 统计通常是计算密集型操作,不宜在每次页面加载时都执行复杂查询。可以考虑按需计算和缓存结合的策略。

  1. 月度概览接口GET /api/statistics/monthly-summary?year=2023&month=8
    • 实现:执行两条SQL,分别计算该月总收入SUM(CASE WHEN c.type=1 THEN r.amount ELSE 0 END)和总支出SUM(CASE WHEN c.type=2 THEN r.amount ELSE 0 END),以及收入笔数和支出笔数。这个查询相对较快。
  2. 支出类别占比接口GET /api/statistics/category-pie?year=2023&month=8
    • 实现:查询语句对record表和category表进行JOIN和GROUP BY。
    SELECT c.name, SUM(r.amount) as total FROM record r JOIN category c ON r.category_id = c.id WHERE r.user_id = #{userId} AND c.type = 2 -- 支出 AND YEAR(r.record_time) = #{year} AND MONTH(r.record_time) = #{month} GROUP BY c.id ORDER BY total DESC
    返回的数据格式可以直接被前端的ECharts饼图组件使用。
  3. 月度趋势接口GET /api/statistics/monthly-trend?year=2023
    • 实现:这个查询需要按月份聚合。一种方法是使用SQL的DATE_FORMAT函数。
    SELECT DATE_FORMAT(r.record_time, '%Y-%m') as month, SUM(CASE WHEN c.type=1 THEN r.amount ELSE 0 END) as income, SUM(CASE WHEN c.type=2 THEN r.amount ELSE 0 END) as expense FROM record r JOIN category c ON r.category_id = c.id WHERE r.user_id = #{userId} AND YEAR(r.record_time) = #{year} GROUP BY DATE_FORMAT(r.record_time, '%Y-%m') ORDER BY month
    返回全年每个月的收入和支出总额,用于绘制折线图。

性能优化考虑

  • 缓存:对于“月度概览”和“类别占比”这种一天内变化不会太频繁的数据,可以在Service层加入缓存逻辑。例如,使用Redis,以user_id:statistics:monthly-summary:2023-08为key,存储计算结果,设置过期时间为1小时或6小时。
  • 数据库索引:再次强调,在record(user_id, record_time)record(user_id, category_id, record_time)上建立复合索引,对上述统计查询有巨大提速效果。
  • 定时预计算:对于更复杂的年度报告,可以考虑在每天凌晨通过定时任务(如Spring的@Scheduled)预计算并存储结果,白天用户查询时直接读取计算结果表,实现毫秒级响应。

4. 微信小程序前端开发实践

4.1 项目结构与页面布局

微信小程序有固定的目录结构。我们主要关注以下几个部分:

  • pages/:存放所有页面,每个页面由.wxml(模板)、.wxss(样式)、.js(逻辑)、.json(配置)四个文件组成。
  • app.js/app.json/app.wxss:全局逻辑、配置和样式。
  • components/:自定义组件,比如一个统一的账单列表项组件。
  • utils/:工具函数,比如封装网络请求的request.js

主页 (index) 布局要点: 主页采用垂直布局。顶部用卡片展示本月收支概览数字,中间是快速添加记录的浮动按钮(FAB),下方是最近的账单列表。列表使用wx:for循环渲染,每一项显示图标、类别、金额、时间和备注。为了更好的体验,可以加入下拉刷新(onPullDownRefresh)和上拉加载更多(onReachBottom)的功能。

记账页 (add-record) 交互细节: 这是一个表单页。金额输入框应调起数字键盘(type="digit")。类别选择使用<picker>组件,其rangerange-key属性绑定一个从后端获取或本地预定义的类别数组。日期选择使用<picker mode="date">。所有数据填写后,点击提交按钮,调用wx.request将数据发送到后端/api/records接口。提交成功后,可以给用户一个反馈(如wx.showToast),并返回上一页或主页,同时刷新主页列表数据。

4.2 网络请求封装与状态管理

直接在各个页面的.js文件中写wx.request会导致代码冗余且难以维护。通用的做法是在utils/目录下封装一个request模块。

// utils/request.js const baseURL = 'https://your-domain.com/api'; // 你的后端地址 const request = (options) => { // 从本地存储获取登录态Token const token = wx.getStorageSync('token'); return new Promise((resolve, reject) => { wx.request({ url: baseURL + options.url, method: options.method || 'GET', data: options.data, header: { 'Content-Type': 'application/json', 'X-Token': token // 将Token放在请求头中 }, success: (res) => { if (res.statusCode === 200) { // 假设后端统一返回 {code: 0, data: ..., msg: 'success'} if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { // Token过期或无效,跳转到登录页 wx.navigateTo({ url: '/pages/login/login' }); reject(new Error('未授权')); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(new Error(res.data.msg)); } } else { reject(new Error(`网络请求失败: ${res.statusCode}`)); } }, fail: (err) => { wx.showToast({ title: '网络连接失败', icon: 'none' }); reject(err); } }); }); }; // 导出GET/POST等方法 module.exports = { get: (url, data) => request({ url, method: 'GET', data }), post: (url, data) => request({ url, method: 'POST', data }), put: (url, data) => request({ url, method: 'PUT', data }), delete: (url) => request({ url, method: 'DELETE' }) };

在页面中,可以这样使用:

const api = require('../../utils/request.js'); Page({ data: { recordList: [] }, onLoad() { this.fetchRecordList(); }, async fetchRecordList() { try { const data = await api.get('/records', { page: 1, size: 20 }); this.setData({ recordList: data }); } catch (err) { console.error('获取列表失败', err); } } })

简易状态管理: 对于用户信息、全局类别列表等跨页面共享的数据,可以存储在app.js的全局对象中,或者使用小程序的getApp()方法访问。对于更复杂的状态,可以考虑使用像mobx-miniprogram这样的第三方库,但对于记账本这类规模的项目,全局变量和本地存储(wx.setStorageSync)通常已足够。

4.3 图表集成与数据可视化

微信小程序官方提供了<canvas>组件用于绘图,但直接使用较复杂。推荐使用业界成熟的ec-canvas组件,它是ECharts的小程序版本。

集成步骤

  1. 从ECharts官网下载ec-canvas组件源码,放入小程序项目的components/目录。
  2. 在需要图表的页面的.json文件中声明使用该组件。
    { "usingComponents": { "ec-canvas": "/components/ec-canvas/ec-canvas" } }
  3. 在页面的.wxml模板中放置组件,并指定一个Canvas ID。
    <view class="chart-container"> <ec-canvas id="pie-chart" canvas-id="pie-canvas" ec="{{ ec }}"></ec-canvas> </view>
  4. 在页面的.js文件中,引入ec-canvas,并在onReady生命周期中初始化图表。首先从后端获取统计数据的API,然后将数据格式化为ECharts需要的option配置项。
    import * as echarts from '../../components/ec-canvas/echarts'; const api = require('../../utils/request.js'); Page({ data: { ec: { lazyLoad: true } }, // 懒加载 onReady() { this.initChart(); }, async initChart() { // 1. 获取数据 const pieData = await api.get('/statistics/category-pie', {year:2023, month:8}); // 2. 格式化数据 const chartData = pieData.map(item => ({name: item.name, value: item.total})); // 3. 初始化图表组件 this.pieChart = this.selectComponent('#pie-chart'); this.pieChart.init((canvas, width, height) => { const chart = echarts.init(canvas, null, { width, height }); canvas.setChart(chart); const option = { tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: '70%', data: chartData, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } }] }; chart.setOption(option); return chart; }); } })

实操心得ec-canvas在小程序真机上渲染有时会遇到层级问题(比如图表被其他组件遮盖)。一个常见的解决方案是,将图表所在的<view>的样式设置为position: relative; z-index: 1;,并确保其父容器没有设置overflow: hidden不当裁剪。另外,图表数据量不宜过大,饼图类别最好控制在10项以内,否则会影响渲染性能和视觉体验。

5. 系统部署与上线注意事项

5.1 后端服务部署

一个完整的部署流程包括环境准备、打包、配置和启动。

环境准备

  • 服务器:选择一台云服务器(如阿里云ECS、腾讯云CVM),安装CentOS 7/8或Ubuntu 20.04 LTS系统。
  • Java环境:安装JDK 8或11(根据项目编译版本选择)。yum install java-11-openjdk-devel或从Oracle官网下载安装。
  • Web容器:传统的SSM项目通常打包成WAR包,部署到Tomcat。更现代的做法是使用Spring Boot内嵌Tomcat,打成可执行的JAR包,用java -jar命令运行,管理起来更简单。我们的项目可以改造成Spring Boot,这是平滑升级。
  • 数据库:安装MySQL 5.7或8.0,创建数据库和用户,并导入项目SQL脚本。

打包与运行: 如果使用Maven,在项目根目录执行mvn clean package -DskipTests,会在target目录生成WAR或JAR文件。

  • Tomcat部署:将WAR包复制到Tomcat的webapps/目录下,重启Tomcat即可。
  • Spring Boot JAR部署:直接运行nohup java -jar your-app.jar --spring.profiles.active=prod > app.log 2>&1 &。这里--spring.profiles.active=prod指定使用生产环境配置文件(application-prod.properties),里面配置了生产环境的数据库连接、日志级别等。

生产环境配置: 务必在application-prod.properties中做好安全配置:

  • 数据库密码使用强密码,且不要提交到代码仓库。
  • 关闭Swagger等调试接口。
  • 配置正确的CORS(跨域),只允许你的小程序域名访问。
  • 设置日志级别和输出路径,便于排查问题。

5.2 微信小程序提交审核与发布

小程序前端代码需要在微信开发者工具中上传,并提交审核。

上传代码

  1. 在微信开发者工具中,点击“上传”按钮,填写版本号和项目备注。
  2. 上传后,代码会出现在微信公众平台(小程序后台)的“版本管理”中。

提交审核前检查

  • 体验版测试:在后台设置体验版,邀请测试人员扫码体验,全面测试所有功能。
  • 基本信息:确保小程序的名称、简介、类目选择正确。记账本通常属于“工具-记账”或“生活服务”类目。
  • 隐私协议:如果小程序收集用户信息(如微信头像、昵称,甚至手机号),必须在小程序后台设置隐私协议,并在代码中调用wx.requirePrivacyAuthorize接口。
  • 内容安全:确保所有用户生成内容(如备注)有过滤机制,防止违规信息。图片上传功能(如果有)需谨慎,可能需要内容安全API校验。

审核要点: 微信审核主要关注功能完整性、是否符合平台规范、是否存在违规内容。记账类小程序需注意:

  • 不能涉及虚拟货币交易或金融类敏感功能。
  • 如果有社交分享功能,不能诱导分享。
  • 确保小程序运行稳定,无严重Bug。

审核通过后,即可发布上线。管理员可以在后台将审核通过的版本设为“全量发布”,所有用户就能搜索和使用了。

5.3 日常运维与数据备份

系统上线后,日常维护是保证稳定运行的关键。

日志监控

  • 定期查看应用日志(app.log)和服务器系统日志(/var/log/messagesdmesg),关注错误和警告信息。
  • 可以使用简单的tail -f app.log实时监控,或使用ELK(Elasticsearch, Logstash, Kibana)等日志系统进行集中管理。

性能监控

  • 使用服务器自带的tophtopvmstat命令监控CPU、内存、磁盘I/O和网络流量。
  • 监控Java应用进程,可以使用jstatjmap(谨慎使用)或Arthas等工具。
  • 监控数据库慢查询日志(slow_query_log),对执行时间过长的SQL进行优化。

数据备份策略: 数据是无价的,必须定期备份。

  1. 数据库备份
    • 全量备份:每天凌晨使用mysqldump命令进行全库备份。
    mysqldump -u[username] -p[password] --databases family_account > /backup/family_account_$(date +%Y%m%d).sql
    • 增量备份:如果数据量大,可以开启MySQL的二进制日志(binlog),定期备份binlog文件。
    • 远程备份:将备份文件自动同步到另一台服务器或对象存储(如阿里云OSS、腾讯云COS)。
  2. 应用备份:备份打好的JAR/WAR包和对应的配置文件。
  3. 备份验证:定期(如每季度)执行恢复演练,确保备份文件是有效可用的。

安全更新

  • 定期更新服务器操作系统、Java运行环境、MySQL数据库的安全补丁。
  • 关注Spring、MyBatis等使用框架的安全公告,及时升级版本。

6. 常见问题排查与优化技巧

在实际开发和运维中,总会遇到各种问题。这里记录几个典型场景和解决思路。

6.1 后端接口常见错误排查

问题一:接口返回404 Not Found。

  • 可能原因:URL路径错误;Tomcat未启动或应用未部署成功;Spring MVC的@RequestMapping注解路径配置有误。
  • 排查步骤
    1. 在服务器上用ps -ef | grep javasystemctl status tomcat检查服务进程状态。
    2. 查看Tomcat日志catalina.out,看应用启动时是否有异常,Controller是否成功加载。
    3. 在浏览器或Postman中直接访问服务器IP:端口,看是否能显示Tomcat默认页或应用首页(如果有的话)。
    4. 仔细核对小程序中request.js里配置的baseURL和后端接口的实际路径。

问题二:接口返回500 Internal Server Error。

  • 可能原因:后端代码运行时异常,如空指针、数据库连接失败、SQL语法错误等。
  • 排查步骤
    1. 查看应用日志:这是最直接的途径。定位到错误堆栈信息,找到异常的根源。
    2. 检查数据库:确认数据库服务是否运行,连接配置(URL、用户名、密码)是否正确,尤其是生产环境和开发环境配置是否切换。
    3. 检查SQL:如果是MyBatis的SQL错误,查看对应Mapper的XML文件,或者打开MyBatis的SQL日志(在配置文件中设置logImpl=STDOUT_LOGGING)来查看实际执行的SQL语句。

问题三:接口返回401 Unauthorized。

  • 可能原因:Token缺失、过期或无效;拦截器验证失败。
  • 排查步骤
    1. 检查小程序端是否成功登录并存储了Token,每次请求是否正确携带在Header中。
    2. 检查后端拦截器逻辑,特别是从Redis获取Token对应openid的部分。可能是Redis连接失败,或者Token键名不一致。
    3. 检查Token的过期时间设置是否合理。可以适当延长,并在每次有效请求后刷新过期时间(滑动过期)。

6.2 微信小程序端常见问题

问题一:真机预览正常,但体验版或线上版白屏/功能异常。

  • 可能原因:域名未配置;开发者工具设置了不校验域名,但真机环境需要。
  • 排查步骤
    1. 检查服务器域名:登录微信公众平台,在“开发管理”-“开发设置”-“服务器域名”中,确保request合法域名已正确配置为你的后端API地址(如https://your-domain.com)。
    2. 检查TLS版本:微信小程序要求服务器支持TLS 1.2及以上版本。可以用openssl s_client -connect your-domain.com:443 -tls1_2命令测试。
    3. 检查代码中的硬编码:确保代码中没有写死本地IP(如http://localhost:8080)作为请求地址。

问题二:wx.request在开发者工具成功,真机失败。

  • 可能原因:手机网络问题(如使用了代理);服务器HTTPS证书不受信任(特别是自签名证书);域名未备案(国内服务器)。
  • 排查步骤
    1. 关闭手机的代理设置。
    2. 为生产环境域名申请受信任的CA机构颁发的SSL证书(如Let‘s Encrypt免费证书)。
    3. 确保服务器域名已完成ICP备案。

问题三:图表组件ec-canvas不显示或显示异常。

  • 可能原因:Canvas层级问题;数据格式错误;初始化时机不对。
  • 排查步骤
    1. 检查.wxss中Canvas容器的z-index,尝试调高。
    2. onReady生命周期中初始化图表,确保页面渲染完成。
    3. setOption前,用console.log打印出option对象,确认数据格式符合ECharts要求。
    4. 在微信开发者工具的“调试器”-“Console”中查看是否有JavaScript报错。

6.3 数据库性能优化建议

当用户数据积累到一定量(比如十万条记录以上),查询可能会变慢。

优化一:善用索引。这是成本最低、效果最显著的优化手段。通过EXPLAIN命令分析慢查询SQL的执行计划。

EXPLAIN SELECT * FROM record WHERE user_id = 123 AND record_time BETWEEN '2023-01-01' AND '2023-12-31';

查看key字段是否用到了你创建的索引。如果没有,考虑创建或调整索引。对于record表,(user_id, record_time)的复合索引对按用户和时间范围查询是高效的。

优化二:避免SELECT *,按需取字段。尤其是在列表查询时,只取出需要的字段,减少网络传输和内存占用。

-- 不推荐 SELECT * FROM record WHERE user_id = 123; -- 推荐 SELECT id, amount, category_id, record_time FROM record WHERE user_id = 123;

优化三:分页查询优化。对于深度分页(如LIMIT 10000, 20),使用SELECT * ... LIMIT M, N效率会越来越低。优化方法:

  1. 使用索引覆盖:如果查询条件和排序字段都在索引中,可以只查索引,避免回表。
  2. 记录上次查询位置:例如,记录上一次查询结果中最大的idrecord_time,下次查询用WHERE id > last_max_id LIMIT 20。这要求记录是按ID或时间顺序连续增长的。

优化四:引入缓存。对于实时性要求不高的统计结果,如“本月总支出”、“年度趋势”,可以使用Redis缓存。在Service层,先查缓存,命中则返回,未命中则查数据库,计算结果并存入缓存(设置合理过期时间)。注意缓存键的设计要能区分用户和数据范围,例如stats:user:123:monthly_summary:202308

这个家庭记账本系统项目,从技术实现到部署上线,涉及了现代Web应用开发的多个核心环节。它不仅是一个功能完整的应用,更是一个绝佳的学习样板,理解了它,就打通了从移动端到服务端再到数据库的全链路思维。在实际开发中,还会遇到更多细节问题,但解决问题的过程,正是能力提升的阶梯。

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

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

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

立即咨询