1. 项目概述:SpringBoot办公发文管理系统
这个基于SpringBoot的办公发文管理系统,是我在去年为某中型企业实施的一个实际项目。当时客户急需一套能替代纸质审批流程的电子化解决方案,要求系统必须支持发文起草、审批流转、归档查询等核心功能,同时要兼顾易用性和安全性。
系统采用Java 17作为基础环境,SpringBoot 2.7作为主框架,配合MyBatis-Plus实现数据持久化。前端选用Thymeleaf模板引擎搭配LayUI组件库,这种组合既保证了开发效率,又能满足企业级应用对界面规范性的要求。数据库选用MySQL 8.0,考虑到发文系统对事务一致性的高要求,我们特别优化了InnoDB引擎的配置参数。
提示:选择SpringBoot框架时,建议使用2.7.x这个长期支持版本(LTS),避免使用3.x系列可能存在的兼容性问题,特别是企业环境中常需要集成的老系统。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能模块设计
2.1 发文起草模块
起草模块采用富文本编辑器(WangEditor)实现内容编辑,通过自定义XSS过滤规则防御注入攻击。核心实体类设计如下:
java复制public class Document {
private Long id;
private String docNumber; // 发文编号
private String title; // 文件标题
private String content; // 正文内容
private Integer status; // 状态(0:草稿 1:审批中 2:已发布)
private Long creatorId; // 创建人
private LocalDateTime createTime;
// 省略getter/setter
}
文件编号生成采用"部门代码+年份+序号"的规则,通过Redis原子计数器保证唯一性:
java复制public String generateDocNumber(String deptCode) {
String year = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy"));
String key = "doc:serial:" + deptCode + ":" + year;
Long serial = redisTemplate.opsForValue().increment(key);
return String.format("%s-%s-%05d", deptCode, year, serial);
}
2.2 审批流程引擎
采用Activiti 7集成实现工作流,但针对办公场景做了以下优化:
- 简化流程定义:预设"部门负责人→分管领导→办公室"三级审批模板
- 增加加急通道:对特急文件可跳过中间环节
- 审批超时自动转交:设置48小时超时规则
流程核心配置示例:
xml复制<process id="docApproval" name="发文审批流程">
<startEvent id="start"/>
<userTask id="deptApprove" name="部门审批"
activiti:candidateGroups="dept_leader"/>
<userTask id="divisionApprove" name="分管领导审批"
activiti:candidateGroups="division_leader"/>
<userTask id="officeApprove" name="办公室审核"
activiti:candidateGroups="office"/>
<endEvent id="end"/>
<!-- 省略连线定义 -->
</process>
2.3 文件归档与检索
采用Elasticsearch实现全文检索,关键配置包括:
- 自定义分词器:组合IK分词与拼音插件
- 权重设置:标题(title^3) > 正文(content) > 文号(docNumber^2)
- 权限过滤:通过Spring Security ACL控制可见范围
3. 关键技术实现细节
3.1 防止PDF/XSS攻击的方案
针对发文系统常见的文件安全风险,我们实现了多层防护:
- PDF渲染防护:
java复制@Bean
public PdfRenderer pdfRenderer() {
// 启用沙箱模式
System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
return new PdfRenderer(new PDFRendererConfig()
.setMaxFileSize(10_000_000) // 限制10MB
.setAntiAliasing(true));
}
- XSS过滤:
java复制public String xssFilter(String html) {
if (StringUtils.isEmpty(html)) return html;
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("p", "br", "div", "span")
.allowAttributes("class").onElements("div", "span")
.toFactory();
return policy.sanitize(html);
}
3.2 高并发发文编号生成
采用Redis+Lua脚本保证分布式环境下的编号唯一性:
lua复制-- KEYS[1]: 计数器key
-- ARGV[1]: 部门代码
-- ARGV[2]: 年份
local current = redis.call('incr', KEYS[1])
if current == 1 then
redis.call('expire', KEYS[1], 31536000) -- 设置1年过期
end
return ARGV[1] .. "-" .. ARGV[2] .. "-" .. string.format("%05d", current)
3.3 审批消息通知
集成WebSocket实现实时通知,前端采用SockJS作为降级方案:
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-notify")
.setAllowedOrigins("*")
.withSockJS();
}
}
4. 系统部署与性能优化
4.1 生产环境配置建议
application-prod.yml关键配置示例:
yaml复制spring:
datasource:
url: jdbc:mysql://db-cluster:3306/doc_system?useSSL=false&serverTimezone=Asia/Shanghai
hikari:
maximum-pool-size: 20
connection-timeout: 30000
redis:
cluster:
nodes: redis-node1:6379,redis-node2:6379,redis-node3:6379
lettuce:
pool:
max-active: 16
max-wait: 10000
server:
tomcat:
max-threads: 200
accept-count: 100
4.2 性能调优实战
通过JProfiler分析发现的三个关键优化点:
- MyBatis二级缓存优化:
java复制@Configuration
public class MyBatisConfig {
@Bean
public MybatisRedisCache mybatisRedisCache() {
return new MybatisRedisCache("docCache", 30, TimeUnit.MINUTES);
}
}
- 审批历史查询优化:
sql复制-- 原查询(执行时间约1200ms)
SELECT * FROM doc_approval WHERE doc_id = ? ORDER BY create_time DESC;
-- 优化后(添加联合索引后降至200ms)
ALTER TABLE doc_approval ADD INDEX idx_doc_create (doc_id, create_time);
- 文件导出内存控制:
java复制@GetMapping("/export")
public void exportDocuments(HttpServletResponse response) {
response.setContentType("application/vnd.ms-excel");
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) { // 启用流式导出
// 分批查询数据写入
}
}
5. 常见问题解决方案
5.1 审批人无法看到待办任务
可能原因及排查步骤:
- 检查Activiti的
ACT_RU_IDENTITYLINK表是否有对应关系 - 验证用户是否在正确的用户组(dept_leader等)
- 查看流程定义XML中的candidateGroups配置
5.2 文件编号重复问题
解决方案:
- 检查Redis持久化配置(AOF+RDB)
- 增加本地缓存作为二级备份
- 实现编号恢复机制:
java复制public String recoverDocNumber(String deptCode, String year) {
String key = "doc:serial:" + deptCode + ":" + year;
Long maxDbSerial = documentMapper.selectMaxSerial(deptCode, year);
Long current = redisTemplate.opsForValue().get(key);
if (current == null || current < maxDbSerial) {
redisTemplate.opsForValue().set(key, maxDbSerial + 1);
}
return generateDocNumber(deptCode);
}
5.3 大文件上传失败
优化方案:
- 前端分片上传(使用plupload等库)
- 后端合并分片代码示例:
java复制@PostMapping("/mergeChunks")
public Result mergeChunks(String fileMd5, String fileName) {
File tempFolder = new File(uploadPath, fileMd5);
File destFile = new File(uploadPath, fileName);
try (FileChannel outChannel = new FileOutputStream(destFile).getChannel()) {
for (File chunk : tempFolder.listFiles()) {
try (FileChannel inChannel = new FileInputStream(chunk).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
}
FileUtils.deleteDirectory(tempFolder);
return Result.success();
}
6. 项目扩展方向
在实际使用过程中,我们发现系统还可以在以下方面进行增强:
- 移动端适配:开发基于uniapp的跨平台APP,实现扫码签批功能
- 电子签章集成:对接合规的CA认证系统
- 智能分类:利用HanLP实现发文自动分类
- 版本对比:集成DiffMatchPatch实现文档版本差异可视化
java复制// HanLP分类示例
public String autoClassify(String content) {
List<String> keywordList = HanLP.extractKeyword(content, 5);
Map<String, Double> categoryScores = classificationModel.predict(keywordList);
return Collections.max(categoryScores.entrySet(),
Map.Entry.comparingByValue()).getKey();
}
这个项目让我深刻体会到,办公系统的核心不在于技术复杂度,而在于对业务规则的理解和异常情况的处理。比如有次客户提出"领导手写批注需要电子化留存",我们最终通过集成手写板输入方案解决了这个问题。这种实际场景中的需求,往往才是检验系统设计是否完善的关键。
