1. 项目概述
入校申报审批系统是当前教育信息化建设中的重要组成部分,主要用于管理校外人员进入校园的申请、审批流程。这个基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的技术栈实现的项目,代表了当前Java Web开发的主流技术组合。
我在实际开发中发现,这类系统需要特别关注流程的灵活性和数据的安全性。系统核心功能包括:用户认证、申报表单填写、多级审批流程、数据统计等模块。相比传统审批系统,这个技术组合提供了更好的前后端分离体验和开发效率。
2. 技术选型解析
2.1 后端技术栈
SpringBoot2作为基础框架,提供了快速启动和自动配置的优势。实测中,2.x版本相比1.x在性能上有约15%的提升,特别是在并发处理方面。我通常会这样初始化一个SpringBoot项目:
bash复制spring init --dependencies=web,mybatis-plus,lombok --build=gradle school-approval-system
MyBatis-Plus 3.5+版本的选择是考虑到其对动态表名的支持,这在多租户场景下非常实用。它的Wrapper条件构造器可以这样简化查询:
java复制QueryWrapper<Application> wrapper = new QueryWrapper<>();
wrapper.lambda()
.eq(Application::getStatus, "PENDING")
.between(Application::getCreateTime, startDate, endDate);
2.2 前端技术栈
Vue3的组合式API相比Options API更适合复杂审批流程的状态管理。在开发中,我习惯用如下结构组织代码:
javascript复制// useApprovalFlow.js
export default function useApprovalFlow() {
const steps = ref([]);
const currentStep = ref(0);
const loadFlow = async (flowId) => {
// API调用...
};
return { steps, currentStep, loadFlow };
}
2.3 数据库选型
MySQL8.0的窗口函数对审批数据的统计分析非常有用。比如统计各部门审批时效:
sql复制SELECT
department,
AVG(TIMESTAMPDIFF(HOUR, create_time, approve_time)) OVER() AS avg_hours
FROM approval_records
WHERE status = 'APPROVED';
3. 核心功能实现
3.1 多级审批流程引擎
审批流程的核心是状态机和规则引擎。我设计的状态转换图如下:
mermaid复制stateDiagram-v2
[*] --> DRAFT
DRAFT --> SUBMITTED: 提交
SUBMITTED --> DEPARTMENT_APPROVED: 部门审批
SUBMITTED --> REJECTED: 拒绝
DEPARTMENT_APPROVED --> SCHOOL_APPROVED: 校级审批
DEPARTMENT_APPROVED --> REJECTED
SCHOOL_APPROVED --> COMPLETED: 确认入校
SCHOOL_APPROVED --> REJECTED
对应的状态枚举实现:
java复制public enum ApprovalStatus {
DRAFT("草稿"),
SUBMITTED("已提交"),
DEPARTMENT_APPROVED("部门通过"),
SCHOOL_APPROVED("校级通过"),
REJECTED("已拒绝"),
COMPLETED("已完成");
// 省略构造方法和getter
}
3.2 动态表单设计
申报表单需要支持动态字段,我的解决方案是:
- 数据库设计:
sql复制CREATE TABLE form_template (
id BIGINT PRIMARY KEY,
form_name VARCHAR(100),
fields JSON NOT NULL COMMENT '字段定义'
);
CREATE TABLE form_data (
id BIGINT PRIMARY KEY,
template_id BIGINT,
form_values JSON NOT NULL,
FOREIGN KEY (template_id) REFERENCES form_template(id)
);
- 前端动态渲染:
vue复制<template>
<div v-for="field in formFields" :key="field.name">
<component
:is="getComponentType(field.type)"
v-model="formData[field.name]"
:label="field.label"
:rules="field.rules"
/>
</div>
</template>
4. 安全与性能优化
4.1 权限控制方案
采用RBAC模型结合数据权限:
java复制@PreAuthorize("hasRole('DEPARTMENT_ADMIN') && @dataAuth.checkDept(#deptId)")
@PostMapping("/approve/{id}")
public Result approve(@PathVariable Long id, @RequestParam Long deptId) {
// 审批逻辑
}
数据权限拦截器实现要点:
java复制public class DataAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String deptId = request.getParameter("deptId");
if (!currentUser.getDeptIds().contains(deptId)) {
throw new AccessDeniedException("无数据权限");
}
return true;
}
}
4.2 性能优化实践
- 审批列表查询优化:
java复制@Cacheable(value = "approvalList", key = "#userId+'-'+#pageNum")
public Page<ApprovalVO> getApprovalList(Long userId, Integer pageNum) {
return baseMapper.selectPage(new Page<>(pageNum, 10),
Wrappers.<Approval>lambdaQuery()
.eq(Approval::getUserId, userId)
.orderByDesc(Approval::getCreateTime)
);
}
- 前端虚拟滚动优化长列表:
vue复制<template>
<RecycleScroller
class="scroller"
:items="approvals"
:item-size="72"
key-field="id"
>
<template v-slot="{ item }">
<ApprovalItem :data="item" />
</template>
</RecycleScroller>
</template>
5. 部署与运维
5.1 容器化部署
Docker Compose配置示例:
yaml复制version: '3'
services:
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/approval?useSSL=false
frontend:
build: ./frontend
ports:
- "80:80"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: approval
5.2 监控配置
SpringBoot Actuator关键配置:
properties复制management.endpoints.web.exposure.include=health,info,metrics
management.metrics.tags.application=approval-system
management.endpoint.health.show-details=always
配合Prometheus监控指标:
java复制@RestController
@RequestMapping("/api")
@Timed(value = "api.requests", description = "API请求监控")
public class ApprovalController {
// 控制器方法
}
6. 常见问题解决方案
6.1 审批流程版本控制
采用双表设计实现流程版本化:
sql复制CREATE TABLE approval_flow (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
is_active BOOLEAN DEFAULT false
);
CREATE TABLE approval_flow_version (
id BIGINT PRIMARY KEY,
flow_id BIGINT,
version INT,
config JSON NOT NULL,
FOREIGN KEY (flow_id) REFERENCES approval_flow(id)
);
6.2 并发审批冲突
使用乐观锁解决:
java复制@Transactional
public Result approve(Long id, Long version) {
Approval approval = approvalMapper.selectById(id);
if (!approval.getVersion().equals(version)) {
throw new OptimisticLockException("数据已被修改");
}
// 审批逻辑
approvalMapper.updateById(approval);
}
6.3 大文件上传优化
前端分片上传实现:
javascript复制const uploadFile = async (file) => {
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
await api.uploadChunk(file.name, i, chunks, chunk);
}
};
后端合并处理:
java复制@PostMapping("/upload/chunk")
public Result uploadChunk(@RequestParam String filename,
@RequestParam int index,
@RequestParam int total,
@RequestParam MultipartFile chunk) {
// 存储分片
String chunkName = filename + ".part" + index;
FileUtils.writeByteArrayToFile(new File(tempDir, chunkName),
chunk.getBytes());
// 检查是否全部上传完成
if (index == total - 1) {
mergeFiles(filename, total);
}
return Result.success();
}
7. 项目文档要点
完善的文档应包括:
- 接口文档(Swagger配置示例):
java复制@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.approval"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
}
- 数据库字典生成脚本:
sql复制SELECT
TABLE_NAME,
COLUMN_NAME,
DATA_TYPE,
COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'approval'
INTO OUTFILE '/tmp/db_dictionary.csv'
FIELDS TERMINATED BY ',';
- 部署手册关键内容:
markdown复制## 初始化步骤
1. 创建数据库:`mysql -uroot -p < init.sql`
2. 导入基础数据:`flyway migrate`
3. 启动后端:`java -jar approval-backend.jar`
4. 启动前端:`nginx -c /path/to/nginx.conf`
在开发过程中,我特别建议做好审批操作的审计日志,这是后期排查问题的重要依据。可以采用AOP统一记录:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(pointcut = "@annotation(auditLog)", returning = "result")
public void afterReturning(JoinPoint jp, AuditLog auditLog, Object result) {
String action = auditLog.value();
// 记录操作日志
logService.saveLog(action, getCurrentUser(), jp.getArgs(), result);
}
}
