SSM+Vue前后端分离个人事务管理系统开发实践解析
2026/9/16 8:50:18 网站建设 项目流程

简介:这套基于SSM与Vue的个人事务管理系统源码附带毕业论文,面向计算机、数学、电子信息等专业的毕业设计、课程设计及期末大作业场景,是一份可直接参考学习的完整项目。后端采用Spring、SpringMVC、MyBatis分层架构,前端用Vue.js配合ECMAScript编写动态交互页面,MySQL存储业务数据;功能覆盖个人日常事务的登记、删除、编辑、提醒、状态流转与分类统计等常见模块,代码包内还包含数据库初始化脚本与接口联调所需配置。资源包为ZIP压缩,共392个文件、约9.99MB,其中79个Java文件负责业务处理,36个Vue文件组成界面组件,19个JS脚本辅助前后端交互,16个XML配置完成框架整合;另有CSS样式、SVG/PNG图标、SQL脚本及npm相关的运行打包脚本,可帮助你快速搭建环境并启动项目。当前已有85人学习下载,随包附带的毕业论文可用于理解系统需求分析、概要设计与功能实现;借助安装、运行、打包批处理脚本,几乎能一键完成本地部署。项目目录结构清晰,代码可读性高,无论是作为毕业设计样本,还是作为SSM+Vue前后端分离技术的练手项目,都具备较好的参考与二次开发价值。

1. 个人事务管理系统:SSM 与 Vue 的前后端分离落地

个人事务管理系统这类选题,几乎每年都出现在 Java 相关的毕业设计和课程作业里,原因是它的业务边界足够清晰:用户注册登录、待办事项的增删改查、日程提醒、分类统计,每一块都能对应到 SSM 框架的核心知识点上。用 SSM 做后端接口、Vue 做前端页面,正好覆盖了从数据库表设计到前后端联调的完整链路,这也是为什么现成的源码包在求职和答辩场景里被反复检索。

这套资源内置了 MySQL 数据库脚本、SSM 后端工程、Vue 前端工程和毕业论文文档,Windows 环境下通过三个批处理文件1-install.bat2-run.bat3-build.bat就能把环境跑起来。对打算二次开发的人来说,真正的门槛不在启动,而在理解 SSM 的请求流转和 Vue 与后端的数据契约;这两个问题捋清楚,后面的改造就只是堆代码的事。

2. SSM 后端分层:从 Controller 到 Mapper 的事务处理链路

2.1 SSM 三件套在项目里的实际分工

SSM 是 Spring、Spring MVC、MyBatis 三个框架的组合,它们各管一段:Spring 负责对象创建和依赖注入,Spring MVC 负责接收 HTTP 请求并分发到 Controller,MyBatis 负责把 Java 方法和 SQL 映射起来。在个人事务管理系统里,一个典型的请求链路是:Vue 前端发送POST /api/task/save,Tomcat 把请求交给 DispatcherServlet,再由 HandlerMapping 找到对应的 TaskController,Controller 调用 TaskService,Service 通过 TaskMapper 接口执行 SQL,最后把结果逐层返回。

这种分层的直接好处是替换成本低。比如要换 ORM 层,只动 Mapper 和 MyBatis 配置,Controller 和 Service 不需要改;要加一个统计功能,只需要在 Service 里组合现有 Mapper 方法。

2.2 spring-mvc.xml 中关键配置的取舍

SSM 工程里spring-mvc.xml是请求路由的核心,启动阶段容易出错的地方基本都集中在这里。下面的配置片段是这套资源常见的形态:

<!-- 扫描 controller 包 --> <context:component-scan base-package="com.personal.controller"/> <!-- 开启 Spring MVC 注解驱动 --> <mvc:annotation-driven/> <!-- 静态资源放行,避免 .js/.css 被拦截 --> <mvc:resources mapping="/static/**" location="/static/"/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>

component-scan只扫描 controller 包,这一点容易被忽略。如果扫描范围过大,把 Service 也扫进来,会出现事务注解不生效的怪问题,因为 Spring 容器和 Spring MVC 容器各自管理了一部分 Bean,代理对象不是同一个。mvc:annotation-driven负责注册 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter,没有这行,Controller 里的@RequestMapping无法生效。

提示:如果项目里同时存在applicationContext.xmlspring-mvc.xml,要确认 Service 和 Mapper 在根容器里扫描,Controller 在 MVC 容器里扫描,职责分开。

2.3 Service 层的事务边界

个人事务系统的核心操作是待办事项的保存和状态更新,单表操作看起来不需要事务,但实际使用中会有批量操作场景,比如一次性勾选多条任务标记为已完成、把整天的日程复制到另一天。这时事务就很重要。spring-mybatis.xml里通常用DataSourceTransactionManager管理事务:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

