news 2026/10/10 14:51:55

Spring Boot + Vue前后端分离律所案件管理系统开发全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot + Vue前后端分离律所案件管理系统开发全解析

1. 项目概述与核心需求拆解

1.1 律所管理系统到底解决了什么问题

我先把这个项目放在一个真实的场景里聊一聊。你在一个律师事务所里,日常办公最头疼的是什么?不是打官司,而是案件信息的流转和管理。一个律师手头同时跟进七八个案子,每个案子的当事人、对方当事人、案由、阶段、开庭时间、收费情况、文书材料,全散落在微信聊天记录、Excel表格、纸质卷宗里,想找一份材料翻箱倒柜,想统计年度收案量还要手动数半天。

这套"前后端分离Spring Boot律师事务所案件管理系统"要解决的就是这类问题。它把律所的核心业务——案件管理、客户管理、收费管理、文档管理、统计报表——从线下搬到了线上,做成了一套标准的Web应用。管理者和行政人员能看全局,律师能维护自己手头的案子,所有数据沉淀在数据库里,随时可查可统计。

标题里的技术栈很清晰:后端用Spring Boot,前端用Vue,持久层用MyBatis配合MySQL数据库,前后端通过RESTful接口通信。这套组合在当前的Java全栈开发里属于非常主流的技术选型,无论你是刚学完Spring Boot基础准备做第一个完整项目的新手,还是想给自己简历上加一个高质量业务系统的求职者,这个项目都值得完整跟一遍。我拆这个项目的时候会尽量把每一步的原理和坑都讲透,而不是单纯甩一段源码给你。

1.2 功能模块与角色权限设计

开发这类管理系统,第一件事不是写代码,是先分清角色。律师所有三类典型使用者:系统管理员负责系统配置和账号管理,律师/助理处理具体的案件业务,前台或行政人员负责客户接待和收退费登记。我按这个逻辑把系统拆成几个核心模块:

  • 登录与权限模块:基于JWT做身份认证,不同角色登录后看到不同的菜单和操作按钮;
  • 案件管理模块:案件的新增、分派、跟进、归档,支持按案号、当事人姓名、案由快速检索;
  • 客户管理模块:维护客户的基本资料、历史委托记录,做关联查询时能直接看到这个客户名下有几个案子;
  • 收费管理模块:记录律师费、退费、开票状态,统计每个案件的收支情况;
  • 文书管理模块:上传起诉状、代理词、判决书等文件,和案件关联绑定,支持在线预览和下载;
  • 统计报表模块:按时间段、律师、案件类型聚合数据,生成受案量、结案率等图表。

这里有个很多新人容易忽略的细节——权限校验到底做在前端还是后端?正确的做法是两端都做。前端控制菜单显隐只是提升体验,后端接口必须做角色校验才行,否则别人拿个普通账号直接调接口就能访问管理员数据。后面讲JWT实现时我会专门演示这个逻辑。

2. 技术选型与架构设计的底层逻辑

2.1 为什么选Spring Boot + Vue这套组合

市面上做管理系统可以选的技术栈太多了,Python的Django/Flask、PHP的Laravel、Go的Gin都能做。但Spring Boot在这类业务系统里优势非常明显,尤其是配合MyBatis操作数据库时。Spring Boot的自动配置机制极大简化了项目初始化过程,内嵌Tomcat让部署只需要一个jar包,Spring Security或JWT整合做权限控制有成熟的生态支持。

前端选Vue的理由也很实在。这套系统的主要使用场景是律所内部的电脑浏览器,操作密集、交互复杂、需要快速响应。Vue的响应式数据绑定适合处理这种表单密集型页面,Element UI或者Element Plus这一套现成的组件库,做表格、弹窗、表单校验效率非常高。相比React,Vue对国内开发者更友好,中文文档齐全,遇到问题搜解决方案也方便。

