1. 项目概述
这个Web项目申报管理系统是我去年为某高校科研处开发的一套信息化解决方案。当时他们还在用Excel表格和纸质材料管理各类科研项目申报,经常出现材料丢失、进度混乱的情况。系统上线后,实现了从项目申报、评审到结题的全流程数字化管理,老师们再也不用抱着厚厚一沓材料到处跑审批了。
系统采用前后端分离架构,后端基于SpringBoot+MyBatis实现RESTful API,前端使用Vue+ElementUI构建管理界面,数据库选用MySQL 8.0。特别设计了动态表单引擎支持不同学科的项目申报模板配置,这是整个系统最具挑战性的部分。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 后端技术栈选型
选择SpringBoot 2.7作为基础框架主要考虑三点:
- 快速启动特性:科研处要求3个月内上线,传统SSM框架的配置复杂度会拖慢进度
- 内嵌Tomcat:简化部署流程,最终打包成的单个jar文件直接通过Jenkins部署到测试和生产环境
- Starter生态:整合Redis缓存、RabbitMQ消息队列等组件只需添加依赖即可
数据库访问层采用MyBatis而非JPA的原因是:
- 项目申报表单存在大量动态字段,需要灵活的结果集映射
- 复杂统计报表需要手写优化SQL,如年度学科经费统计视图
- 历史数据归档功能涉及大批量数据操作
踩坑提醒:MyBatis的二级缓存在使用分布式部署时会遇到一致性问题,我们最终通过Redis实现了自定义缓存方案
2.2 前端架构设计
Vue 3的组合式API更适合这类中后台管理系统:
javascript复制// 动态表单组件示例
const formConfig = ref({
fields: [
{
type: 'input',
model: 'projectName',
label: '项目名称',
rules: [{ required: true }]
},
{
type: 'select',
model: 'projectType',
label: '项目类别',
options: axios.get('/api/dict/project-types')
}
]
})
Element Plus的组件库提供了现成的:
- 表单验证体系
- 表格分页组件
- 树形部门选择器
- 富文本编辑器集成
2.3 数据库关键设计
项目核心表关系如下:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| t_project | id, title, type_id, applicant_id | 项目主表 |
| t_form_template | id, form_schema, version | 动态表单模板 |
| t_approval_flow | id, project_id, current_node | 审批流程状态 |
| t_attachment | id, project_id, file_url | 电子附件管理 |
特别注意的点:
- 使用JSON类型存储动态表单配置
- 为审批流程设计了版本控制机制
- 文件存储采用MinIO分布式方案
3. 核心功能实现
3.1 动态表单引擎
这是系统最复杂的模块,技术实现路径:
- 后端定义表单JSON Schema
java复制// Java实体示例
@Data
public class FormTemplate {
private Long id;
private String templateName;
@Column(columnDefinition = "json")
private String formSchema; // 存储JSON配置
}
- 前端通过Vue动态渲染组件
javascript复制<template v-for="field in schema.fields">
<el-input
v-if="field.type === 'input'"
v-model="formData[field.model]"
:rules="field.rules"
/>
</template>
- 表单数据存储采用纵表设计
sql复制CREATE TABLE t_form_data (
id BIGINT PRIMARY KEY,
project_id BIGINT,
field_key VARCHAR(50),
field_value TEXT,
INDEX idx_project (project_id)
);
3.2 审批工作流
基于状态模式实现的轻量级审批引擎:
java复制public interface ApprovalState {
void submit(ApprovalContext context);
void approve(ApprovalContext context);
void reject(ApprovalContext context);
}
// 具体状态实现
public class DraftState implements ApprovalState {
@Override
public void submit(ApprovalContext context) {
context.setState(new PendingState());
// 发送站内信通知初审专家
messageService.notifyExperts(context.getProjectId());
}
}
审批流程图示例:
- 申请人提交 → 院系初审
- 初审通过 → 科研处形式审查
- 形式审查通过 → 专家评审
- 评审通过 → 立项公示
3.3 文件管理方案
采用MinIO搭建私有云存储:
yaml复制# application.yml配置
minio:
endpoint: http://fileserver:9000
access-key: project-upload
secret-key: encrypted_password
bucket-name: research-projects
文件上传关键代码:
java复制public String uploadFile(MultipartFile file, Long projectId) {
String objectName = "project/" + projectId + "/" +
UUID.randomUUID() + getFileExtension(file);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return endpoint + "/" + bucketName + "/" + objectName;
}
4. 系统安全设计
4.1 权限控制模型
采用RBAC与ABAC混合模型:
- 角色定义:申报人、院系管理员、科研处、评审专家、系统管理员
- 权限粒度:菜单权限、操作权限、数据权限(如只能查看本学院项目)
Shiro配置示例:
java复制@RequiresRoles("department_admin")
@RequiresPermissions("project:review")
@GetMapping("/pending-projects")
public List<Project> getPendingProjects() {
String college = getCurrentUserCollege();
return projectService.getPendingProjects(college);
}
4.2 审计日志实现
基于AOP的操作日志记录:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(auditLog)",
returning = "result")
public void afterReturning(JoinPoint jp, AuditLog auditLog, Object result) {
String operation = auditLog.value();
String params = Arrays.toString(jp.getArgs());
String username = SecurityUtils.getUsername();
auditLogService.saveLog(
new SysLog(username, operation, params));
}
}
日志表设计包含:
- 操作人、操作时间、IP地址
- 操作类型(增删改查)
- 请求参数、返回结果(脱敏后)
5. 部署与性能优化
5.1 容器化部署方案
Docker Compose编排文件示例:
yaml复制version: '3'
services:
app:
image: project-application:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: project_db
redis:
image: redis:6.2
5.2 缓存策略设计
多级缓存方案:
- 本地Caffeine缓存:高频访问的字典数据
java复制@Cacheable(value = "dictCache", key = "#type")
public List<DictItem> getDictByType(String type) {
return dictMapper.selectByType(type);
}
- Redis分布式缓存:项目详情页
java复制public ProjectDetail getProjectDetail(Long id) {
String cacheKey = "project:detail:" + id;
String json = redisTemplate.opsForValue().get(cacheKey);
if (StringUtils.isNotBlank(json)) {
return JSON.parseObject(json, ProjectDetail.class);
}
ProjectDetail detail = projectMapper.selectDetailById(id);
redisTemplate.opsForValue().set(
cacheKey,
JSON.toJSONString(detail),
30, TimeUnit.MINUTES);
return detail;
}
5.3 性能监控配置
SpringBoot Actuator集成:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.metrics.tags.application=project-system
Prometheus监控指标示例:
code复制http_server_requests_seconds_count{
method="GET",
uri="/api/projects",
status="200",
application="project-system"
}
6. 典型问题解决方案
6.1 动态表单数据查询
解决方案:采用通用查询接口+字段映射
xml复制<!-- MyBatis映射文件 -->
<select id="selectFormData" resultType="map">
SELECT field_key, field_value
FROM t_form_data
WHERE project_id = #{projectId}
</select>
<!-- 转换为前端需要的结构 -->
public Map<String, Object> buildFormData(Long projectId) {
List<FormData> dataList = formDataMapper.selectByProjectId(projectId);
return dataList.stream()
.collect(Collectors.toMap(
FormData::getFieldKey,
FormData::getFieldValue));
}
6.2 批量导出性能优化
采用分页查询+多线程处理:
java复制public void exportProjects(OutputStream output) {
ExcelWriter writer = ExcelUtil.getWriter(true);
int pageSize = 500;
int total = projectMapper.countProjects();
// 并行处理每页数据
IntStream.range(0, (total + pageSize - 1) / pageSize)
.parallel()
.forEach(page -> {
List<Project> list = projectMapper.selectPage(
page * pageSize, pageSize);
writer.write(list, page == 0);
});
writer.flush(output, true);
writer.close();
}
6.3 高并发提交控制
Redis分布式锁实现:
java复制public boolean submitProject(Long projectId) {
String lockKey = "project:submit:" + projectId;
String requestId = UUID.randomUUID().toString();
try {
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("操作过于频繁");
}
// 执行业务逻辑
return approvalService.submit(projectId);
} finally {
// 释放锁时要验证requestId防止误删
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
7. 项目演进方向
- 智能填报辅助
- 基于NLP的申报书质量检测
- 历史成功案例推荐
- 预算编制合理性校验
- 移动端适配
- 企业微信/钉钉集成
- 审批消息推送
- 扫码签名功能
- 数据分析看板
- 学科申报趋势分析
- 评审专家评分画像
- 项目成果转化追踪
这套系统经过三个月的开发和一学期的试运行,目前已经稳定支撑了全校2000+教师的科研项目管理工作。最大的收获是认识到:业务系统的核心价值不在于技术有多先进,而在于能否真正解决用户的痛点。比如最初设计的复杂动态审批流程,在实际使用中反而成了负担,后来简化为可配置的线性流程后,用户满意度明显提升。