Service 实现类的方法上标注@Transactional即可声明事务。需要注意readOnly=true的使用范围,只在纯粹的查询方法上设置,否则 Spring 会做只读优化,某些数据库方言下插入操作会报异常。事务默认只回滚运行时异常,如果在 Service 里 catch 了异常而没有 rethrow,事务不会触发回滚,数据会停留在不一致的状态。

2.4 MyBatis Mapper 的编写与参数传递

MyBatis 的 Mapper 接口和 XML 映射文件是 SSM 项目里写 SQL 的主要位置。以任务查询为例:

public interface TaskMapper { List<Task> selectTaskList(@Param("userId") Integer userId, @Param("status") Integer status); int insertTask(Task task); int updateTaskStatus(@Param("id") Integer id, @Param("status") Integer status, @Param("updateTime") Date updateTime); int deleteTask(Integer id); }

对应的TaskMapper.xml

<select id="selectTaskList" resultType="com.personal.entity.Task"> SELECT id, user_id, title, content, status, due_time FROM t_task <where> <if test="userId != null"> AND user_id = #{userId} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY due_time ASC </select> <update id="updateTaskStatus"> UPDATE t_task SET status = #{status}, update_time = #{updateTime} WHERE id = #{id} </update>

@Param注解用来显式指定参数名,这比直接用#{0}#{1}可读性好得多。有一个常见错误是方法只有一个参数时,XML 里直接写#{id}没问题;一旦参数变成两个,编译器在 debug 模式下能拿到参数名,release 模式下拿不到,不加@Param就是各种BindingException。个人事务系统里任务查询经常同时带 userId 和 status,所以从一开始就养成了用@Param的习惯。

<where>标签会自动处理多余的 AND 或 OR,多条件组合查询时不必在 SQL 尾部拼where 1=1,这也是 MyBatis 相比 JDBC 手写 SQL 的优势。

3. Vue 前端与 SSM 的联调:路由、接口封装与跨域

3.1 Vue 工程的目录结构与构建脚本

这套资源的前端不是简单的 HTML 页面,而是完整的 Vue 工程,.vue单文件组件、vue-routeraxios一应俱全。资源里保留了update-password.vue.bakIndexAsideStatic.vue.bak这类备份文件,是开发过程中用 git 或编辑器历史功能没有清干净的残留。拿到项目第一件事应该是先跑1-install.bat

call npm install call npm run dev

1-install.bat实际就是npm install2-run.bat是启动后端(通常是启动 Tomcat 或执行 Spring Boot 的 jar),3-build.bat是前端生产构建npm run build。如果npm install卡在某个依赖上,检查 Node 版本;Vue 2 项目在 Node 18 以上环境容易报 OpenSSL 错误,解决办法是set NODE_OPTIONS=--openssl-legacy-provider再执行。

3.2 vue.config.js 的代理与跨域处理

SSM 后端默认跑在 8080 端口,Vue 开发服务器跑在 8081 或 9090,浏览器直接访问后端接口会触发跨域。项目里常用的方案不是在后端加 CORS 过滤器,而是在vue.config.js里配置 devServer 代理:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }

所有以/api开头的请求都会被转发到http://localhost:8080pathRewrite/api前缀去掉。后端 Controller 的 RequestMapping 里不需要带/api,两边互不干扰。注意changeOrigin: true是给后端发请求时把 Host 头改成 target 的域名,一些后端框架会校验 Host,不改可能报 403。

3.3 axios 封装与 token 状态管理

个人事务系统需要登录后才能操作,前端通过 axios 统一处理请求头,把登录后的 token 带在每次请求里。常见封装方式:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { if (res.code === 401) { window.location.href = '/login' } return Promise.reject(new Error(res.msg)) } return res }, error => { return Promise.reject(error) } ) export default service

请求拦截器统一处理 token 注入,响应拦截器统一处理业务状态码。401 跳转登录页的逻辑写在这里,而不是在每个页面里各自判断,避免了大量重复代码。localStorage存储 token 有一个隐患,XSS 攻击可以读取到,但对课程设计和毕设场景来说,引入 vuex-persistedstate 做会话保持已经算加分项,不必强上 httpOnly Cookie。

3.4 路由守卫控制页面访问权限

路由配置里需要区分哪些页面需要登录。router/index.js中用全局前置守卫做拦截:

const router = new VueRouter({ routes: [ { path: '/login', component: Login }, { path: '/home', component: Home, meta: { requiresAuth: true }, children: [ { path: 'task', component: TaskList }, { path: 'todo', component: TodoList }, { path: 'profile', component: Profile } ] } ] }) router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.matched.some(record => record.meta.requiresAuth)) { if (!token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() } })

