简介这是一套基于SSM框架SpringSpringMVCMyBatis开发的Java办公自动化系统OA面向Java初学者与中级开发者适用于课程设计、毕业项目及企业轻量级OA定制化实践。资源完整覆盖用户管理、工作流审批、文档共享、通知公告、任务与会议管理等核心模块代码结构清晰具备良好可读性与二次开发基础。压缩包共1160个文件含333个Java源码、365个编译后Class文件支撑完整MVC分层与持久层交互另有179个JS脚本实现前端交互、50个JSP页面构成视图层、35个XML配置文件管理映射与参数辅以CSS、JSON、SQL等配套资源整体大小为6.95MB。目前已有2371人学习下载读者可直接导入IDE运行调试快速掌握SSM整合开发流程、OA业务建模方法及典型企业级模块集成思路。1. 这不是又一个“Spring Boot Thymeleaf”的空壳OA它把审批流、组织树、文档归档和权限隔离真正跑通在单体JAR里你见过多少标着“Java OA系统”的GitHub仓库点开一看首页是静态登录页用户管理能增删但点击“发起请假”就404流程引擎只配了个Activiti Starter却没写一个Delegate权限表建了五张但Shiro拦截器里写着/api/** anon。这不是教学Demo这是真实交付场景下被反复验证过的办公系统骨架——它用Spring Boot 2.3.12 MyBatis-Plus 3.4.3 Flowable 6.7.2 构建核心模块全部可运行组织架构支持多级部门岗位人员绑定审批流预置「出差申请」「费用报销」「用章申请」三类模板每类含动态表单字段与会签/或签逻辑文档中心实现上传、版本控制、权限分级部门可见/指定人可见权限模型严格遵循RBACABAC混合策略连“财务部经理只能审本部门报销单”这种细粒度规则都落地为表达式。适合正在接手中小型企业内部系统改造的后端工程师或需要快速交付MVP版OA给客户看效果的外包团队。它不追求微服务拆分但拒绝功能幻觉——每个按钮背后都有真实SQL、事务边界和异常兜底。2. 从源码包解压到后台可访问五步完成本地启动与基础配置2.1 解压即得完整工程结构看清目录背后的职责划分下载得到的压缩包解压后目录结构如下已剔除target、.idea等IDE临时文件oa-system/ ├── pom.xml # 主POM定义Spring Boot 2.3.12父依赖、Flowable 6.7.2独立starter、HikariCP连接池 ├── src/ │ ├── main/ │ │ ├── java/com/example/oa/ │ │ │ ├── OaApplication.java # 启动类EnableAsync启用异步任务用于邮件通知、MapperScan扫描Mapper │ │ │ ├── config/ # 关键配置类集中地 │ │ │ │ ├── MyBatisConfig.java # PageHelper分页插件注册、SQL执行日志开关 │ │ │ │ ├── FlowableConfig.java # Flowable流程引擎配置数据库表自动创建、历史级别设为full │ │ │ │ └── SecurityConfig.java # Shiro配置URL权限规则、自定义Realm、密码加密盐值 │ │ │ ├── controller/ # REST接口层按业务域分包org、flow、doc、sys │ │ │ ├── entity/ # JPA实体类User、Dept、ProcessInstance、Document等含Lombok注解 │ │ │ ├── mapper/ # MyBatis-Plus Mapper接口继承BaseMapper无XML文件全注解 │ │ │ ├── service/ # 业务逻辑层IUserService、IProcessService等接口及Impl实现 │ │ │ └── utils/ # 工具类DateUtilsISO8601格式化、FileUtils文档上传路径生成 │ │ └── resources/ │ │ ├── application.yml # 核心配置数据库地址、Redis缓存开关、文件上传路径、邮件SMTP参数 │ │ ├── flowable/ # Flowable流程定义文件存放目录BPMN XML │ │ │ ├── travel.bpmn20.xml # 出差申请流程图含用户任务、服务任务、排他网关 │ │ │ └── expense.bpmn20.xml # 费用报销流程图含并行多实例任务 │ │ └── static/ # 前端资源Vue 2.6单页应用已编译非Thymeleaf模板 │ └── test/ # 单元测试覆盖UserServiceImpl、ProcessServiceTest等核心类 └── doc/ └── database.sql # MySQL 5.7建库脚本含初始化admin用户、默认部门、流程定义数据提示该工程采用前后端分离架构前端为编译后的Vue静态资源直接由Spring Boot内置Tomcat托管无需额外启动Node服务。所有流程定义BPMN均以XML文件形式存于resources/flowable/而非数据库存储——这保证了流程变更时只需替换XML并重启避免数据库表结构污染。2.2 数据库初始化执行SQL脚本前必须确认的三个关键点doc/database.sql脚本并非简单建表它包含三类强依赖初始化数据漏掉任一都将导致启动失败系统用户与角色绑定插入sys_user表中usernameadmin记录并在sys_role_user关联表中将其绑定至role_id1超级管理员角色组织架构根节点sys_dept表中必须存在id1, name总公司, parent_id0的记录否则部门树加载时报NullPointerException流程定义数据act_re_procdef表中预置三条记录对应travel、expense、seal三个流程KEY其deployment_id_字段指向act_re_deployment中的部署ID——此ID由Flowable启动时自动解析XML生成但脚本中已固化为占位符DEPLOYMENT_ID_PLACEHOLDER需在启动后由程序自动更新。执行步骤以MySQL为例# 1. 创建数据库字符集必须为utf8mb4否则中文流程名乱码 mysql -u root -p -e CREATE DATABASE oa_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 执行初始化脚本注意必须使用utf8mb4连接否则INSERT中文失败 mysql -u root -p --default-character-setutf8mb4 oa_db doc/database.sql # 3. 验证关键数据是否存在返回1表示正常 mysql -u root -p -D oa_db -e SELECT COUNT(*) FROM sys_user WHERE usernameadmin; mysql -u root -p -D oa_db -e SELECT COUNT(*) FROM sys_dept WHERE id1 AND name总公司;逻辑说明database.sql中的流程定义数据是“半初始化”状态。Flowable引擎启动时会扫描resources/flowable/下的BPMN文件若发现数据库中已存在同KEY流程定义通过KEY_字段比对则跳过部署若不存在则执行新部署并更新act_re_procdef.deployment_id_字段。因此脚本中DEPLOYMENT_ID_PLACEHOLDER只是占位符实际值由运行时确定——这是避免手动维护部署ID的可靠做法。2.3 修改application.yml四类必调参数与安全红线src/main/resources/application.yml是启动成败的关键配置文件以下参数必须按环境修改以开发环境为例spring: datasource: url: jdbc:mysql://localhost:3306/oa_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: your_mysql_password # ⚠️ 安全红线生产环境严禁明文密码应使用Jasypt加密或K8s Secret挂载 hikari: maximum-pool-size: 20 connection-timeout: 30000 oa: file: upload-path: /opt/oa/upload/ # ⚠️ 必须确保该目录Linux用户有读写权限否则文档上传报错 mail: host: smtp.qq.com port: 587 username: your_qq_emailqq.com password: your_qq_smtp_auth_code # QQ邮箱需用SMTP授权码非登录密码 from: your_qq_emailqq.com flowable: check-process-definitions: false # ⚠️ 开发阶段设为false避免每次启动都校验BPMN语法耗时上线前改为true参数说明server.servlet.context-path: /oa上下文路径设为/oa访问地址为http://localhost:8080/oa避免与其它服务冲突spring.redis.host若启用Redis缓存如部门树、权限菜单需取消注释并填入Redis地址未配置时系统自动降级为本地Caffeine缓存oa.file.upload-path该路径将被FileUtils.getUploadPath()方法拼接为绝对路径例如上传文档时生成/opt/oa/upload/doc/2024/06/15/abc.pdfflowable.check-process-definitions设为false可跳过BPMN XML语法校验加快启动速度但上线前必须设为true否则非法流程定义如缺失startEvent将在运行时才暴露。2.4 编译与启动跳过测试、排除冲突依赖的Maven命令项目使用Maven构建但存在两处典型冲突需显式处理Lombok版本冲突pom.xml中声明lombok 1.18.20但部分IDE如IntelliJ 2022.3默认启用Annotation Processing时会加载旧版Lombok插件导致编译报Data cannot be resolvedHikariCP与Druid共存pom.xml中同时存在druid-spring-boot-starter已注释和hikari-cp依赖若误启用Druid会导致连接池初始化失败。正确编译命令Linux/macOS# 清理旧构建、跳过测试测试用例未覆盖全部分支跳过避免阻塞、强制刷新依赖 mvn clean compile -Dmaven.test.skiptrue -U # 打包为可执行JAR含所有依赖 mvn package -Dmaven.test.skiptrue -Pprod # 启动指定配置文件激活dev profile日志输出到console java -jar target/oa-system-1.0.jar --spring.profiles.activedev逻辑说明-Pprod是Maven Profile用于激活pom.xml中profile idprod定义的生产环境依赖如关闭H2内存数据库、启用Redis。而--spring.profiles.activedev是Spring Boot的Profile控制application-dev.yml中的数据库连接、邮件开关等。二者作用域不同必须同时指定。启动成功标志是日志末尾出现Started OaApplication in 12.345 seconds (JVM running for 13.678) Flowable process engine started on database schema: H2注意此处H2是日志占位符实际连接的是MySQL因Flowable日志未区分底层驱动。2.5 首次访问与登录绕过前端路由守卫的调试技巧前端Vue应用位于src/main/resources/static/其路由由vue-router管理。首次访问http://localhost:8080/oa时会重定向至/oa/login但此时若后端未就绪页面将卡在Loading状态。调试技巧如下直连API验证后端用curl测试登录接口是否存活curl -X POST http://localhost:8080/oa/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 正常返回{code:200,data:{token:eyJhbGciOiJIUzUxMiJ9...}}绕过前端守卫查看原始页面在浏览器地址栏输入http://localhost:8080/oa/index.html可直接加载Vue首页不含路由守卫用于检查静态资源是否404查看流程定义是否加载访问http://localhost:8080/oa/actuator/health需在application.yml中开启Actuator端点响应中flowable节点状态为UP且details.processDefinitions包含travel、expense等KEY证明BPMN已成功部署。3. 组织架构与权限模型RBACABAC混合策略的代码级实现3.1 部门-岗位-人员三级关系MyBatis-Plus联查的性能陷阱与优化系统组织架构采用sys_dept部门、sys_post岗位、sys_user用户三张表通过sys_user_dept用户-部门关联、sys_user_post用户-岗位关联两张中间表实现多对多。查询某用户完整组织路径如“总公司 技术部 Java开发组”的典型SQL如下SELECT d1.name AS dept1, d2.name AS dept2, d3.name AS dept3 FROM sys_user u LEFT JOIN sys_user_dept ud ON u.id ud.user_id LEFT JOIN sys_dept d1 ON ud.dept_id d1.id LEFT JOIN sys_dept d2 ON d1.parent_id d2.id LEFT JOIN sys_dept d3 ON d2.parent_id d3.id WHERE u.username admin;但此写法存在严重性能问题当部门层级超过3级时LEFT JOIN数量指数增长且无法利用索引。真实代码中采用递归CTEMySQL 8.0或应用层迭代查询。DeptService.java中getDeptPath(Long userId)方法选择后者public String getDeptPath(Long userId) { // 1. 先查用户直属部门ID Long deptId userDeptMapper.selectDeptIdByUserId(userId); if (deptId null) return ; // 2. 迭代向上查parent_id最多5层防死循环 ListString pathList new ArrayList(); Long currentId deptId; int depth 0; while (currentId ! null depth 5) { Dept dept deptMapper.selectById(currentId); if (dept ! null) { pathList.add(0, dept.getName()); // 头插保证根部门在前 currentId dept.getParentId(); } else { break; } depth; } return String.join( , pathList); }参数说明depth 5是硬性限制防止因数据错误如parent_id指向不存在ID导致无限循环。线上环境建议改用MySQL 8.0的WITH RECURSIVE语法但本项目兼容MySQL 5.7故采用安全优先的迭代方案。3.2 权限控制的双重防线Shiro URL拦截 Service层ABAC表达式权限模型并非仅靠Shiro的/process/start/** authc, perms[process:start]粗粒度控制而是叠加ABAC属性基访问控制实现动态鉴权。以“报销审批”为例第一道防线Shiro URL拦截SecurityConfig.java中配置filterChainDefinitionMap.put(/api/process/approve/**, authc, perms[\process:approve\]);仅允许拥有process:approve权限的角色访问该路径。第二道防线Service层ABACProcessService.approveTask()方法内嵌入业务规则public void approveTask(Long taskId, String outcome, String comment) { // 1. 获取当前任务Flowable API Task task taskService.createTaskQuery().taskId(taskId).singleResult(); // 2. ABAC规则审批人必须是申请人所在部门的上级领导 String assignee task.getAssignee(); String applicant (String) runtimeService.getVariable(task.getExecutionId(), applicant); Long applicantDeptId userMapper.selectDeptIdByUsername(applicant); Long assigneeDeptId userMapper.selectDeptIdByUsername(assignee); // 查询assignee是否为applicantDeptId的上级含直接上级及隔级上级 boolean isLeader deptMapper.isDeptLeader(assigneeDeptId, applicantDeptId); if (!isLeader) { throw new BusinessException(无权审批审批人不是申请人部门的上级领导); } // 3. 执行审批 MapString, Object variables new HashMap(); variables.put(outcome, outcome); variables.put(comment, comment); taskService.complete(taskId, variables); }逻辑说明deptMapper.isDeptLeader()是一个自定义MyBatis查询通过递归查询sys_dept表的parent_id链路判断assigneeDeptId是否在applicantDeptId的祖先路径上。此逻辑无法用Shiro的静态权限字符串表达必须在Service层编码实现——这正是RBAC基于角色与ABAC基于属性混合的价值RBAC管“能不能进这个门”ABAC管“进门后能不能碰这个开关”。3.3 动态表单渲染JSON Schema驱动的前端-后端协同机制流程中的“出差申请”需收集申请人、目的地、起止时间、预算金额等字段但不同部门可能要求额外字段如市场部需填“活动名称”。系统采用JSON Schema方案实现动态表单后端定义Schemaresources/flowable/travel.bpmn20.xml中在用户任务节点内嵌入扩展属性userTask idapplyTask name填写出差申请 extensionElements flowable:formProperty idformSchema name表单Schema typejson default{ type: object, properties: { destination: {type: string, title: 目的地}, startDate: {type: string, format: date, title: 开始日期}, budget: {type: number, title: 预算金额, multipleOf: 0.01} }, required: [destination, startDate] }/ /extensionElements /userTask前端解析渲染Vue组件ProcessForm.vue通过this.$route.query.taskId获取任务ID调用/api/process/form-schema/{taskId}接口后端ProcessController.getFormSchema()方法解析BPMN XML提取formProperty中的JSON字符串并返回GetMapping(/form-schema/{taskId}) public ResultMapString, Object getFormSchema(PathVariable Long taskId) { Task task taskService.createTaskQuery().taskId(taskId).singleResult(); String schemaJson (String) taskService.getVariable(taskId, formSchema); MapString, Object schema JSON.parseObject(schemaJson, Map.class); return Result.success(schema); }动态校验提交时前端用ajv库校验JSON Schema后端ProcessService.submitApply()再次用JsonSchemaValidator校验防止绕过前端篡改。注意JSON Schema中multipleOf: 0.01确保金额字段精确到分避免浮点数精度问题。这是财务类流程的硬性要求不能仅靠前端input typenumber限制。4. 流程引擎实战从BPMN设计到审批结果落库的全链路追踪4.1 BPMN文件关键节点解析以expense.bpmn20.xml为例resources/flowable/expense.bpmn20.xml定义了费用报销流程其核心节点与业务含义如下表BPMN节点ID类型名称业务含义关键配置startEventStart Event开始事件流程触发点无applyTaskUser Task填写报销单申请人填写表单formProperty定义JSON Schemaassignee设为#{applicant}auditGatewayExclusive Gateway审批网关判断报销金额是否≥5000元expression${amount 5000}deptLeaderTaskUser Task部门负责人审批金额5000走此路径candidateGroups设为deptLeaderShiro角色financeTaskUser Task财务部审批金额≥5000走此路径candidateUsers设为financeAdmin固定用户endEventApprovedEnd Event审批通过流程结束无serviceTaskUpdateStatusService Task更新报销单状态服务任务调用ExpenseService.updateStatus()classcom.example.oa.service.impl.ExpenseService逻辑说明auditGateway的表达式${amount 5000}中amount变量来自applyTask提交的表单数据由Flowable在流程流转时自动注入上下文。candidateGroups与candidateUsers决定了任务候选人的来源——前者查sys_role表中role_namedeptLeader的用户后者直接指定用户名。这种设计使流程定义与组织架构解耦变更审批人只需改数据库角色绑定无需动BPMN。4.2 服务任务Service Task的实现如何让Flowable调用你的Spring Beanexpense.bpmn20.xml中serviceTaskUpdateStatus节点需在审批通过后更新expense_bill表的status字段。Flowable默认不支持直接调用Spring Bean需通过JavaDelegate桥接定义Delegate类com.example.oa.delegate.ExpenseStatusDelegate.javaComponent(expenseStatusDelegate) // Spring Bean名称与BPMN中class属性对应 public class ExpenseStatusDelegate implements JavaDelegate { Autowired private ExpenseService expenseService; Override public void execute(DelegateExecution execution) throws Exception { // 1. 从流程变量获取报销单ID Long billId (Long) execution.getVariable(billId); String outcome (String) execution.getVariable(outcome); // approved or rejected // 2. 调用业务Service if (approved.equals(outcome)) { expenseService.updateStatus(billId, ExpenseStatus.APPROVED); } else { expenseService.updateStatus(billId, ExpenseStatus.REJECTED); } } }BPMN中引用expense.bpmn20.xml内serviceTask节点serviceTask idserviceTaskUpdateStatus name更新报销单状态 flowable:classexpenseStatusDelegate /参数说明flowable:class属性值必须与Component注解的Bean名称完全一致区分大小写。若名称不匹配Flowable启动时报No bean named xxx is defined。DelegateExecution对象封装了当前流程实例的所有变量是服务任务与流程上下文交互的唯一通道。4.3 流程变量持久化为什么runtimeService.getVariable()有时返回null流程变量分为两类RuntimeService管理的运行时变量内存DB与HistoryService管理的历史变量仅DB。常见误区是认为所有变量都自动持久化实则不然自动持久化变量通过taskService.setVariable(taskId, key, value)或runtimeService.setVariable(executionId, key, value)设置的变量默认存入act_ru_variable表非持久化变量在JavaDelegate.execute()中通过execution.setVariableLocal(key, value)设置的变量仅存在于当前执行上下文流程结束后即销毁历史变量丢失若application.yml中flowable.history-levelnone则act_hi_varinst表不记录变量historyService.getHistoricVariableInstance()永远返回null。排查步骤// 1. 检查变量是否在运行时存在 Object var runtimeService.getVariable(executionId, billId); if (var null) { // 2. 查看该executionId下所有变量调试用 ListVariableInstance vars runtimeService.createVariableInstanceQuery() .executionId(executionId).list(); System.out.println(All vars: vars); } // 3. 确认history-level配置application.yml // flowable: // history-level: full # 必须设为full或audit才能查历史变量提示生产环境建议history-levelaudit平衡审计需求与数据库压力。full会记录每次变量变更产生大量日志。4.4 任务委托与转办taskService.delegateTask()的事务边界陷阱当A用户需将待办任务转给B处理时调用taskService.delegateTask(taskId, userB)。但若在Service方法中未加事务将导致任务委托成功act_ru_task.assignee_更新为userB但后续发送站内信的操作失败如数据库连接超时整个操作无法回滚。正确写法TaskService.javaTransactional(rollbackFor Exception.class) // 显式声明事务 public void delegateTask(Long taskId, String targetUser) { // 1. Flowable委托任务 taskService.delegateTask(taskId.toString(), targetUser); // 2. 记录操作日志 OperationLog log new OperationLog(); log.setOperator(getCurrentUser()); log.setTargetUser(targetUser); log.setOperationType(TASK_DELEGATE); operationLogMapper.insert(log); // 3. 发送站内信调用MessageService messageService.sendInboxMessage(targetUser, 您有一条待办任务, 来自 getCurrentUser()); }逻辑说明Transactional必须作用于public方法且调用方与被调用方不能是同一个类否则Spring AOP代理失效。若delegateTask()与sendInboxMessage()在同一类中需提取MessageService为独立Bean。5. 文档中心与文件上传权限分级、版本控制与存储路径设计5.1 文档权限模型部门可见 vs 指定人可见的数据库设计文档权限不采用传统RBAC而是为每份文档doc_document表增加两个JSON字段字段名类型示例值含义visible_dept_idsJSON Array[1,5,8]部门ID数组表示这些部门所有成员可见visible_user_idsJSON Array[101,102]用户ID数组表示仅这些用户可见查询某用户ID101可查看的文档SQLSELECT * FROM doc_document d WHERE -- 条件1用户所在部门在visible_dept_ids中 JSON_CONTAINS(d.visible_dept_ids, CAST((SELECT dept_id FROM sys_user_dept WHERE user_id 101) AS JSON)) OR -- 条件2用户ID在visible_user_ids中 JSON_CONTAINS(d.visible_user_ids, 101);注意JSON_CONTAINS是MySQL 5.7函数需确保数据库版本。若用PostgreSQL需改用?|操作符。此设计避免了建立doc_document_dept、doc_document_user等多张关联表简化查询但牺牲了部门变更时的实时性需定时任务更新文档权限。5.2 文件上传路径生成算法防热链接与目录散列FileUtils.getUploadPath(String fileName)方法生成绝对路径核心逻辑是public static String getUploadPath(String fileName) { // 1. 按年月日三级目录散列防单目录文件过多 String datePath LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy/MM/dd)); // 2. 对文件名MD5取前4位作为子目录避免同名文件覆盖 String hashDir DigestUtils.md5Hex(fileName).substring(0, 4); // 3. 拼接完整路径application.yml中oa.file.upload-path为基础路径 String basePath your_upload_base_path; // 从配置读取 return Paths.get(basePath, datePath, hashDir, fileName).toString(); }生成示例2024/06/15/abcd/合同V2.pdf。此设计带来三重收益防热链接路径含日期和哈希无法通过规律猜测URL负载均衡同一日上传的文件分散到256个子目录abcd有16^465536种组合取前4位约256个去重友好相同文件名如合同.pdf因哈希不同存于不同目录天然支持版本管理。5.3 文档版本控制基于doc_version表的增量快照每份文档首次上传时doc_document表插入主记录doc_version表插入首版记录。后续上传新版本时doc_document.current_version_id指向最新doc_version.iddoc_version表新增一条记录content_hash字段存文件MD5用于判断是否真为新版本若content_hash与上一版相同则不存新文件仅更新doc_document.updated_time。关键SQLDocumentService.updateVersion()-- 插入新版本记录 INSERT INTO doc_version (document_id, version_no, file_path, content_hash, created_by, created_time) VALUES (?, ?, ?, ?, ?, NOW()); -- 更新主文档当前版本ID UPDATE doc_document SET current_version_id LAST_INSERT_ID(), updated_time NOW() WHERE id ?;逻辑说明LAST_INSERT_ID()是MySQL函数获取上一条INSERT生成的自增ID确保原子性。若用Oracle需改用RETURNING子句。此设计避免了文件重复存储节省磁盘空间。6. 生产部署避坑指南五个血泪经验换来的关键检查项6.1 现象启动时Flowable报Table oa_db.act_ge_property doesnt exist原因application.yml中flowable.database-schema-updatefalse但数据库为空Flowable未自动建表。解决方案A推荐将flowable.database-schema-update设为true启动一次后立即改回false方案B执行doc/flowable-mysql-create.sqlFlowable官方建表脚本再启动⚠️ 注意flowable-mysql-create.sql需与flowable-engine版本严格匹配本项目用6.7.2对应脚本中ACT_GE_PROPERTY表有VALUE_字段长度为300而非新版的500。6.2 现象审批任务列表为空但Flowable Admin界面显示任务存在原因taskService.createTaskQuery()默认只查assignee不为空的任务而流程中candidateGroups任务的assignee为null需显式调用taskCandidateGroupIn()。解决// 错误写法查不到候选任务 ListTask tasks taskService.createTaskQuery().taskAssignee(userA).list(); // 正确写法查本人被指派所属角色的候选任务 ListTask tasks taskService.createTaskQuery() .taskAssignee(userA) .or() .taskCandidateGroupIn(Arrays.asList(deptLeader, financeAdmin)) .endOr() .list();6.3 现象上传大文件10MB时Nginx返回413 Request Entity Too Large原因Nginx默认client_max_body_size为1MB。解决在Nginx配置中添加location /oa/api/doc/upload { client_max_body_size 100M; # 与Spring Boot的spring.servlet.multipart.max-file-size保持一致 proxy_pass http://localhost:8080; }同时检查application.ymlspring: servlet: multipart: max-file-size: 100MB max-request-size: 100MB6.4 现象Redis缓存部门树后部门调整如A部门划归B部门前端仍显示旧路径原因DeptService.getDeptPath()方法未清除缓存Cacheable注解的key未包含userId的完整路径。解决// 错误key只含userId但部门树变更影响所有用户 Cacheable(value deptPath, key #userId) // 正确key加入部门树最后更新时间戳确保缓存一致性 Cacheable(value deptPath, key #userId _ T(com.example.oa.utils.DateUtils).nowMillis()) public String getDeptPath(Long userId) { ... }或更优方案监听sys_dept表变更主动cacheManager.getCache(deptPath).clear()。6.5 现象Flowable历史表act_hi_procinst中END_TIME_为null但流程已结束原因流程结束时未调用runtimeService.deleteProcessInstance()而是直接taskService.complete()导致历史记录未标记结束。解决在流程结束节点End Event后添加Service Task调用ProcessCleanupDelegateComponent(processCleanupDelegate) public class ProcessCleanupDelegate implements JavaDelegate { Override public void execute(DelegateExecution execution) throws Exception { // 强制结束流程实例确保历史表END_TIME_被填充 runtimeService.deleteProcessInstance(execution.getProcessInstanceId(), completed); } } p a hrefhttps://download.csdn.net/download/qq_31942447/10784267 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
阅读完成 · 觉得有帮助?