再强调一下MyBatis在这套架构里的定位。一般管理系统会大量涉及多表关联查询、条件动态拼接、统计报表这种灵活多变的SQL,MyBatis的XML映射文件给了你直接写SQL的空间,遇到复杂需求时不要和框架绕弯子,直接写SQL反而最清晰。配合MyBatis-Plus的分页插件,列表分页查询几行代码就搞定。这套组合不是性能最强的,但一定是业务开发效率最高的之一。

2.2 前后端分离的通信与认证机制

前后端分离的核心就是两边各管各的:前端只管页面渲染和用户交互,后端只提供数据接口,两者通过HTTP请求通信。这就带来两个必须处理的工程问题:跨域和认证。

跨域问题解释起来不复杂。前端的开发服务器跑在localhost的某个端口(比如8080),后端接口跑在另一个端口(比如9090),浏览器出于安全策略会拦截跨端口请求,这就是经典的"跨域"。解决方式在后端配置跨域过滤器,允许指定来源的请求访问接口,后面部署章节我会给出完整配置。

认证机制我用的是JWT。用户登录成功后,后端生成一个包含用户ID和角色的签名Token返回给前端,前端把Token存在本地存储里,之后每次请求都在HTTP请求头里带上这个Token。后端通过拦截器校验Token的合法性,并从中解析出用户信息。这么做的好处是服务端不用存储Session,天然适合前后端分离架构,也方便以后做负载均衡扩展。Token的过期策略、刷新机制我在实际操作中踩过坑,后面专门说。

2.3 数据库设计与表关系

数据库设计决定了这套系统能走多远,我见过太多项目因为表结构没设计好,写到后面越改越痛苦。这套系统我建议从这几张核心表说起:

  • sys_user(用户表):存储登录账号、密码、姓名、角色ID,密码存MD5加盐后的值;
  • sys_role(角色表):定义管理员、律师、前台等角色;
  • case_info(案件表):核心业务表,包含案号、案件名称、案由、受理日期、当前阶段、承办法官、关联客户ID和负责人律师ID;
  • customer_info(客户表):客户姓名、联系方式、证件号码、地址等;
  • fee_info(收费表):关联案件ID和客户ID,记录收费金额、收费日期、收费方式、开票状态;
  • case_document(文书表):关联案件ID,存文件名、文件路径、上传时间、上传人。

表关系上,customer_info和case_info是一对多,一个客户可以委托多个案件;case_info和fee_info是一对多,一个案件可以有多次收费记录;case_info和case_document是一对多,一个案件下有多个文书。外键在业务表里通过逻辑字段关联,不要真的在数据库层面大批量加物理外键,不然删除数据时会被约束卡住。这个经验是生产环境踩坑换来的。

建表的SQL里注意几个细节:金额字段用decimal(10,2),不要用float,否则计算精度会出问题;时间字段统一用datetime;每个表都加create_time和update_time字段,方便后续排查数据和做统计。

3. 后端核心实现要点

3.1 项目目录结构与分层设计

后端项目我建议按标准的Controller-Service-Mapper三层结构组织。Spring Boot项目创建好之后,包里大致这样分:

com.lawfirm.system ├── controller # 接口层,接收前端请求 ├── service # 业务层,处理业务逻辑和事务 │ └── impl ├── mapper # MyBatis数据访问层 ├── entity # 数据库实体类 ├── dto # 接口传输对象 ├── config # 配置类(跨域、拦截器、MyBatis) ├── common # 公共类(统一返回结果、异常处理、工具类) └── filter # JWT认证过滤器

为什么要把entity(数据库实体)和dto(接口传输对象)分开?因为数据库字段和前端需要的JSON字段经常不一致。比如实体里有密码字段,但登录接口的返回对象里绝对不能带密码;比如列表查询需要额外返回案件关联的客户姓名,而实体里只有customerId。把两者分开,各层之间解耦,改接口不影响实体结构。

