ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+SSM+Django网络办公系统实战:权限设计与审批流实现

Java+SSM+Django网络办公系统实战:权限设计与审批流实现 基于JavaSSMDjango的网络办公系统是我这段时间梳理过最典型的综合性项目之一。它表面上没有复杂的算法也没有酷炫的前端交互但从需求拆解、权限设计、审批流转、双技术栈联调到最后的打包部署每一个环节都有能让新手卡上半天的暗坑。这篇内容不打算罗列功能截图而是专注拆解这套OA系统从需求到实现的关键链路SSM主业务和Django辅助模块是怎么分工的、核心表如何设计、审批状态机怎么落地、联调部署时最容易踩哪些雷。如果你想做在线办公系统或者在准备毕业设计答辩、系统学习Java后端开发这篇文章应该能给你一份有效的参考。很多同学看到“JavaSSMDjango”这个组合会觉得奇怪甚至怀疑是不是硬凑技术栈。说实话这种判断一半对一半错。它确实把Java和Python两套体系拉进了同一个系统但如果把它们理解成两个独立服务、通过接口协作这反而是企业中很常见的一种架构形态核心业务用Java保证稳定辅助业务用Python快速开发和交付。下面我按一个完整项目的推进顺序把每一步的设计逻辑和实操细节都摊开讲。1. 先搞清楚一套网络办公系统到底要解决什么问题1.1 办公场景里那些让人崩溃的低效环节做OA之前首先得明白它要治什么病。很多需求文档喜欢把办公系统写成“员工管理、通知公告、请假审批、会议室预定、文档管理”列一堆功能模块看起来什么都覆盖了但恰恰漏掉了核心问题。我跑了几个中小型公司的场景之后感触很深。没有线上办公系统时一个员工请假可能要走这样的流程先找直属领导当面说再拿纸质请假单找人事人事再去找部门负责人签字如果领导出差这张单子就这么压着。报销也一样发票贴好送到财务财务对账的时候才发现缺少审批依据又退回去重走一遍。会议通知靠微信群发发出去之后谁看了、谁没看根本不知道到了时间会议室还被其他部门占了。文档管理就更乱了合同、方案、制度散落在各台电脑和聊天记录里找一份半年前的报价单可能要翻一整天的聊天记录。这些场景汇总起来就是三个核心痛点流程没有线上化、信息没有统一入口、权责没有留痕手段。远程办公和混合办公逐渐普及之后这些问题会被进一步放大——人和人不在同一个物理空间单纯靠线下沟通已经无法组织起高效的协作必须有一个统一的“网上办公室”。1.2 网络办公系统的核心目标流程在线、信息不落地、权责可追溯所以我对这套网络办公系统的定位从来不是“把纸质表单搬到网页上”而是把业务流程本身数字化。流程一旦在线审批节点、处理时间、处理人都会被记录下来信息一旦集中公告、文档、日程都从同一个平台获取就不再会有“这件事我不知道”的扯皮权责一旦可追溯每一个操作都有操作日志出了问题时可以查到是谁在什么时间做了什么操作。这里特别提醒一点办公系统的“审计能力”非常重要。无论是企业内部的合规要求还是政务类系统的电子政务场景流程留痕和操作日志都是底线要求。所以在我设计的系统里除了业务表本身还专门设计了操作日志表和审批记录表任何状态的变更都要追加一条记录。很多人做OA只看重“能不能审批”忽略了“审完有没有痕迹”这是设计上的大忌。1.3 系统面向的用户角色与权限边界办公系统里至少有三种角色要处理清楚普通员工提交请假、报销、用章申请查看公告和自己的日程参与会议维护个人信息。部门主管/审批人处理下属提交的申请可以同意或者驳回查看本部门的工作动态。系统管理员维护用户、角色、菜单、部门结构分配权限查看系统运行日志和数据统计。这个划分本身不复杂但紧接着就引出一个老生常谈又容易翻车的问题数据权限。角色权限通常指的是“能不能点某个菜单、能不能做某个操作”但数据权限是“能看哪些数据”。比如财务主管可以查看全公司工资数据部门主管只能看本部门人员普通员工只能看自己的申请记录。在同一张表、同一个接口的情况下只判断“是否登录、是否管理员”远远不够必须把数据范围计算进去。这部分我后面会在表结构和拦截器设计里详细展开。2. 技术架构拆解SSM和Django为什么能共存于同一个项目2.1 SSM三件套Spring、SpringMVC、MyBatis的分工既然标题里写着JavaSSM那就先把SSM的骨架说清楚。SSM是Spring、SpringMVC、MyBatis三件套的简称在很多Java后端项目里仍然是经典组合。Spring负责IoC容器和AOP。所有业务对象通过依赖注入管理事务、日志、权限校验这些横切逻辑通过AOP统一处理。没有Spring服务层会变成一堆互相new对象的散装代码。SpringMVC负责Web层。它处理前端发过来的HTTP请求完成参数绑定、校验、路由分发控制层里一个RequestMapping就能把URL映射到具体方法。MyBatis负责数据持久化。它把SQL写在Mapper接口或XML里灵活控制SQL的拼接和结果映射。相比JPA的“全自动”MyBatis更接近SQL本身适合复杂查询和报表统计。一句话总结Spring管对象的组装和事务SpringMVC管请求的分发MyBatis管数据库的访问。三者各司其职这也是SSM面试里最基础、最常被追问的一组概念。2.2 Django在这个系统里的定位辅助业务与数据服务那Django又是干嘛的我这里采用的方案是SSM作为系统的核心负责人事、权限、审批、公告等主业务Django作为辅助服务负责几个典型场景数据看板和统计报表基于业务数据做汇总分析比如出勤率、审批时效、部门工作量。定时数据同步任务从外部数据源抓取信息或者定时生成报表并推送通知Django的Celery和ORM配合起来非常快。内部管理后台Django自带Admin改造之后可以快速实现一些低频率的后台配置和数据维护功能。为什么把辅助模块交给Django而不是全部写在SSM里核心原因是开发效率。Java要做一套统计报表Controller、Service、Mapper、XML、前端页面全部要写而Django基于ORM和模板默认的Admin后台能省掉一大片重复劳动。把辅助业务拆出去SSM主工程可以保持相对精简边界也更清晰。2.3 异构技术栈之间的通信方案Java和Python之间怎么通信这里不引入复杂的服务框架最稳妥也最容易调试的方案是REST API。SSM作为服务提供方把用户、审批、部门等核心数据以JSON接口暴露出去Django作为服务调用方通过requests或httpx请求这些接口获取数据再加工成报表。当然也可以选择共享同一个MySQL数据库SSM写业务表Django只读或者只操作自己的辅助表。但我个人不推荐共享库因为一旦两个技术栈对同一张表做写操作后期表结构变更、字段类型兼容都会变成灾难来源。我自己采用的是各自维护独立数据库业务数据通过SSM提供的接口同步到Django的分析库辅助模块产出结果再通过接口回写或由SSM去拉取。这样两个服务即便日后要拆分、替换也不会互相拖累。2.4 开发环境与依赖版本清单这是一套可复现的版本组合我在实际搭建时踩过版本不兼容的坑所以直接给一份确定可用的清单组件版本/说明JDK1.8或11推荐1.8稳定且兼容旧项目Maven3.6以上Spring5.3.xSpringMVC5.3.xMyBatis3.5.xMySQL5.7或8.0字符集使用utf8mb4Tomcat9.0Python3.9或3.10Django3.2 LTS或4.2 LTSRedis可选做会话缓存和接口限流定时任务在Django端建议用Celery 5.x不过如果只是简单报表直接写Django自带的cron script配合系统定时任务也行能省掉一堆基础设施。3. 核心功能与数据模型设计3.1 功能模块全景把常用办公系统拆分出来需要有如下这些模块组织架构与用户管理、角色与权限管理、审批工作流请假、报销、用章、出差、日程与会议管理、公告通知、文档资料库、考勤统计、操作日志与系统监控。下面用一张表说清楚每个模块的输入输出和关键点功能模块主要输入关键输出设计注意点用户与部门管理部门信息、员工信息部门树、员工账号部门支持树形层级员工要有启用/停用状态角色与权限管理角色定义、菜单权限角色-菜单映射动态菜单用户登录后生成可访问菜单树审批工作流申请单、审批意见已办任务、待办任务状态机管理支持同意、驳回、撤销会议与日程会议主题、时间、参与人日历视图、会议提醒会议室与时间段冲突检测公告通知公告正文、发布范围已读/未读统计记录每个用户的阅读状态避免“不知道”的扯皮文档资料库文件分类、上传附件在线预览、下载大文件上传限制、文件存储路径统一考勤统计打卡记录、外勤记录出勤表、异常统计与请假、出差数据打通避免重复扣减操作日志用户操作行为日志列表、审计报表拦截器或AOP统一记录不能依赖业务代码自觉3.2 RBAC权限模型怎么落地权限设计我选用了经典RBAC模型User、Role、Menu三个核心实体外加UserRole和RoleMenu两张关联表。普通场景足够而且答辩时也很好讲。五张核心表的关系是用户属于角色角色绑定菜单菜单里的按钮对应操作权限。菜单权限只是第一层第二层是操作权限。比如“用户管理”这个菜单下面管理员能增删改查普通人力专员可能只能看列表导出Excel。我的做法是在菜单表里增加权限标识字段比如perm_code类似system:user:add然后通过一个自定义RequiresPermission注解或拦截器统一校验。真正麻烦的是第三层数据权限。同一个“查看员工”接口管理层要看全员部门主管只能看本部门。我的方案是在查询SQL里根据当前用户动态拼接数据范围条件管理员不加过滤条件主管级别加where department_id 当前部门普通员工再细化为where user_id 当前用户。这种设计比每次在Service层硬编码条件要规范得多。3.3 审批流状态机从待审批到归档审批模块是整个系统的核心。我以请假单为例一张单子从提交到结束至少要经历“待审批 - 同意/驳回”两个大状态再算上“撤销”和“已归档”。更完整的版本还会区分一级审批和二级审批状态机就要扩展为待一级审批、一级通过待二级审批、二级通过、已驳回、已撤销、已归档。我在代码里用int类型作为状态字段同时配一个常量类来定义可读的状态名称。每次状态变更都往审批记录表写一条记录包含审批人、审批动作、审批意见和时间。这里有一个非常关键的细节所有状态变更必须加事务控制否则一旦审批记录写完而主单状态没更新成功就会出现“记录说同意了单子还停在待审批”的脏数据。3.4 核心表结构设计示例我自己建表时会先画一个最简版以用户表、请假单、审批记录为例SQL大概长这样CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT SHA256加密密码, real_name VARCHAR(50) NOT NULL, department_id BIGINT COMMENT 所属部门, enabled TINYINT DEFAULT 1 COMMENT 是否启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE t_leave ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL COMMENT 申请人, leave_type TINYINT NOT NULL COMMENT 1事假2病假3年假, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, reason VARCHAR(500), status TINYINT DEFAULT 0 COMMENT 0待一级审批1待二级审批2已同意3已驳回4已撤销5已归档, version INT DEFAULT 0 COMMENT 乐观锁版本号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_id(user_id), KEY idx_status(status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT请假申请表; CREATE TABLE t_approval_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, biz_type TINYINT NOT NULL COMMENT 业务类型:1请假, biz_id BIGINT NOT NULL COMMENT 业务单ID, approver_id BIGINT NOT NULL COMMENT 审批人, action TINYINT NOT NULL COMMENT 1同意2驳回3转交4撤销, comment VARCHAR(500) COMMENT 审批意见, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_biz(biz_type, biz_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT审批记录表;设计上的几个通用原则我想强调一下逻辑删除字段is_deleted要有但核心流程表里我保留物理删除原因是审批单属于审计数据不应该允许用户物理删除每个表都保留create_time和update_time排查问题和做统计报表时没有这两个字段会非常痛苦deleted之外凡是经常用来过滤的字段都建索引比如状态字段和用户ID字段。4. 从零到跑通核心环节的实操实现4.1 工程结构与初始化步骤工程结构上我建议拆成两个独立项目不要硬塞在一个Maven工程里。SSM主项目是标准的Maven Web项目Django辅助服务是独立的Python项目目录长这样oa-system/ ├── oa-backend/ # SSM主工程 │ ├── pom.xml │ └── src/main/ │ ├── java/com/oa/ │ │ ├── controller/ │ │ ├── service/ │ │ ├── mapper/ │ │ ├── entity/ │ │ ├── common/ │ │ └── config/ │ └── resources/ │ ├── mappers/ │ ├── spring/ │ └── jdbc.properties ├── oa-report/ # Django辅助服务 │ ├── manage.py │ ├── requirements.txt │ └── reportapp/ │ ├── models.py │ ├── views.py │ └── urls.py └── sql/ └── init.sql初始化时先把sql脚本执行到MySQL再启动SSM主工程最后启动Django服务。顺序不能反Django如果需要调用SSM接口来初始化基础数据而SSM服务没起来就会直接超时报错。4.2 SSM端关键代码统一返回结果与控制层前后端交互统一用一个ResultT包装类这样接口返回结构稳定前端解析起来也省心public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok() { ... } public static T ResultT error(String message) { ... } }Controller层不要写业务逻辑只做参数接收、参数校验和结果转换。真正复杂的逻辑放到Service层。下面是一个简单的请假单提交接口RestController RequestMapping(/api/leave) public class LeaveController { private final LeaveService leaveService; PostMapping(/submit) public ResultLong submit(RequestBody LeaveForm form) { // 参数校验按业务规则处理比如结束时间必须晚于开始时间 Long leaveId leaveService.submit(form); return Result.ok(leaveId); } PostMapping(/approve) public ResultVoid approve(RequestBody ApproveForm form) { leaveService.approve(form.getLeaveId(), form.getAction(), form.getComment()); return Result.ok(); } }4.3 审批状态流转的实现细节审批状态流转是必须放在Service层并加事务的。状态编码我建议定义成常量或枚举不要到处写魔法数public class LeaveStatus { public static final int PENDING_FIRST 0; public static final int PENDING_SECOND 1; public static final int APPROVED 2; public static final int REJECTED 3; public static final int CANCELED 4; public static final int ARCHIVED 5; }审批方法的伪代码逻辑Transactional(rollbackFor Exception.class) public void approve(Long leaveId, Long approverId, int action, String comment) { Leave leave leaveMapper.selectById(leaveId); if (leave null) { throw new BusinessException(申请单不存在); } // 核心校验当前状态必须是待审批防止重复审批 if (leave.getStatus() ! LeaveStatus.PENDING_FIRST leave.getStatus() ! LeaveStatus.PENDING_SECOND) { throw new BusinessException(当前状态不可审批); } // 乐观锁更新避免并发审批同时通过 int rows leaveMapper.compareAndSetStatus( leaveId, leave.getStatus(), action 1 ? nextStatus(leave.getStatus()) : LeaveStatus.REJECTED, leave.getVersion()); if (rows 0) { throw new BusinessException(单据已被其他审批人处理请刷新后再试); } // 插入审批记录 ApprovalRecord record new ApprovalRecord(...); approvalRecordMapper.insert(record); }这里比较关键的是乐观锁写法。有两个审批人同时打开同一张单子A点同意B也点同意如果只简单update status 2 where id ...两条同时执行也都能成功但业务上就错了。所以更新条件必须带上当前状态和版本号用compareAndSetStatus这样的原子更新来保证只有一个请求能真正修改成功。MyBatis的XML里对应的更新语句长这样update idcompareAndSetStatus UPDATE t_leave SET status #{newStatus}, version version 1 WHERE id #{id} AND status #{oldStatus} AND version #{version} /update4.4 Django端模型、查询与删除操作的注意点Django辅助模块里我主要做统计和报表所以模型相对简单。比如要做一个考勤统计结果表from django.db import models class AttendanceReport(models.Model): dept_id models.BigIntegerField() work_date models.DateField() total_count models.IntegerField(default0) leave_count models.IntegerField(default0) late_count models.IntegerField(default0) status models.CharField(max_length16, defaultpending) class Meta: db_table t_attendance_reportDjango的查询一直在被题目中的“django执行查询-删除对象”热点多次提到这里重点讲两个容易踩的细节。第一个是查询集取数据时REST接口返回给Java端的一定要做序列化处理。不要直接返回QuerySet对象必须先转成列表或字典。比如from django.http import JsonResponse from django.db.models import Count def dept_summary(request): dept_id request.GET.get(dept_id) rows ( AttendanceReport.objects .filter(dept_iddept_id, statusdone) .values(work_date) .annotate(totalCount(id)) .order_by(work_date) ) data list(rows) return JsonResponse({code: 0, data: data})第二个是删除对象时的级联问题。Django的ORM里如果外键设置了on_deletemodels.CASCADE删除主表对象时会自动把关联子表记录也删掉。这看起来方便但办公系统里很多核心数据不能这么硬删否则审批记录、日志全没了。所以我建议临时表可以用delete()核心业务表一般做软删除在模型里加一个is_deleted字段默认查询时用管理器统一过滤掉已删除记录。批量删除还有一个坑点QuerySet.delete()不会触发模型自带的delete()方法和信号如果依赖信号做日志或通知就会发现数据没了但日志没写。这一点在面试中也经常被问到。4.5 打包部署与联调步骤SSM端打包成WAR包丢到Tomcat的webapps目录Django端用python manage.py runserver做测试生产环境则用Gunicorn或uWSGI。整体用Nginx做反向代理把不同路径转发到不同服务server { listen 80; server_name oa.example.com; # SSM主应用 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # Django统计和报表 location /report/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }联调时最容易出问题的是网络超时。SSM接口如果处理慢Django默认请求超时时间又很短会出现两者各自运行正常一调用就报错的诡异问题。解决方案是给Django端的HTTP请求设置明确超时长比如连接时间3秒、读取时间10秒并且加对应的错误重试逻辑。5. 真实开发中踩过的坑和排查方法5.1 SSM高频运行问题速查表现象常见原因处理建议启动报“Bean named xxx is currently in creation”循环依赖在Setter上添加Lazy注解或重构Service依赖方向查询实体属性全是null数据库字段下划线和Java驼峰没映射开启mybatis.configuration.map-underscore-to-camel-casetrue中文乱码请求和响应编码不一致在web.xml里配置CharacterEncodingFilter强制UTF-8#{}查不到数据误用了${}拼接导致SQL错误默认用#{}预编译${}仅用于固定动态表名或排序字段接口报404Controller路径写错或没加RequestMapping检查HandlerMapping日志确认请求地址与Controller路径完全一致事务失效同一个类内部方法互调且没有走代理拆分到不同Service或通过AopContext.currentProxy()调用这里我把“SSM常用注解”这个问题一并说透。控制层常用Controller、RestController、RequestMapping、ResponseBody、RequestBodyService层常用Service和Transactional依赖注入最常用Autowired或ResourceMyBatis层面有Mapper、Select、Param。如果你在项目里同时使用注解和XML记住一个原则简单SQL用注解复杂动态SQL用XML这样程序可读性和可维护性最好。5.2 Django查询与删除相关的细节问题Django的删除操作不同写法效果差异很大。删除单个对象obj.delete()会触发级联和信号删除QuerySetfilter().delete()是批量删除不走模型的delete()方法也不触发每个对象的信号。在办公系统里做清理任务时我建议分两步走先把要删的ID查询出来判断是否有需要归档的关联数据然后再执行批量删除并在日志里记录删除数量和类型。下面是一个安全删除示例to_delete TempFile.objects.filter(create_time__lt2025-01-01, is_archivedTrue) count_info to_delete.delete() logger.info(f清理过期临时文件{count_info})另外一个高频坑是Django的时区问题。如果开启了USE_TZTrue写入数据库的时间是UTC时间而SSM端存的是本地时间两边一对比数据会差8小时。处理办法是统一约定所有接口传输的时间都用字符串并使用ISO格式数据库存储用北京时间关闭USE_TZ或配置TIME_ZONEAsia/Shanghai并明确一个主时区。5.3 跨技术栈联调的隐患Java和Django之间传参最大的坑是时间格式和数值类型。Java的LocalDateTime默认序列化格式是“2025-01-01T10:00:00”Django的JSON解析器和前端通常更习惯“2025-01-01 10:00:00”所以要么在Java端配置Jackson格式化统一为yyyy-MM-dd HH:mm:ss要么在Django端做字符串兼容转换最好是在联调前就约定好格式。然后是跨域问题。如果SSM主应用和Django辅助服务分属不同域名或端口浏览器直接请求Django接口会触发跨域拦截。Django端需要安装django-cors-headers并配置允许的域名。但如果是后端服务之间互相调用跨域问题不存在要关注的反而是防火墙、端口和代理转发是否正确。5.4 业务逻辑上的并发与数据一致性办公系统业务层面必须面对并发。请假、报销单据并发审批只是问题之一会议室预定也会出现两个部门同时订到同一间会议室的情况。处理原则建议统一采用乐观锁或唯一索引来控制。会议室预定我在表设计时给会议时间加了一个唯一约束具体做法是会议室ID和会议开始时间组合成唯一索引插入新会议时如果时间重叠就直接报冲突这样比查完再插更可靠。审批流并发则用版本号乐观锁前面审批代码里的version字段就是干这个的。还有一类问题是数据一致性。Django从SSM拉取数据生成报表时如果SSM端数据正在修改拉下来的报表可能是不一致的快照。这种情况不一定要上分布式事务简单点可以让Django在拉取时对SSM接口传版本号或时间段参数只统计已结束的业务单减少中间态数据对报表的影响。6. 文档编写、演示讲解与答辩准备6.1 源码文档的项目该怎么整理标题里的“源码LW调试文档讲解等”是这类项目最常见的交付形态其中LW通常指配套论文或设计文档。很多同学源码跑通了论文和文档却不知道怎么下笔这里提供一个稳妥的大纲需求分析、系统总体设计架构图功能模块图、数据库设计ER图表结构说明、模块详细设计核心流程描述主要代码片段说明、系统测试功能测试用例结果分析、部署说明。调试文档我习惯按环境搭建、启动步骤、常见异常三部分来写。环境搭建要写清楚JDK、MySQL、Tomcat、Python版本避免读者随便装一个新版本就跑不起来。启动步骤要从导入SQL开始一直到浏览器访问地址每步都给出预期结果。常见异常则把上面提到的坑整理进去读者照着排查就能快速解决。6.2 演示讲解的时间线设计演示视频或现场答辩时最容易犯的错误是把所有功能都点一遍听起来像流水账。我的建议是设计一条有剧情的主线管理员登录创建部门和三个员工账号 配置角色权限 员工提交请假审批 部门主管审批通过 普通用户收到审批结果 Django报表页面展示考勤统计。这样一套流程走下来既展示了核心业务闭环又体现出权限模型和双技术栈协作全场不会乱。演示前准备几条典型数据非常关键。不要现场才新增账号提前把用户、部门、历史审批数据填充好演示的时候才能从容展示列表分页、状态筛选、统计图表这些亮点。6.3 面试和答辩阶段的高频问题结合java面试题的热点方向我整理了这几个问题建议大家提前准备SSM中三者的作用分别是什么请结合项目中的具体业务说明。这个问题要落到实际不要背定义。权限模型怎么设计的RBAC之外你如何做数据权限控制重点讲动态SQL拼接数据范围。为什么用Django它和SSM之间如何通信答案围绕服务拆分和API协作展开。审批并发如何保证数据一致性讲到乐观锁版本控制就足够再补充一个悲观锁场景对比。MyBatis中#{}和${}的区别这个问题几乎是SSM岗位必问务必说清楚预编译和SQL拼接的根本区别。6.4 从毕设/演示系统走向生产环境的差距演示系统能跑到生产环境中间还有一段距离。如果要继续把这套系统往真实项目推我建议从这几个方向改造。首先引入Spring Boot而不是手动配置SSM的XMLSpring Boot内置Tomcat、自动配置依赖开发效率会明显提升。其次把Django和SSM的认证打通用统一令牌解决跨服务的登录态问题而不是在Django端另搞一套用户名密码。然后是部署方式迁移到Docker ComposeMySQL、Redis、Tomcat、Django各一个容器补丁发布和机器迁移都更方便。最后是补齐监控和日志采集至少做到错误日志集中存储和关键业务指标的监控报警不然上线之后出了问题只能靠用户跟你说。这里我可以多聊一句心得办公系统表面看是“增删改查”但真正有价值的部分永远在业务抽象和架构边界上。审批状态机、RBAC数据权限、跨服务接口约定这些才是项目里拿得出手的亮点。这套项目做下来的整体感受我真正花时间的部分其实不是SSM的CRUD也不是Django的报表接口而是想清楚两个技术栈之间的边界在哪里。同一套系统里同时出现Java和Python很容易把工程写成一团乱麻但只要你时刻记得“核心服务负责业务辅助服务负责效率”所有决策都会变得清晰。对正在做OA、电子政务、或者任何协同办公类项目的朋友我再多一句实在的建议开工之前先画两张图一张是“角色-数据矩阵”一张是“审批状态流转图”。这两张图想清楚了项目的基本盘就已经稳了一半剩下的实现不过是把这些设计翻译成代码而已。
返回列表