1. 项目概述:基于SpringBoot的大创管理系统
大创(大学生创新创业训练计划)管理系统是高校教务管理中的重要一环。去年我指导过的一个毕业设计小组,用SpringBoot框架完整实现了一套大创管理系统,包含从立项申报到结题验收的全流程管理。这个系统最核心的价值在于:用技术手段解决了传统纸质审批的三大痛点——流程不透明、材料易丢失、统计效率低。
系统采用经典的B/S架构,前端用Thymeleaf模板引擎实现服务端渲染,后端基于SpringBoot 2.7.4构建。数据库选型时,考虑到学校IT部门已有MySQL运维能力,最终采用MySQL 8.0作为主数据库,配合Redis缓存高频访问的公示数据。整个系统包含12个核心模块,从用户权限管理到项目经费报销全覆盖。
特别说明:本文涉及的所有技术方案均通过学校信息中心安全审核,系统上线后已稳定运行两个学期,处理了300+个大创项目的全生命周期管理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心需求解析
2.1 业务流程建模
大创管理的核心流程可以抽象为五个阶段:
- 申报阶段:学生组队→导师确认→在线填报
- 评审阶段:形式审查→专家盲审→结果公示
- 中期检查:进度报告→导师评价→预警处理
- 结题验收:成果提交→答辩评审→经费结算
- 成果推广:优秀案例展示→成果转化跟踪
我们使用Activiti工作流引擎驱动状态流转,每个阶段设置对应的状态机。例如申报阶段的状态转换规则如下:
java复制// 状态机配置示例
StateMachineBuilder.Builder<States, Events> builder = StateMachineBuilder.builder();
builder.configureStates()
.withStates()
.initial(States.DRAFT)
.state(States.TEACHER_APPROVED)
.state(States.COLLEGE_APPROVED);
builder.configureTransitions()
.withExternal()
.source(States.DRAFT).target(States.TEACHER_APPROVED)
.event(Events.SUBMIT)
.action(ctx -> {
// 发送站内信通知导师
messageService.notifyTeacher(ctx.getExtendedState());
});
2.2 权限模型设计
系统采用RBAC(基于角色的访问控制)模型,但针对高校场景做了特殊适配:
| 角色类型 | 权限特征 | 特殊需求 |
|---|---|---|
| 学生 | 项目创建/编辑 | 需关联导师 |
| 导师 | 项目审核 | 限制指导项目数 |
| 院系管理员 | 形式审查 | 查看本院系项目 |
| 专家评委 | 盲审打分 | 匿名处理 |
| 教务处 | 全局管理 | 数据导出 |
权限控制通过Spring Security实现,关键配置如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/project/submit").hasRole("STUDENT")
.antMatchers("/project/review").hasAnyRole("TEACHER","EXPERT")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll();
}
}
3. 关键技术实现
3.1 多级审批工作流
采用Activiti集成方案时遇到的最大挑战是动态审批路径的处理。例如某些重点项目需要增加校外专家评审环节。我们的解决方案是:
- 在流程定义中使用网关节点:
xml复制<process id="project_approval" name="Project Approval">
<startEvent id="start"/>
<sequenceFlow sourceRef="start" targetRef="collegeGateway"/>
<exclusiveGateway id="collegeGateway"/>
<sequenceFlow sourceRef="collegeGateway" targetRef="normalReview">
<conditionExpression xsi:type="tFormalExpression">
${project.level == 'GENERAL'}
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="collegeGateway" targetRef="specialReview">
<conditionExpression xsi:type="tFormalExpression">
${project.level == 'IMPORTANT'}
</conditionExpression>
</sequenceFlow>
</process>
- 审批人动态分配策略:
java复制@Service
public class DynamicAssigneeService implements TaskAssignmentHandler {
@Override
public String handleAssignment(Task task, String assignee) {
Project project = projectService.getById(task.getBusinessKey());
if("specialReview".equals(task.getTaskDefinitionKey())){
return expertService.getRandomExpert(project.getCategory());
}
return assignee;
}
}
3.2 文件安全处理
系统需要处理各类申报材料(Word、PDF等),我们实现了三重防护:
- 文件类型白名单:
java复制private static final Set<String> ALLOWED_TYPES = Set.of(
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
public void uploadFile(MultipartFile file) {
if(!ALLOWED_TYPES.contains(file.getContentType())){
throw new IllegalFileTypeException();
}
// 后续处理...
}
- 病毒扫描集成:
java复制@Async
public CompletableFuture<ScanResult> scanFile(Path filePath) {
ClamAVClient clamav = new ClamAVClient("192.168.1.100", 3310);
byte[] response = clamav.scan(filePath);
return CompletableFuture.completedFuture(
ClamAVClient.isCleanReply(response) ?
ScanResult.CLEAN : ScanResult.INFECTED
);
}
- PDF内容安全处理(防御XSS):
java复制public SanitizedPdf sanitizePdf(byte[] original) {
PDFParser parser = new PDFParser(new RandomAccessBuffer(original));
parser.parse();
COSDocument cosDoc = parser.getDocument();
// 移除危险元素
for(COSObject obj : cosDoc.getObjects()){
if(obj instanceof COSStream){
COSStream stream = (COSStream)obj;
filterJavaScript(stream);
}
}
// 返回处理后的字节流
return new SanitizedPdf(cosDoc);
}
4. 典型问题解决方案
4.1 高并发申报提交
在申报截止日前常出现集中提交的情况,我们通过以下措施保障系统稳定:
- Nginx限流配置:
nginx复制http {
limit_req_zone $binary_remote_addr zone=submit:10m rate=5r/s;
server {
location /project/submit {
limit_req zone=submit burst=10 nodelay;
proxy_pass http://backend;
}
}
}
- 数据库优化:
sql复制-- 申报表增加复合索引
ALTER TABLE project_application
ADD INDEX idx_college_status (college_id, status);
- 异步日志处理:
java复制@KafkaListener(topics = "application-log")
public void handleLog(ApplicationLog log) {
logService.asyncSave(log); // 使用Hibernate批量插入
}
4.2 跨院系数据隔离
各院系管理员只能查看本院项目,实现方案:
- 数据过滤拦截器:
java复制@Component
public class DataFilterInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
User user = getCurrentUser();
if(user.getCollegeId() != null && user.isCollegeAdmin()){
RequestContext.setDataScope(user.getCollegeId());
}
return true;
}
}
- MyBatis插件自动追加条件:
java复制@Intercepts(@Signature(type= Executor.class,
method="query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}))
public class DataScopeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object parameter = invocation.getArgs()[1];
if(parameter instanceof ParameterMap
&& RequestContext.getDataScope() != null){
ParameterMap map = (ParameterMap)parameter;
map.put("collegeId", RequestContext.getDataScope());
}
return invocation.proceed();
}
}
5. 部署与监控方案
5.1 容器化部署
采用Docker Compose编排方案:
yaml复制version: '3.8'
services:
app:
image: registry.cn-hangzhou.aliyuncs.com/yourrepo/dcms:${TAG}
environment:
- SPRING_PROFILES_ACTIVE=prod
ports:
- "8080:8080"
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
redis:
image: redis:6-alpine
command: redis-server --requirepass ${REDIS_PASS}
5.2 健康监控配置
Spring Boot Actuator扩展配置:
properties复制# application-prod.properties
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true
# 自定义健康指标
@Component
public class FileStorageHealthIndicator
implements HealthIndicator {
@Override
public Health health() {
Path storagePath = Paths.get("/data/uploads");
if(Files.notExists(storagePath)){
return Health.down()
.withDetail("error", "存储目录不存在")
.build();
}
return Health.up()
.withDetail("freeSpace",
Files.getFileStore(storagePath)
.getUsableSpace() / 1024 / 1024 + "MB")
.build();
}
}
6. 开发经验总结
-
文档自动生成技巧:
- 使用Swagger UI生成API文档时,推荐添加分组配置:
java复制@Bean public Docket publicApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("public-apis") .select() .apis(RequestHandlerSelectors.basePackage("com.dcms.api")) .paths(PathSelectors.ant("/api/**")) .build(); } -
性能优化发现:
- 项目列表查询的N+1问题解决方案:
java复制@EntityGraph(attributePaths = {"teacher","college"}) @Query("select p from Project p where p.status = :status") List<Project> findByStatusWithGraph(@Param("status") Status status); -
事务处理教训:
java复制// 错误示例:大事务导致锁超时 @Transactional public void approveProject(Long id) { Project project = projectRepo.findById(id).orElseThrow(); project.setStatus(APPROVED); projectRepo.save(project); // 耗时操作不应放在事务内 emailService.sendApprovalNotice(project); statisticsService.updateCollegeStats(project.getCollege()); } // 正确做法:拆分事务边界 @Transactional public void approveProject(Long id) { Project project = projectRepo.findById(id).orElseThrow(); project.setStatus(APPROVED); projectRepo.save(project); } @Async public void afterApproval(Long projectId) { Project project = projectRepo.findById(projectId).orElseThrow(); emailService.sendApprovalNotice(project); statisticsService.updateCollegeStats(project.getCollege()); } -
缓存使用心得:
- 评审结果缓存策略示例:
java复制@Cacheable(value = "reviewResults", key = "#projectId", condition = "#projectId != null", unless = "#result == null") public ReviewResult getCachedResult(Long projectId) { return reviewRepo.findByProjectId(projectId); } @CacheEvict(value = "reviewResults", key = "#result.project.id") public ReviewResult saveReview(Review review) { return reviewRepo.save(review); }
系统最终实现的功能全景图包含:
- 用户中心:统一身份认证(集成学校LDAP)
- 项目管理:全生命周期跟踪看板
- 评审管理:双盲评审+AI查重
- 经费管理:预算执行可视化
- 成果展示:多维度数据看板
- 消息中心:邮件+站内信+微信通知
这套系统在落地过程中最大的收获是:必须深入理解业务规则后再进行技术设计。比如最初我们设计的评审流程是线性的,但实际业务中经常需要根据项目类型动态调整评审路径。最终采用工作流引擎+规则引擎的组合方案,才真正满足了业务灵活性需求。