以案件新增这个操作举例,完整调用链路是这样的:前端把表单数据JSON发到CaseController的addCase接口,Controller把JSON转成CaseDTO,Service层将DTO转成CaseInfo实体,然后调Mapper的insert方法写入数据库。插入成功后,Service层还要处理关联操作——比如给案件生成一个自定义格式的案号、给客户分配默认跟进人。这些逻辑放在Controller里会很臃肿,放在Service层是最合适的。

3.2 MyBatis整合与XML映射文件配置

Spring Boot整合MyBatis很简单,引入mybatis-spring-boot-starter依赖,然后在application.yml里配置数据源和Mapper扫描。我给出一个生产可用的配置:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/lawfirm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: yourpassword jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.lawfirm.system.entity configuration: map-underscore-to-camel-case: true

这里有两个关键配置。第一,map-underscore-to-camel-case设置为true后,数据库下划线字段customer_id能自动映射到实体类的驼峰属性customerId,不需要每个字段都手写ResultMap(除非遇到多表关联查询的复杂返回结构)。第二,mapper-locations指向XML文件目录,所有动态SQL写在XML里,比注解方式更利于维护和调试。

写XML映射文件时有个高频场景是条件动态查询。案件列表需要一个支持多条件筛选的查询,案号模糊匹配、客户姓名匹配、当前阶段精确匹配、负责人律师匹配,其中任意一个条件都可能为空。这种SQL在MyBatis里用 标签加 判断实现:

