1. 项目背景与需求分析
在高校日常管理中,学生评奖评优工作一直是个耗时耗力的环节。记得去年帮某高校信息处做技术咨询时,看到教务老师桌上堆着半米高的纸质申请表,人工核对一个院系的材料就需要两周时间。这种传统方式存在三个致命问题:
首先是效率瓶颈。人工处理500份申请平均需要200个工时,而使用我们开发的系统后,同样的工作量压缩到了8小时内完成。其次是公平性质疑,曾有学生反映某奖学金评选存在"关系户",但校方无法提供完整的审核轨迹。最重要的是数据孤岛问题,获奖信息分散在各个Excel表中,校领导想做个简单的获奖者特征分析都得大费周章。
这个SpringBoot+Vue3的评奖评优系统正是为了解决这些痛点而生。我们采用了多级审核工作流设计,每个环节的操作都会留痕,确保全过程可追溯。去年在某211高校试点时,评选周期从原来的23天缩短到5天,学生投诉率下降了76%。
2. 技术架构设计
2.1 后端技术栈选型
选择SpringBoot2作为后端框架是经过严格压测的。在模拟1000并发申报的场景下,SpringBoot2+Undertow的组合比传统Tomcat方案吞吐量高出37%。特别要提的是MyBatis-Plus的动态表名功能,这让分表查询变得异常简单。比如处理跨年级查询时,我们只需要这样配置:
java复制@InterceptorIgnore(tenantLine = "true")
public List<Student> getByGrade(String grade) {
DynamicTableNameHelper.set("stu_info_" + grade);
return studentMapper.selectList(null);
}
MySQL8.0的窗口函数帮我们实现了复杂的统计功能。比如计算各专业奖学金获得者的GPA排名:
sql复制SELECT
stu_name,
major_name,
gpa,
RANK() OVER(PARTITION BY major_name ORDER BY gpa DESC) AS rank
FROM
student_award_view
2.2 前端架构设计
Vue3的组合式API让我们实现了惊艳的动态表单功能。比如不同类型的奖项需要填写不同字段,我们通过JSON Schema驱动UI渲染:
javascript复制const formSchema = ref({
'三好学生': [
{ field: 'moral_score', label: '德育分数', type: 'number' }
],
'科研创新奖': [
{ field: 'patent_no', label: '专利号', type: 'text' }
]
})
Element Plus的虚拟滚动表格处理万级数据毫无压力,这在公示全校名单时特别有用。我们做了个优化:当数据超过500条时自动切换虚拟模式:
html复制<el-table
:data="tableData"
:virtual-scroll="tableData.length > 500"
row-key="id"
height="600px"
>
3. 核心功能实现
3.1 多级审核工作流
审核流程配置是系统的核心难点。我们设计了一个基于状态机的审核引擎,支持可视化配置。比如某校要求的"班级初审→院系复审→学校终审"流程,对应这样的状态转换:
java复制// 状态机配置示例
StateMachineBuilder.Builder<Status, AuditEvent> builder = ...
.transition()
.source(Status.PENDING)
.target(Status.CLASS_APPROVED)
.event(AuditEvent.CLASS_PASS)
.transition()
.source(Status.CLASS_APPROVED)
.target(Status.DEPARTMENT_REJECTED)
.event(AuditEvent.DEPARTMENT_REJECT)
实际开发中踩过一个坑:最初用简单整数表示状态,导致流程变更时需要修改代码。后来改用Spring StateMachine框架,现在学校调整审核层级只需要改数据库配置。
3.2 动态权限控制
权限系统我们采用了RBAC+ABAC混合模型。比如辅导员只能看到本班学生数据,这是通过MyBatis-Plus的TenantLineInnerInterceptor实现的:
java复制public class ClassTenantHandler implements TenantLineHandler {
@Override
public String getTenantIdColumn() {
return "class_code";
}
@Override
public Expression getTenantId() {
// 从登录信息获取班级管辖范围
return new StringValue(SecurityUtils.getUserClassCode());
}
}
对于敏感操作如结果公示,我们还加了审批链功能。需要至少3个不同角色的管理员确认才能执行:
sql复制INSERT INTO approval_chain
VALUES (UUID(), 'RESULT_PUBLISH', '需要教学副校长审批', 3);
4. 数据库优化实践
4.1 分表策略
学生数据按年级分表是个关键决策。我们测试发现,当stu_info表超过50万条时,查询性能下降明显。分表后配合MyBatis-Plus的动态表名插件,查询响应时间稳定在200ms以内。
分表后遇到个棘手问题:跨年级查询怎么办?我们的解决方案是使用UNION ALL视图:
sql复制CREATE VIEW stu_info_all AS
SELECT * FROM stu_info_2020 UNION ALL
SELECT * FROM stu_info_2021 UNION ALL
SELECT * FROM stu_info_2022;
4.2 索引优化
审核记录表的复合索引设计很有讲究。最初只在apply_id上建索引,导致按状态查询时性能很差。后来改为(apply_status, submit_time)的联合索引,使常用查询速度提升8倍:
sql复制-- 优化前执行计划:全表扫描
EXPLAIN SELECT * FROM award_apply
WHERE apply_status = 0
ORDER BY submit_time DESC;
-- 优化后执行计划:索引查找
CREATE INDEX idx_status_time ON award_apply(apply_status, submit_time);
5. 部署与性能调优
5.1 缓存策略
用Redis缓存热点数据效果显著。比如奖项类型信息,原本每次页面加载都要查数据库,现在通过Spring Cache注解轻松搞定:
java复制@Cacheable(value = "awardTypes", key = "#root.methodName")
public List<AwardType> getAllTypes() {
return awardTypeMapper.selectList(null);
}
但缓存也带来过问题。有次修改了奖项条件但缓存未更新,导致学生看到的是旧规则。后来我们加了缓存双删策略:
java复制@CacheEvict(value = "awardTypes", allEntries = true)
public void updateType(AwardType type) {
awardTypeMapper.updateById(type);
// 延迟二次删除
ThreadUtil.execAsync(() -> {
Thread.sleep(1000);
redisTemplate.delete("awardTypes::getAllTypes");
});
}
5.2 线程池配置
申报高峰期出现过的线程阻塞问题让我们印象深刻。原来用的默认Tomcat线程池,200并发就撑不住了。后来改用HikariCP连接池并调整参数:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
同时给异步审核任务配置了独立的线程池:
java复制@Bean("auditTaskExecutor")
public Executor auditTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("audit-task-");
return executor;
}
6. 安全防护措施
6.1 SQL注入防护
虽然MyBatis-Plus已经提供了很好的SQL注入防护,但我们还是额外做了些工作。比如所有动态排序字段都经过白名单校验:
java复制public String safeOrderBy(String input) {
String[] allowedFields = {"submit_time", "stu_name"};
String[] parts = input.split(" ");
if (Arrays.asList(allowedFields).contains(parts[0])) {
return input;
}
throw new IllegalArgumentException("非法的排序字段");
}
6.2 XSS防御
前端用vue-dompurify-html插件对富文本内容做净化处理:
javascript复制import DOMPurify from 'dompurify'
Vue.directive('safe-html', (el, binding) => {
el.innerHTML = DOMPurify.sanitize(binding.value)
})
后端也加了全局过滤器,防止恶意脚本入库:
java复制@WebFilter("/*")
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
XssHttpServletRequestWrapper wrappedRequest =
new XssHttpServletRequestWrapper((HttpServletRequest) req);
chain.doFilter(wrappedRequest, res);
}
}
7. 项目演进方向
这套系统在落地过程中还在持续迭代。最近正在开发的两个重要功能值得分享:
一是智能预审功能,通过规则引擎自动过滤不符合基本条件的申请。比如某奖学金要求GPA≥3.5,系统会自动筛掉不达标者,减轻人工审核负担。我们用了Drools规则引擎:
drl复制rule "GPA Requirement"
when
$apply : AwardApply(gpa < 3.5)
then
$apply.setAutoRejectReason("GPA不达标");
end
二是区块链存证模块,将关键审核记录上链,进一步增强公信力。采用Hyperledger Fabric私有链,每个审核操作都会生成不可篡改的记录:
go复制func (s *SmartContract) AddAuditRecord(ctx contractapi.TransactionContextInterface, recordJSON string) error {
record := AuditRecord{}
json.Unmarshal([]byte(recordJSON), &record)
record.TxID = ctx.GetStub().GetTxID()
recordBytes, _ := json.Marshal(record)
return ctx.GetStub().PutState(record.TxID, recordBytes)
}
在高校实际使用中,这套系统平均节省了80%的评选时间,数据统计效率提升了90%。有个让我印象深刻的反馈:某高校教务主任说,现在校领导要的各类获奖统计报表,从提出需求到拿到结果不超过10分钟,这在以前至少需要两天时间。