这套基于SpringBoot+Vue+MyBatis+MySQL的办公管理系统,是我在为企业客户实施数字化转型过程中沉淀的典型解决方案。现代企业办公场景中,传统纸质审批和Excel管理方式已明显力不从心——我曾见过一个30人团队每月产生200+审批单,仅文件查找就消耗15%的工作时间。通过这套系统实施后,同类流程处理效率提升60%以上。
系统采用前后端分离架构,这是经过多个项目验证的可靠选择。后端用SpringBoot构建RESTful API,前端用Vue实现动态交互,MyBatis操作MySQL数据库。这种架构下,我们的开发团队可以并行工作:后端工程师专注业务逻辑和数据处理,前端团队同时打磨用户体验,迭代速度比传统单体架构快2-3倍。
SpringBoot的选择绝非偶然。在经历过传统SSH架构的配置地狱后,SpringBoot的自动配置特性让我们的项目启动时间从3天缩短到3小时。特别值得分享的是其Starter机制——比如引入spring-boot-starter-data-redis后,只需配置连接信息就能直接使用RedisTemplate,这种开箱即用的体验大幅降低了技术集成成本。
安全方面采用JWT而非Session,这是考虑到办公系统常有跨终端访问需求。我们实现的Token刷新机制很有意思:当Token过期时间剩余不足30%时,前端会静默获取新Token,用户完全感知不到重新登录的过程。这比固定过期时间的方案体验更友好。
Vue3的组合式API让我们能更灵活地组织代码。比如公文审批模块,我们将审批流程状态机、表单验证、附件预览等逻辑拆分为独立composable函数,不同审批环节复用相同逻辑,代码量减少40%。
Element Plus的按需引入也很有讲究。通过配置unplugin-vue-components插件,组件只在首次使用时加载,最终打包体积控制在1MB以内。这里有个优化技巧:将Message、Loading等轻量组件单独打包,首屏加载时间能再减少200ms。
RBAC模型的实际应用比理论复杂得多。我们设计了五层权限控制:
@PreAuthorize注解特别提醒:权限数据表一定要建立role_menu和user_role的联合唯一索引,否则会出现重复授权导致鉴权混乱。我们曾因此导致某个部门的员工能看到全公司数据,教训深刻。
审批流程的核心是状态机设计。我们定义的状态转换矩阵如下:
| 当前状态 | 操作 | 下一状态 | 条件 |
|---|---|---|---|
| 起草中 | 提交 | 审批中 | 附件已上传 |
| 审批中 | 通过 | 已归档 | 所有审批人完成 |
| 审批中 | 驳回 | 起草中 | - |
| 审批中 | 转审 | 审批中 | 新审批人≠当前审批人 |
实现时采用策略模式+责任链,每个状态变更操作对应一个具体handler。这样当客户提出"加签"需求时,只需新增一个Handler类即可,不会影响现有逻辑。
员工表(staff)的索引设计很有讲究:
sql复制CREATE INDEX idx_dept_position ON staff(staff_dept, staff_position);
CREATE UNIQUE INDEX idx_account ON staff(staff_account);
联合索引保证按部门查询时的排序效率,而唯一索引防止账号重复。注意避免在性别这类低区分度字段建索引,效果微乎其微。
公文表(document)的附件存储采用"分片key+OSS路径"模式:
code复制attachment_url格式:{env}/{yyyyMM}/{document_id}_v{version}.{ext}
这种结构既便于按环境隔离数据,又天然支持版本控制。我们通过Nginx配置实现了附件下载时的权限校验,避免直接暴露OSS地址。
日程任务查询是个典型的多条件搜索场景。MyBatis动态SQL这样写:
xml复制<select id="selectTasks" resultMap="taskResult">
SELECT * FROM task
<where>
<if test="ownerId != null">AND task_owner = #{ownerId}</if>
<if test="priority != null">AND task_priority = #{priority}</if>
<if test="startTime != null">AND end_time >= #{startTime}</if>
<if test="endTime != null">AND start_time <= #{endTime}</if>
<if test="completed != null">AND is_completed = #{completed}</if>
</where>
ORDER BY task_priority DESC, start_time ASC
LIMIT #{offset}, #{pageSize}
</select>
配合PageHelper分页插件,前端传参示例如下:
json复制{
"ownerId": 10086,
"priority": 1,
"startTime": "2024-04-01",
"pageNum": 1,
"pageSize": 10
}
SpringBoot应用推荐用分层Docker镜像构建:
dockerfile复制FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar
FROM eclipse-temurin:17-jre-jammy
COPY --from=builder /app/build/libs/*.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
关键启动参数:
bash复制java -jar \
-Dspring.profiles.active=prod \
-Dserver.tomcat.max-threads=200 \
-Dspring.datasource.hikari.maximum-pool-size=20 \
-Xms512m -Xmx1024m \
app.jar
特别注意:Tomcat线程数建议设为CPU核心数的2-4倍,连接池大小不要超过数据库最大连接数的80%。
Vue项目通过nginx部署时,这个配置能显著提升加载速度:
nginx复制server {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1k;
gzip_comp_level 6;
location / {
try_files $uri $uri/ /index.html;
expires 1y;
add_header Cache-Control "public";
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header X-Real-IP $remote_addr;
}
}
启用Brotli压缩效果更好,但需要编译nginx时加入--with-http_brotli_module。实测可将vue-router等大文件再压缩20%-30%。
初期我们使用foreach拼接SQL实现批量插入:
xml复制<insert id="batchInsert">
INSERT INTO document VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{item.title}, ...)
</foreach>
</insert>
当批量插入1000条数据时,SQL长度超过MySQL默认的4MB限制。解决方案是:
defaultExecutorType=BATCH<transaction>包裹批量操作调整后性能提升8倍,内存消耗降低90%。
在公文列表页,我们曾遇到这样的问题:
js复制const documents = ref([])
// 从API获取数据后直接赋值
documents.value = apiResponse.data
当修改某个公文对象时,视图不更新。原因在于API返回的是普通对象,失去响应性。正确做法:
js复制documents.value = reactive(apiResponse.data)
// 或者使用深拷贝
documents.value = JSON.parse(JSON.stringify(apiResponse.data))
更推荐的做法是使用Pinia管理状态,天然避免这类问题。
这套系统在实际使用中还可以进一步扩展:
我在GitHub上开源了核心模块的代码实现,包含完整的Docker Compose部署文件。获取方式见文末联系方式,备注"办公系统"我会优先处理。对于企业用户,还可以提供定制化的二次开发服务。