<select id="selectCaseList" resultType="com.lawfirm.system.entity.CaseInfo"> SELECT ci.*, cus.customer_name FROM case_info ci LEFT JOIN customer_info cus ON ci.customer_id = cus.id <where> <if test="caseNo != null and caseNo != ''"> AND ci.case_no LIKE CONCAT('%', #{caseNo}, '%') </if> <if test="customerName != null and customerName != ''"> AND cus.customer_name LIKE CONCAT('%', #{customerName}, '%') </if> <if test="caseStage != null and caseStage != ''"> AND ci.case_stage = #{caseStage} </if> <if test="lawyerId != null"> AND ci.lawyer_id = #{lawyerId} </if> </where> ORDER BY ci.create_time DESC </select>

标签会自动去掉第一个条件前面的AND,这个细节如果你手动拼WHERE很容易写错。这种写法查询条件组合非常灵活,前端传哪些参数就拼哪些条件,一个Mapper方法能应对列表页的所有筛选场景。

3.3 基于JWT的登录认证与权限控制

JWT的完整流程是:用户提交用户名密码,后端校验通过后生成Token返回给前端,前端存储并在后续请求头里携带,后端通过拦截器验证Token决定是否放行。核心代码分三步:

第一步,登录接口生成Token。用jjwt库,版本用0.9.1,这个版本对初学者最友好。生成逻辑:

String token = Jwts.builder() .setSubject(user.getUsername()) .claim("role", user.getRole()) .claim("userId", user.getId()) .setExpiration(new Date(System.currentTimeMillis() + 72 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();

这里有个设计细节值得说:Token有效期设72小时,律所场景下用户不会频繁登录,有效期太短会让人烦躁,设太长又有安全风险。自己练习时可以短一点,生产环境按公司安全规范来,一般8小时到24小时比较常见。

第二步,写一个拦截器,在Spring Boot里实现HandlerInterceptor接口,重写preHandle方法。从请求头取Token,解析失败就返回401状态码,解析成功就把用户信息放到request的attribute里,供后续业务逻辑使用。

第三步,在WebMvcConfigurer里注册拦截器,并配置放行路径。登录接口和静态资源不需要认证,其余接口全部拦截:

registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns("/api/auth/login", "/error");

权限控制还要做到角色级别的粒度。我在Service层的查询方法上加个判断:如果当前登录人是管理员,可以查全部案件;如果是普通律师,只能查自己名下或自己参与的案件。这个逻辑放在后端非常重要,前端不管怎么藏按钮,后端不校验等于白做。

4. 前端核心实现要点

4.1 Vue工程搭建与目录组织

前端推荐用Vue CLI或者Vite创建工程。我说一下Vite的方式,现在新项目用Vite更多,启动和打包速度都比之前的Webpack方案快不少。创建命令就一行:

npm create vue@latest

按提示选择Vue Router、Pinia、ESLint这些选项。创建的工程里,src目录按下面这种方式组织:

src ├── api # 接口请求封装 ├── router # 路由配置 ├── store # 全局状态管理 ├── views # 页面组件 ├── components # 公共组件 ├── layout # 主布局(侧边栏+顶栏+内容区) └── utils # 工具函数

组件库我建议用Element Plus,表格、表单、弹窗、菜单这些组件开箱即用,和Vue 3配合得很好,写管理系统的效率比手写原生HTML快好几倍。安装命令:

npm install element-plus

在main.js里全局注册:

import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' app.use(ElementPlus)

4.2 Axios封装与接口调用

前后端分离项目里,前端所有请求都要走Axios,但直接在每个组件里写axios.get会造成大量重复代码。正确做法是封装一个统一请求工具,统一处理基础URL、Token携带、响应拦截和错误提示。

在utils/request.js里:

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动携带Token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) // 响应拦截器:统一处理错误 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res.data }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } ) export default request

这里有个关键决策:开发环境通过Vite的代理把/api开头的请求转发到后端地址,避免跨域问题。生产环境则用Nginx统一转发。所以前端代码里不用写死后端IP和端口,一律以/api开头,这个习惯在团队协作时尤其重要,换环境部署不用改前端代码。

4.3 动态路由与菜单权限

前面说过不同角色看到的菜单不同,实现方式有两种。第一种最简单,写死在路由配置里,用meta.roles标注每个页面哪些角色能访问,路由守卫里判断用户角色是否匹配。第二种是动态路由,用户登录后根据角色从后端获取可访问的菜单和路由表,动态添加到路由实例里。第二种扩展性更强,适合这套系统——未来给某个角色新加页面权限,直接改数据库就行,不用重新发版。

动态路由的核心逻辑在router/index.js里配合Pinia实现。登录成功保存用户信息和角色后,在路由守卫里根据角色动态注册对应模块的路由。需要注意动态添加路由时要调用router.addRoute(),并且每次页面刷新后路由会重置,所以刷新时要重新拉取菜单信息。这个坑我实际开发时踩过,刷新后白屏,排查半天发现是路由没重新注册。

5. 完整部署教程

5.1 本地环境准备与项目启动

先说环境要求,这套系统本地跑起来需要的软件清单如下:

软件版本建议用途
JDK1.8或11运行Spring Boot
Maven3.6以上管理后端依赖
MySQL5.7或8.0数据存储
Node.js16以上运行前端工程
IDEA社区版即可开发工具

数据导入很简单,用Navicat或命令行执行项目里的lawfirm.sql脚本,这一步会创建数据库和全部表结构,还会写入一个默认管理员账号。执行SQL之前记得手动创建数据库,编码用utf8mb4:

CREATE DATABASE lawfirm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

后端启动流程:IDEA打开后端工程,等待Maven下载完依赖,修改application.yml里的数据库连接密码,然后运行主类Application。只要日志里出现"Started Application in x.xxx seconds"就说明启动成功了。

前端启动流程:命令行进入前端工程目录,先执行npm install装依赖,这一步在国内网络环境下会慢一些,可以换成国内的npm镜像源。依赖装完执行npm run dev,看到Local: http://localhost:5173/就说明启动成功。浏览器打开这个地址,用默认账号admin/admin登录。

5.2 前后端构建打包

开发调试用npm run dev,部署上线则不同。后端打jar包很简单,在项目根目录执行:

mvn clean package -DskipTests

第一次打包会花几分钟下载依赖,打包成功后target目录下会出现一个lawfirm-system-0.0.1.jar。运行这个jar包只需要一行命令:

java -jar target/lawfirm-system-0.0.1.jar

前端打包命令是:

npm run build

打包完成后dist目录里就是纯静态文件,部署时让Nginx把请求指向这个目录即可。

这里必须提一个生产环境部署的关键问题:前端打包后怎么访问后端接口?答案是配置Vite的build.proxy在生产环境不生效,所以打包后的前端代码里的/api请求必须由Nginx转发到Spring Boot服务上。Nginx配置示例:

server { listen 80; server_name lawfirm.example.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

关键点在于try_files后面要加/index.html,否则刷新某个路由时Nginx会返回404,因为前端的Vue Router用的是History模式,页面切换不经过服务端。

5.3 生产环境部署方案

生产部署我推荐用最简单的方式:服务器上装好MySQL和Java环境,把jar包扔上去用nohup后台运行。步骤就三条:

  • 上传jar包到服务器,例如/opt/lawfirm目录;
  • 执行nohup java -jar lawfirm-system-0.0.1.jar > app.log 2>&1 &;
  • 把前端dist目录上传到Nginx的html目录,改好配置重启Nginx。

不建议在服务器上打包,直接在本地打好包再上传,减小服务器压力。服务器安全组要放开80端口(Nginx)和3306端口(MySQL),但3306一定不要对公网开放,数据库只允许本机连接,这是基本的安全底线。

数据库迁移到服务器时,有两种常用方式:一种是导出SQL文件再导入服务器数据库;另一种是用Navicat的数据传输功能直接同步。数据量不大时两种都行,我习惯用mysqldump导出再导入,可控性更强:

mysqldump -u root -p lawfirm > lawfirm_backup.sql

服务器上执行:

mysql -u root -p lawfirm < lawfirm_backup.sql

6. 常见问题与排查技巧实录

6.1 跨域问题

前端启动后发现请求后端接口报CORS错误,浏览器控制台显示"Access-Control-Allow-Origin"。原因就是开发环境的Vite端口和后端端口不一致。解决办法有两层:第一层是后端加全局跨域配置类,允许前端地址访问;第二层是前端配置Vite代理,让/api请求走代理转发。

我建议开发阶段优先配置Vite代理,这样前端代码里不发任何绝对地址,全都用/api开头,代理配置在vite.config.js里:

server: { proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true } } }

ChangeOrigin设置为true会把请求头里的Host改成目标地址,有些后端框架对Host有校验时尤其重要。

6.2 MyBatis相关坑

MyBatis最常见的坑是日期类型的比较。在XML里写时间范围查询时,直接用大于小于符号会报错,要用转义字符。比如查创建时间晚于某个时间点:

<if test="startTime != null"> AND create_time &gt;= #{startTime} </if>

另一个高频问题是多表查询返回字段映射不正确。private String customerName死活查询不出来,排查步骤:先看SQL直接在数据库执行有没有结果,再看实体类有没有加对应属性,最后看resultType是否配置正确。这三个环节逐级排查基本都能定位。

6.3 数据库连接问题

连接MySQL报Communications link failure,九成情况是MySQL没启动,或者连接地址里的端口写错。还有一类常见报错是Unknown database,说明application.yml里的数据库名和实际创建的不一致。这些问题优先级排查方式:先看MySQL服务状态,再看连接配置,最后看防火墙。

使用MySQL 8.0时还有一个特殊点:驱动类名和旧版不同,要用com.mysql.cj.jdbc.Driver,URL里必须加serverTimezone参数,否则时区报错。

6.4 前后端联调问题汇总

前端请求返回304 Not Modified,这是浏览器缓存导致的。调试接口时把浏览器开发者工具Network选项里的Disable cache勾上,避免拿到旧的缓存结果误判代码问题。

前端页面刷新后登录状态丢失,这是Token只存在内存里导致的。解决方案是把Token存到localStorage,请求拦截器每次从localStorage取值。但localStorage有XSS风险,生产环境建议用httpOnly的Cookie方案,这个复杂一些,自己练习时用localStorage够用。

每次改完前端代码页面不刷新,这是Vite的HMR热更新没有生效。检查一下是不是把代码写在node_modules目录里了——我见过有人把源码放错位置导致热更新失效。源码必须写在src目录下,Vite才会监听文件变化。

7. 写在最后:破事里的经验

我从这个项目里学的最大教训是:做管理系统,数据库设计花的时间永远不算浪费。你花一个下午把表结构想清楚,后续每个模块写起来都会顺畅;你要是没想清楚就开写,后面每加一个功能都要回头改表,改表又影响实体类、SQL和前端页面,牵一发动全身。

另一个经验是关于调试的。前后端分离项目出问题时,第一步永远是用Postman或者直接浏览器访问后端接口,确认接口返回是否正常。很多新手遇到页面显示不对,先在前端代码里翻半天,最后发现是后端接口数据就没查出来。定位问题的顺序应该永远是:后端接口 → 前端请求 → 页面渲染,从数据源到展示层逐段排查,而不是倒着来。

这套系统后续还可以加不少东西:案件时间线功能、任务提醒、卷宗二维码扫码归档、对接钉钉或企业微信的消息通知。做成什么样取决于真实使用场景的需求,技术方案都是现成的,遇到需求往上加功能就行。希望这篇文章能让你不止把代码跑起来,还明白每个设计背后的为什么。

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

手写体、超大工程图与科学图表:TeleOCR 的适用边界实测报告

手写体、超大工程图与科学图表&#xff1a;TeleOCR 的适用边界实测报告 【免费下载链接】TeleOCR 项目地址: https://ai.gitcode.com/XingChen-AGI/TeleOCR TeleOCR 的社区热度几乎全部由榜单数字点燃&#xff1a;1.2B 参数、OmniDocBench v1.6 综合 96.87 分登顶、Wil…

作者头像 李华
网站建设 2026/10/10 14:50:57

基于Java与Vue的智慧停车反向寻车:多源融合定位与语义引导实践

简介&#xff1a;这份资源面向具备Java与Vue基础、熟悉Spring Boot与MySQL的开发者及计算机专业高年级学生&#xff0c;针对地下停车场寻车困难、信号弱、定位不准等痛点&#xff0c;给出智慧停车反向寻车语义引导与弱信号定位平台的完整项目实例。内容覆盖停车场数字孪生建模、…

作者头像 李华
网站建设 2026/10/10 14:50:25

Spark on YARN从零配置实战:版本选型、内存参数与避坑指南

去年接了一个数据平台的活&#xff0c;团队里几个新同学上来就被 Spark 配置折腾得够呛。明明看文档都会&#xff0c;一上集群就各种ClassNotFoundException、Container exited、物理内存超限。我自己的经验是&#xff0c;Spark 的配置说难不难&#xff0c;但它藏在一堆配置文件…

作者头像 李华
网站建设 2026/10/10 14:48:31

PyTorch CNN实战:MNIST手写数字识别课设包完整解析

简介&#xff1a;这份资源面向深度学习入门者与课程设计需求的学生&#xff0c;围绕卷积神经网络识别MNIST手写数字这一经典实验展开&#xff0c;帮助读者理解CNN的基本结构与训练流程。压缩包共11个文件&#xff0c;约176KB&#xff0c;包含Python脚本、Word设计报告、Markdow…

作者头像 李华
网站建设 2026/10/10 14:48:31

VOC手机检测数据集从零构建:标注、转换与踩坑指南

简介&#xff1a;面向目标检测算法训练与验证的数据集资源&#xff0c;围绕手机目标识别场景构建&#xff0c;适合使用YOLO、Faster-RCNN等主流框架的算法工程师、科研人员及计算机视觉学习者。包内共一万五千余个文件&#xff0c;包含五千余张真实场景jpg图片&#xff0c;以及…

作者头像 李华