Spring Boot + Vue打造律所案件管理系统:从业务建模到权限隔离的全栈实践
发布时间:2026/10/1 5:02:13来源:尧图网络
做律师事务所案件管理系统这个项目一开始很容易被企业级这三个字吓住觉得好像得上一套很复杂的微服务、容器编排什么的。其实真正落地以后你会发现所谓企业级核心在产品逻辑的严谨度、数据的安全可控、以及权限隔离的精细程度上技术栈反而要选最成熟、最稳的组合。这次要说的这套Spring Boot Vue MyBatis MySQL的律所案件管理系统就是按这个思路去设计和实现的。我把它完整梳理一遍从业务拆解到数据库设计从权限控制到部署上线把关键的设计决策和实操中的坑都摆出来。不管你是准备拿它做毕业设计还是律所里的IT人员想搞一套内部系统或者初级开发想看看真实项目的结构这里面的思路和代码细节都能直接参考。1. 项目定位为什么律所管理不能照抄通用CRM1.1 案件管理不是客户管理那么简单市面上现成的CRM系统很多但律所的业务有很强的特殊性。普通企业的客户关系管理核心是把销售线索转化成订单关注的是转化率和成交金额。而律所的案件管理核心是案件生命周期——从立案、调查取证、撰写文书、开庭到结案归档每个环节都有严格的时间节点和法律文书要求。案件不是一个卖出去就结束的商品它是一个持续数月甚至数年的过程过程中会不断产生新的文档、新的费用、新的进展记录。另一个关键点是数据隔离。律所里A律师代理的案件B律师是不能随便看的哪怕同一个事务所。这和普通企业里销售团队之间共享客户信息完全是两个逻辑。系统里每一个案件都必须有明确的权属关系权限控制不是管理员/普通用户这种粗粒度能解决的。还有就是利益冲突检索。接一个新案子之前律所一定要查一下这个客户或者对方当事人是不是已经在所里有其他案件了。这个功能在企业CRM里完全不存在但律所的日常运营里它是刚需。所以照抄一套CRM改改字段只能做出一个花架子真正用起来会发现到处卡壳。这套系统从一开始就是对着律所的业务场景去建模的这也是它值得拿出来讲的原因。1.2 系统要解决的问题清单做技术开发的人容易一上来就想着表结构、接口设计但我建议先花时间把问题清单列清楚。律所管理者最关心的事情翻来覆去就是下面这几类案件台账不清所里现在有多少在办案件每个案件的承办律师是谁走到了什么阶段跟进过程不透明合伙人想知道某个案子进展到哪一步了还得跑过去问律师消息层层传递后严重失真。文档管理混乱起诉状、答辩状、代理词有些还在律师个人电脑里躺着所里想调取模板都没有统一入口。收费节点难以盯控律师费是分阶段收的哪一笔该催了、哪一笔已经到账全靠财务手工翻聊天记录。敏感信息泄露风险案卷材料拍照外传、离职律师带走客户资料传统文件柜管理和个人电脑存储根本防不住。这套系统的功能设计全部围绕上面这些问题展开。说白了不是做什么炫酷的东西而是把律所每天的繁琐事务纳入到规范流程里面去让管理层对全所的运营状况有数让律师对承办案件的进度有底让行政和财务从重复劳动里解脱出来。2. 技术栈选型为什么是Spring Boot Vue MyBatis MySQL2.1 后端Spring Boot的价值不在“新”而在“稳”后端框架选Spring Boot几乎是这个场景下的必然选择。我要说的是Spring Boot在这类管理系统里的优势核心不是它有多新潮而是它把大量繁琐的配置自动化了让你把精力集中在业务代码上。以前用SSMSpring Spring MVC MyBatis搭建项目光是配置文件就要写一大堆数据源配置、事务管理器配置、MyBatis的SqlSessionFactory配置、组件扫描配置、视图解析器配置……刚起步光搭建环境就得折腾一整天。Spring Boot通过自动配置和起步依赖解决了这个问题。比如引入spring-boot-starter-web内置的Tomcat就给你配好了引入mybatis-spring-boot-starterSqlSessionFactory这些也会自动装配。Spring Boot 2.6.x这个版本我建议特别留意。它在配置上有些调整比如循环引用默认被禁止了spring.mvc.pathmatch.matching-strategy默认改成了path_pattern_parser。如果你做的项目里用了Swagger这类依赖默认配置下启动大概率会报错需要在配置里加一行spring: mvc: pathmatch: matching-strategy: ant_path_matcher这类小坑如果没经历过初次部署时会卡住很久。用Spring Boot不丢人踩坑后能快速定位原因反而能让你对框架的原理有更深的理解。2.2 前端Vue更适合管理系统这类重交互场景前端选了Vue搭配Element UI组件库。管理系统和展示型网站不一样它的特点是表单密集、表格密集、弹窗密集。Vue的双向数据绑定在处理这类交互时有天然优势操作表单时数据实时同步不用像jQuery时代那样手动操作DOM。用Vue的时候我对组件的拆分有一个比较深的体会一定要把页面和业务组件分清楚。比如案件列表页面基础的CRUD逻辑是通用的但每个案件列表的行内操作查看进度、分配律师、上传文书、记录费用是各不相同的。我把这些操作封装成独立的业务组件放在components目录下不同页面去组合使用。Vue Router在这种系统里还有一个重要角色动态路由与权限控制。前端不能只靠隐藏按钮来做权限不同角色的菜单本身就应该是不同的。我的做法是登录接口返回该用户的角色和权限码前端根据权限码动态生成路由表再配合路由守卫做拦截。这样即使有人通过URL强行访问没有权限的页面也会被路由守卫重定向回首页。Vue生态里很多人纠结到底用Vue 2还是Vue 3。对这类管理系统两者都能胜任。Vue 3的Composition API在代码组织上确实更清爽尤其是把关联的逻辑内聚在一起。如果从零开始写我推荐直接上Vue 3。但如果你要维护的是现有项目Vue 2也未到不能用的地步关键看团队的熟悉程度。2.3 持久层MyBatis在复杂查询场景下的灵活优势MyBatis和JPAHibernate的选择在社区里争了很多年。我的观点是写复杂SQL多的系统MyBatis更顺手。律所管理系统的数据查询极其依赖SQL的灵活性。比如统计本月各律师新收案件数、筛选所有代理费超过5万且尚未结案的案件、按客户维度汇总历史委托次数。这种查询在MyBatis里写SQL非常直接表连接、子查询、聚合函数都能随心所欲地控制。JPA虽然也能写Query但一旦涉及动态条件拼装就难免要借助Specification这类机制写起来抽象度很高团队里水平参差不齐的话维护起来会很痛苦。MyBatis的方式就直白很多XML里写SQL条件用动态标签拼可读性高也好调试。还要提一下MyBatis的二级缓存。案件管理系统的数据实时性要求很高一个案件的状态变了当事人可能马上就在系统里刷新看到了。我之前有过一次教训给某个查询方法加了二级缓存结果办案人员在页面上更新了案件进度另一个入口查到的还是旧数据造成了不小困扰。后来我干脆对案件相关的所有查询一律不使用二级缓存或者直接关闭全局缓存开关。这类数据对一致性要求极高为了高性能做缓存得不偿失。2.4 数据库选型MySQL足以扛住这个体量数据库选MySQL其实没有什么悬念。律所的案件管理系统数据量级很难到千万级别事务处理、并发规模也没有到需要上Oracle或分布式数据库的程度。MySQL在这个场景下完全够用而且运维成本低、生态资料多遇到问题容易找到解决方案。真正要上心的是两个细节。第一是存储引擎用InnoDB别用MyISAM。很多人建库的时候不留意默认引擎实际生产里案件和文书表都是要频繁做行级更新和事务操作的MyISAM的表级锁在这类场景下会拖慢整体性能。现在的MySQL 8.0默认就是InnoDB但如果你从旧版本迁移过来记得检查旧表。第二是字符集统一用utf8mb4。不要只看到utf8就以为万事大吉代理人姓名里如果出现生僻字或者当事人上传的文档标题带个特殊字符utf8可能直接报错或者写入乱码。utf8mb4才是完整的UTF-8实现能把emoji也存进去现在的新项目无脑选这个就对了。另外MySQL版本的选型上8.0比5.7更推荐。8.0默认字符集就是utf8mb4窗口函数、CTE这些新特性在写统计类SQL时非常好用比如计算某位律师的收案排名、做累计趋势分析等。安装的时候顺手把mysql_native_password相关兼容问题了解清楚就好。3. 核心业务模块拆解与实现思路3.1 案件台账从立案到归档的全周期管理案件是这套系统的绝对核心所有功能都是围绕案件展开的。我设计的一级状态包括立案审查、在办、已结案、已归档。但这个状态机必须足够灵活因为不同类型的案件民事、刑事、非诉在办阶段还有各自的流程节点。案件列表就是一部律所的经营台账。列表里要展示的核心字段包括案号、当事人名称隐去敏感部分、案件类型民事/刑事/行政/非诉、承办律师、立案日期、当前阶段、下次跟进日期、标的额。列表支持按多种条件组合检索尤其是我的案件、本周到期事项这类个人化的快捷筛选律师每天打开系统先看这两个视图就知道今天该干什么了。案件的详情页一定要用**页签Tab**来组织信息基本信息、当事人信息、办案进展、相关文书、费用记录、时间线操作日志。因为案件详情要承载的信息实在太多了堆在一个滚动页面里会让人崩溃分页签既清晰又方便扩展。立案登记是整个系统流程的起点。这里我加上了一个利益冲突检索的环节登记当事人信息时要自动检索库内是否有同名或关联当事人正在代理中。如果命中提示立案人员人工确认。这只是个自动提示不阻断流程但已经能帮律所减少很大一部分执业风险。跟踪一个案件最重要的就是事件时间线。每次跟进、每次开庭、每次文书提交都在案件详情里留一条记录形成完整的办案轨迹。时间线不仅是给律师自己看的更是给合伙人和管理者看的——他们不用找律师当面问打开系统就知道案件的来龙去脉这对管理透明度的提升是最直接的。3.2 角色与权限律所里的信息隔离墙权限设计是这个系统里含金量比较高的部分也是律所区别于普通企业系统的地方。我定义的权限体系是这样的角色权限范围说明系统管理员系统全部功能负责系统配置、用户管理、数据维护合伙人全所案件查看、审批、数据统计可以看全所运营数据但不参与具体办案主办律师承办案件的全部操作对自己承办的案件有最高操作权限协办律师/助理被分配案件的协助操作可以更新案件进度但不能结案财务人员收费记录、开票信息、财务统计无案件详情查看权客户外部仅被授权案件的部分信息查看自己案件的进度、文书、缴费记录权限控制要落实到功能和数据两个层面。功能层面就是按钮和菜单的显隐控制数据层面是行级的数据权限。举个具体的例子协办律师小李能看见他协助的那几个刑事案件但同一时间另一个组办理的商事案件他在案件列表里根本搜不到即使搜到了点进去也会被拦截。这个效果是通过MyBatis的拦截器实现的在SQL执行前自动拼接数据权限条件where子句里加上承办人或者协办人的过滤条件。这种数据权限的实现方式相当于把权限逻辑下沉到了SQL层业务代码里不用每次查询都手动写权限条件也不容易漏。这个设计我觉得是这个项目里最值得借鉴的地方之一。3.3 文书管理模板驱动的文档生成文书管理是律所系统里容易被低估的部分但真正用起来之后律师给的反馈反而是这一块最实用。系统里为常用文书建立了模板库起诉状、答辩状、代理词、法律意见书、律师函、委托合同等。每个模板在Word里设置好占位符例如${当事人姓名}、${案号}、${承办律师}系统读取模板后从案件信息里自动匹配填充生成一份新的Word文档供律师下载微调。这一下子省去了大量重复打字的时间。文档存储这块我用了本地存储加路径入库的方式。没有上FastDFS或者MinIO这类分布式文件系统因为在单机部署阶段这些组件反而增加了运维压力。文件的真实存储路径保存在服务器的磁盘上MySQL里记录的是相对路径和文件的元数据上传人、上传时间、所属案件。后续如果业务量涨起来了文件存储也不难迁移到对象存储因为数据库里存的只是路径映射改动范围可控。还有一个细节所有文书的下载、打印都要留日志。律师的代理词、辩护意见在案件审结之前都算敏感信息谁在什么时候下载过系统里要有记录。这个审计日志模块虽然平时不显眼但真遇到争议时就是保护律所的证据链。3.4 收费与开票财务模块的简化思路律所收费比普通企业销售要复杂一些因为律所常见的是风险代理和分阶段收费一笔案子分首期、中期、尾款等多个节点周期性跨越好几个月。我在案件里维护了多个收费计划阶段1签约首期款 20,000元 已收 阶段2开庭前应付 30,000元 待催 阶段3判决后尾款 30,000元 未到账财务人员看到的就是一张按案件维度的应收表哪个案件哪笔钱该催了一目了然。业务上还可以做一张全局的应收账款视图按时间排序快到期和已逾期的款项这就是律所管理者最直观的现金流视图。这里有一个容易忽略但很实际的点金额字段一律用decimal不能用float或double。涉及钱的业务字段类型用浮点类型就是给自己埋雷。decimal(12,2)能保证金额精度计算代理费提成比例时也不会出现0.10.2不等于0.3的尴尬。数据库层面就把它卡死代码层面再配合Java侧的BigDecimal双保险。4. 数据库设计案件系统的关键表结构4.1 核心主表设计思路整个数据库里最核心的表就三张案件表、当事人关联表、案件进度表。其他文书表、费用表、操作日志表、用户表都是围绕它们来挂的。案件主表的关键设计思路是这样的CREATE TABLE case_info ( case_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 案件ID, case_no VARCHAR(50) NOT NULL COMMENT 案号业务编号, case_type TINYINT NOT NULL COMMENT 案件类型1-民事 2-刑事 3-行政 4-非诉, case_status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0-立项审核 1-在办 2-已结案 3-已归档, case_title VARCHAR(200) NOT NULL COMMENT 案件名称/摘要, client_id BIGINT NOT NULL COMMENT 委托方当事人ID, opposing_party VARCHAR(200) COMMENT 对方当事人, lead_lawyer_id BIGINT NOT NULL COMMENT 主办律师ID, case_amount DECIMAL(12,2) DEFAULT NULL COMMENT 标的额, accept_date DATE NOT NULL COMMENT 立案日期, close_date DATE DEFAULT NULL COMMENT 结案日期, is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (case_id), UNIQUE KEY uk_case_no (case_no), KEY idx_lead_lawyer (lead_lawyer_id), KEY idx_status (case_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT案件主表;这张表设计时有几个值得注意的决策点。案号case_no要独立于主键存在。主键case_id是自增的只给系统内部用案号才是给律师看的业务编号格式一般是年份所内编号比如2025-民-0118。案号要有唯一索引律师在系统里查案件主要就是输入案号。为什么要有逻辑删除标记。律所的案件查询记录涉及到执业合规审查数据是不能物理删除的。就算错了也只能标记作废不能从库里消失。所以每张核心业务表都留了is_deleted字段查询默认加条件is_deleted 0。这个习惯在涉及审计、财务的系统里一定要有。update_time用ON UPDATE CURRENT_TIMESTAMP。这个字段能在数据变更时自动刷新排查问题时能快速知道这条数据最后是在什么时候被改动的。很多新手建表的时候不写这个后面排查脏数据时少了个关键线索。4.2 案件进度和时间线表案件状态只是粗粒度实时动态要依靠进度表去支撑。CREATE TABLE case_progress ( progress_id BIGINT NOT NULL AUTO_INCREMENT, case_id BIGINT NOT NULL, operate_type VARCHAR(50) NOT NULL COMMENT 操作类型跟进/开庭/保全/调解/文书提交, content VARCHAR(1000) NOT NULL COMMENT 进度描述, operator_id BIGINT NOT NULL COMMENT 操作人, operate_time DATETIME NOT NULL COMMENT 操作时间, next_follow_date DATE DEFAULT NULL COMMENT 下次跟进日期, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (progress_id), KEY idx_case_id (case_id), KEY idx_next_follow (next_follow_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT案件进度时间线;这张表撑起了案件详情页的时间线视图也支撑了首页的今日待跟进提醒功能。next_follow_date这个字段很多人一开始会忽略但业务上它的价值非常大——它形成了驱动的闭环律师每天上班打开系统待办清单上能看到今天该跟进哪些案件不用再靠脑子里记或笔记本上翻。同一张进度表也解决了合伙人想了解案件进展的诉求。他们不用问律师直接看时间线就能了解大概。次数多不多、间隔有没有太久、下一步动作是什么时间线上都反映得出来。4.3 用户与角色的表设计用户和角色的关系用的是RBAC基于角色的访问控制模型三张基础表加两张关联表。但和普通RBAC不一样的地方是用户表上还要挂一个user_type律师/助理/合伙人/财务/管理员和employee_no所内工号。因为不同类型用户对应的业务逻辑有很大区别比如只有user_type1律师才能被指派为主办律师助理只能被指派为协办人。再补充一个细节不要把密码明文存在数据库里这是我反复强调的点。密码用BCrypt加密存储每个用户的盐值都是随机的即使数据库泄露了攻击者也很难反推出明文密码。Spring Security的BCryptPasswordEncoder直接就能用。如果项目里暂时没引入Spring Security也可以用Shiro但无论如何咸菜存储密码这个底线不能破。5. 关键业务逻辑与实现细节5.1 案件状态流转的控制案件状态的流转不能做成随便改必须走规定的路径。比如已归档是终态不能再跳回在办。立案审核中只能由合伙人审批后进入在办。我用一个状态机来控制public enum CaseStatus { PENDING(0, 待立案), ACTIVE(1, 办理中), CLOSED(2, 已结案), ARCHIVED(3, 已归档); private final Integer code; private final String desc; // 定义合法状态流转 private static final MapInteger, SetInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(0, Collections.singleton(1)); // 待立案 - 办理中 TRANSITIONS.put(1, new HashSet(Arrays.asList(2))); // 办理中 - 已结案 TRANSITIONS.put(2, Collections.singleton(3)); // 已结案 - 已归档 } public boolean canTransitionTo(CaseStatus target) { SetInteger allowed TRANSITIONS.get(this.code); return allowed ! null allowed.contains(target.code); } }所有状态变更都走同一个更新接口接口内部先做合法性校验再更新状态并插入一条进度记录。这个设计的好处是以后要扩充状态比如加入调解中、上诉中只需要在TRANSITIONS里加合法路径业务代码不用大改状态机演进很容易。5.2 案件访问权限的拦截实现前面提到数据权限下沉到SQL这里展开说说具体怎么做。我在MyBatis里写了一个拦截器拦截所有select语句。拦截器运行时读取当前登录用户的上下文信息判断用户的角色范围。如果是协办律师SQL自动追加条件case_id in (select case_id from case_assistant where assistant_id 当前用户id)。如果是合伙人不加这个条件全所可见。Intercepts({ Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class}) }) public class DataScopeInterceptor implements Interceptor { // 读取当前登录用户信息 // 拼接数据权限SQL }实现的原理是拦截StatementHandler修改原始SQL后再放行。这里要注意拼接条件时要防止SQL注入变量一律用参数绑定不要直接拼字符串。MyBatis自身的#{}参数处理会帮我们转义但自己拼权限条件时容易大意。这个设计把权限控制从每个查询手动加条件变成了框架自动处理大大减少了遗忘权限条件的可能。代码审查时发现加了拦截器之后新增的查询基本上不再出现漏加权限条件的Bug了这是架构约束带来的收益。5.3 MyBatis在报表统计中的常见写法统计报表是律所管理者使用频率很高的模块比如每月收案数、收费回款情况、律师办案饱和度。这类统计用XML里的SQL来写非常顺手。我举一个典型场景统计当前年度每个月的立案数量变化。select idcountCaseByMonth resultTypemap SELECT DATE_FORMAT(accept_date, %Y-%m) AS month, COUNT(*) AS total FROM case_info WHERE accept_date #{startDate} AND is_deleted 0 GROUP BY DATE_FORMAT(accept_date, %Y-%m) ORDER BY month /select关联查询也很常用比如要统计每个律师的在办案件数量select idcountActiveCaseByLawyer resultTypemap SELECT u.real_name AS lawyerName, COUNT(c.case_id) AS caseCount FROM user u LEFT JOIN case_info c ON c.lead_lawyer_id u.user_id AND c.case_status 1 AND c.is_deleted 0 WHERE u.user_type 1 GROUP BY u.user_id ORDER BY caseCount DESC /select这里有一个小技巧要注意LEFT JOIN后面的c.is_deleted 0要放在ON条件里而不是WHERE里。如果放到WHERE里那些没有任何案件的律师c.case_id为NULL会被过滤掉统计结果就不对了。这种细节直接决定报表数据的准确性。5.4 文件上传与预览的落地细节律所系统里文件上传很频繁代理词、证据材料、判决书什么格式都有。我做了统一的上传组件限制单个文件不超过20MB同时用白名单控制文件类型。法律文书一般是doc、docx、pdf、jpg、png这些类型白名单之外的扩展名一律拒绝。这一步能挡住很大一部分恶意上传风险。上传时文件重命名有个规范用系统生成的UUID命名原始文件名存到数据库元数据里。这样可以避免两个律师上传了同名文件互相覆盖的悲剧。PDF预览用的是浏览器内置的iframe加pdf.js方案Word文档预览比较麻烦但我发现转PDF后再预览是律所场景下最适用的方案。具体做法是部署环境上安装LibreOffice用命令行把docx统一转成PDF再输出到前端预览。这段转换逻辑不需要太高级稳定可靠就行。6. 项目部署与运维中的实际问题排查6.1 前后端打包部署流程这个系统的前后端是分离的部署时有两个部分。前端是Vue项目打包用npm run build产物是一个dist静态目录。我不建议在开发环境那种繁琐的代理方式直接上生产而是用Nginx托管前端静态文件并做反向代理转发API请求。Nginx的核心配置有两点静态文件的try_files规则和API的代理转发。server { listen 80; server_name your-domain.com; root /opt/case-system/frontend/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } client_max_body_size 20m; }try_files这行一定要有否则直接访问/case/123这种前端路由时Nginx会返回404而不是把页面交给前端路由去处理。后端的Spring Boot应用打包成Jar包后我用一个start.sh脚本来管理启停#!/bin/bash nohup java -Xms512m -Xmx1024m -jar case-system.jar \ --spring.profiles.activeprod \ --server.port8080 \ /opt/case-system/logs/app.log 21 生产环境内存给到1G起步具体看服务器配置但-Xmx千万不要不设JVM默认用物理内存的四分之一做堆如果服务器内存不大很容器直接卡死。日志输出到固定文件排查问题也不用到处找。6.2 数据库连接池配置Spring Boot默认的HikariCP连接池性能很好但默认参数对生产环境不一定合适。我配置了最大连接数20、最小空闲连接5、连接超时30秒spring: datasource: url: jdbc:mysql://localhost:3306/law_firm?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000连接池参数需要结合并发量来定不能盲目上大。律所系统并发量一般不高20个连接绰绰有余设置太大反而浪费数据库的连接资源。useSSLfalse这个参数要特别注意。测试环境连接MySQL时如果没配SSL证书经常报连接被拒绝的错误。无论使用mysql-connector-java还是mysql-connector-j开发环境下先把useSSLfalse加上能省去很多麻烦。后面如果要做真正的加密传输再单独配置证书。6.3 部署过程中常见的坑速查我整理了一下这个项目中实际踩过的坑另外做了一份排查表按列表查就行。现象原因处理方式前端页面刷新后404Nginx未配置try_files增加路由回退规则指向index.html上传文件超过大小报错Nginx和SpringBoot都有文件大小限制Nginx配client_max_body_size 20mSpringBoot配置spring.servlet.multipart.max-file-size接口跨域报错前端和后端端口不同生产环境用Nginx代理同源访问开发环境后端配CrossOrigin或全局CORS配置中文乱码数据库连接字符集不匹配URL加characterEncodingutf8mb4MySQL表用utf8mb4MySQL连接TTL后丢失数据库空闲连接被断开HikariCP的idle-timeout不要超过数据库wait_timeout案件查询缓慢缺少索引或查询未命中索引加EXPLAIN分析执行计划补索引还有一个容易被忽略的坑是时区问题。MySQL服务器时区跟业务所在地时区如果不一致存进去的时间和取出来的时间可能差好几个小时。连接串上强制指定serverTimezoneAsia/Shanghai同时MySQL那边也设置好default-time-zone 08:00两边对齐后就不会出幺蛾子了。6.4 数据备份策略律所的数据承载着客户隐私和律所核心资产它甚至比软件系统本身还重要。代码没了可以重新开发数据丢了就是灾难。我设置了一套双重的备份方案每天凌晨2点用mysqldump做全量备份备份文件保留最近30天。为什么要凌晨2点因为夜深人静的时候业务基本停了备份出来的数据一致性最好。每天中午12点和晚上8点做增量备份通过binlog日志同步实现数据最多丢失半天。# 每日全量备份脚本 mysqldump -u root -pYourPassword --single-transaction --routines --triggers law_firm | gzip /backup/mysql/law_firm_$(date %Y%m%d).sql.gz # 清理30天前的备份 find /backup/mysql/ -name *.sql.gz -mtime 30 -delete备份这件事很多人知道应该做但真正按期执行的少。我的建议是不只把备份脚本写出来还要配上定时任务并验证恢复流程。至少一个月做一次恢复演练选一台测试机器把备份文件还原一遍确认能正常启动、数据完整。要不然某天真要恢复数据时发现备份文件损坏那时候就晚了。7. 一些容易出错但值得做好的业务细节7.1 重点字段的校验规则业务字段的校验在管理系统里很容易被轻视但恰恰是这些校验规则决定了系统是不是专业。以案号登记为例规则是年份-案由简称-四位流水号前端要做格式校验后端同样要做防止绕过前端直接用接口提交脏数据。当事人手机号的校验也要严格一些。律所给当事人发短信提醒开庭时间的时候手机号不对会导致大量消息发到错误的号码引发当事人投诉。校验规则就是标准的11位手机号正则加上后端数据持久化前的二次校验。金额字段的校验更不用说代理费不能为负风险代理的提成比例必须在0到1之间这些不校验好财务数据一定乱。7.2 日志记录的两个层次系统日志和业务日志要分开。系统日志放在Logback的配置文件里记录框架运行信息、SQL执行情况、异常堆栈主要供开发排查问题业务日志是另外一个单独的日志表记录用户在系统里的关键操作比如谁在什么时候给哪个案件分配了主办律师谁在什么时候导出了当事人名单。业务日志的重要性在系统上线三个月之后会越来越明显。律所内部产生纠纷或者客户对某个环节提出质疑时日志就是最硬的事实依据。我在每个关键业务接口上都加了Log注解用AOP自动记录操作日志不需要在每个方法里手写很省事。7.3 前端组件封装的心得Vue开发到这个体量的系统组件化做得好不好直接影响开发效率。我封装了几个高频复用的组件实际效果很好SearchForm组件统一封装案件列表的搜索栏配置化生成查询条件页面代码大幅减少。DataTable组件统一封装分页表格自带加载态、空态、操作列插槽所有列表页风格保持一致。UploadFile组件统一封装文件上传内部处理进度条、类型校验、错误提示上传入口在不同页面重复使用时不用重写逻辑。StatusTag组件统一封装案件状态标签根据状态码显示对应的颜色和文案改状态样式时只改一处。组件化的收益是边际的写第一个通用组件时会多花一点时间但后面每次复用它省的时间都是净赚。而且这样还能保证整个系统交互的一致性用户在不同页面看到的操作方式是一致的学习成本低这个体验细节对内部系统也很重要。8. 常见问题与排查技巧实录8.1 Swagger和Spring Boot 2.6的兼容问题项目里接Swagger做接口文档结果Spring Boot 2.6.x下直接启动报错提示springfox和path_pattern_parser冲突。原因前面提过一句话这里具体说说排查路径。报错信息会指向PathPatternParser和AntPathMatcher之间的冲突解决方式就是让Spring MVC的路径匹配策略回到旧版的ant_path_matcherspring: mvc: pathmatch: matching-strategy: ant_path_matcher加完配置重启就正常了。遇到这种兼容性问题优先去官方GitHub的issue区搜一般踩坑的人都很多解决方案早都在issue里挂着。8.2 数据权限遗漏导致的信息越权权限这块最容易出的问题就是写代码时忘了校验权限。比如写了一个查询案件详情的接口只校验了登录状态没校验这个用户是否有权访问这个案件。我最终的解决思路是在拦截器层面还是SQL层面都做了控制。接口层校验用户是否有调用接口的权限功能权限拦截器层自动拼接数据范围条件数据权限。两层都过了才能拿到数据。有了这次教训之后我再也不依赖业务代码里记得加权限这种写法改为在框架层面强制约束。这也是我为什么反复强调数据权限要下沉到SQL层的原因靠自觉是不可靠的要靠架构去约束。8.3 关联表查询N1问题案件列表页最开始有个性能问题每页展示20条案件每条案件要查一次当事人姓名结果一页SQL执行了21条。数据量小的时候看不出问题数据量到五千条以上时肉眼可见地卡顿。优化方式是把关联查询改成一次JOIN或者用IN查询一次性取回所有当事人的信息。MyBatis的resultMap里做关联映射也容易踩这个坑尤其用association时要注意批量映射。我后来干脆在列表页不用ORM的关联查询直接写多表连接SQL逻辑清晰也好控制性能。8.4 进程被杀导致的Jar包无法重启运维时遇到一个诡异的问题部署的Java进程总是跑几天就消失重启后又能正常跑几天。排查完发现是服务器内存不足进程被系统OOM Killer杀掉了。解决方式其实不复杂给JVM设置合理的堆内存上限比如-Xmx512m确保不会无限吃内存。在start.sh脚本里加启动命令前检查一下当前剩余内存。条件允许的话就不要和其他重型服务混部在同一台服务器上还是那句话专机专用能省很多事。内存不足导致的应用被杀往往很难通过日志发现问题因为JVM自己都没来得及打印错误就被操作系统强制结束了。看dmesg或/var/log/kern.log里有没有Out of memory的字样这是定位这类问题的直接证据。9. 做完这套系统后沉淀下来的经验分享9.1 技术是工具业务理解才是关键这套系统从需求分析到完整上线最大的体会是写代码很快把业务规则搞透很慢。律所的业务语言有一套自己的体系什么风险代理、保全、代书这些词的背后都是不同的业务流程。如果开发人员不花时间搞清楚这些术语那做出来的系统一定是浮在表面的——界面看起来像那么回事业务上一跑就露馅了。我的做法是前期集中花了一整天时间坐在一位执业超过十年的律师旁边把他处理一个普通民事案件从接案到结案的完整流程走了一遍。一边记笔记一边问这一步需要谁审批、这个文书从哪里来的、逾期会有什么后果。这份手写笔记后来成为系统设计文档的底稿比任何需求调研问卷都管用。9.2 宁可做得朴实不要过度设计这个项目里我主动放弃了很多看起来高级的架构选项没上微服务没上消息队列没上分布式文件系统甚至没有用Docker初期阶段。有人可能会质疑但我说实话对这样一套内部管理系统SaaS化、分布式改造什么时候需要什么时候再说。把Spring Boot单体应用做到极致在这个量级下一定比微服务更好用。开发简单排查问题方便部署也省事。一台服务器加一个MySQL稳定运行远比架构先进价值大。过度设计是很多技术人容易犯的毛病学会做减法我觉得是这次项目里一个重要的收获。9.3 稳定的技术栈就是最好的创新最后聊一点对技术选型的思考。现在前端框架更新换代很快后端生态也是天天有新东西但做企业级管理系统我觉得最重要的是整个技术栈都被广泛验证过。Spring Boot到目前为止已经是一个非常稳定的生态踩坑资料铺天盖地遇到问题几乎都能搜到现成的解决方案。Vue也是一样中文社区非常活跃。MyBatis虽然看起来不那么热门但在国内企业级项目里的使用率一直很高其简洁直接、便于控制SQL的特性在业务复杂的管理系统中优势明显。这套组合下新人上手速度快老手维护成本低客户律所也不需要为了跑系统去购买昂贵的大型商业软件许可。在技术选型上走一条成熟稳定的路线对这类成本敏感又要求安全可靠的内部系统来说就是最好的选择。最后再分享一个细节做这类业务系统的开发文档和注释一定要跟上不光是代码注释还要有部署文档、使用手册、数据字典。在这个项目上线三个月后新来的同事能通过一份完整的数据字典独立上手维护系统这比我写多少完美的代码都重要。系统的生命力在于持续使用和维护而维护的起点就是这些看似不起眼的文档。
网站建设高端定制企业官网