to.matched.some(record => record.meta.requiresAuth)是判断当前路由是否包含需要鉴权的 meta 标记。这样写的好处是子路由不用每个都加判断,父路由的 meta 会传导到子路由记录里。登录成功之后跳转到redirect参数指定的页面,比固定跳首页体验好。

4. 数据库设计与核心表结构

4.1 用户表与任务表的字段设计

个人事务管理系统最少需要三张表:用户表、任务表、分类表。用户表存账号信息,任务表存事务内容,分类表用于给任务打标签。下面是最小可用版本的建表 SQL:

CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, name VARCHAR(30) NOT NULL, sort_order INT DEFAULT 0, CONSTRAINT fk_cat_user FOREIGN KEY (user_id) REFERENCES t_user(id) ); CREATE TABLE t_task ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, category_id INT, title VARCHAR(100) NOT NULL, content TEXT, status TINYINT DEFAULT 0, due_time DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_task_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_task_cat FOREIGN KEY (category_id) REFERENCES t_category(id) );

password字段长度设置为 128,是因为业务里要考虑 MD5 加盐或 BCrypt 加密后的长度,纯 MD5 是 32 位,BCrypt 是 60 位。update_time使用ON UPDATE CURRENT_TIMESTAMP可以在行更新时自动刷新时间,省掉 Service 层的赋值逻辑。

4.2 索引设计与状态查询优化

任务表最频繁的查询是按用户和时间维度:某用户在某段时间范围内有哪些待办事项。这条查询在数据量上来后会很慢,需要加联合索引:

ALTER TABLE t_task ADD INDEX idx_user_due (user_id, due_time);

user_id放在联合索引最左侧,因为所有任务查询都会带用户条件;due_time在后面,用于排序和范围过滤。还有一种场景是按status过滤未完成任务,这个查询通常也会带user_id,所以不单独给status建索引,避免索引冗余。

4.3 数据库初始化脚本的导入时机

资源包里的 SQL 脚本文件通常是db_personal.sql之类的名字。导入时要注意编码问题,Windows 下用命令导入:

mysql -u root -p --default-character-set=utf8 < db_personal.sql

--default-character-set=utf8是必须的。如果脚本里有中文注释或数据,缺了这个参数,导入后会出现中文乱码,页面显示问号。另一个常见问题是 MySQL 版本差异,5.7 的脚本导入到 8.0 可能出现utf8mb4_0900_ai_ci排序规则不兼容,建议先把脚本里的ENGINE=InnoDB DEFAULT CHARSET=utf8mb4这部分统一,再看具体报错。

5. 部署验证与必踩的坑

5.1 启动顺序与日志定位

拿到源码后按顺序执行三个批处理,最容易出现的情况是2-run.bat启动的后端端口和vue.config.js里配的 target 不一致。先确认后端启动日志里的 Tomcat 端口,再确认前端代理指向同一个端口。后端启动失败时优先看两个日志:Tomcat 的 localhost 日志和 MyBatis 的 SQL 日志。MyBatis 的 SQL 日志可以通过mybatis-config.xml开启:

<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>

开启后控制台会打印每个 Mapper 方法执行的 SQL 和参数,排查ResultMap字段映射错误非常管用。

5.2 资源备份文件清理

项目中残留的.bak文件在 Eclipse 或 IDEA 里不会影响编译,但在打包部署时如果被一起打包,Tomcat 解析 Vue 静态资源时可能遭遇文件名冲突。规范做法是删除所有.bak文件,再执行3-build.bat重新打包。如果修改过前端代码但页面没变化,检查浏览器缓存和dist目录是否真的更新了,Vue 打包后的文件是带 hash 的,文件名变了说明构建成功。

5.3 接口验证的快速方法

后端接口是否正常,不一定要等前端页面起来。在浏览器地址栏直接访问http://localhost:8080/api/task/list?userId=1,如果后端没做登录拦截,能看到 JSON 说明接口通;如果返回 401,说明拦截器生效,需要先在请求头里带 token。用 Postman 或 apifox 调试接口时,注意请求头的Content-Type要和服务端@RequestBody的类型匹配,JSON 就写application/json,表单就写application/x-www-form-urlencoded,写错会报HttpMessageNotReadableException

最后调试通过的标准是:登录、任务新增、任务更新状态、按条件查询、删除这些主流程全部走通,且刷新页面后登录状态还在。做到这一步,这套 SSM + Vue 的个人事务管理系统才算是真正跑明白了,后续加文件上传、日历视图、数据统计功能,也只是在这套分层结构上做增量开发。

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

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

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

立即咨询