markdown复制## 1. 项目概述
去年帮学院开发竞赛管理系统时,我深刻体会到现有手工登记方式的低效——Excel表格满天飞,获奖证书难追溯,统计报表全靠人工。这个基于Vue+Node的全栈系统,正是为了解决这些痛点而生。系统上线后,竞赛申报流程从平均3天缩短到2小时,证书电子化率提升至100%,特别适合高校二级学院或竞赛组委会使用。
系统采用经典的前后端分离架构:前端用Vue3+Element Plus实现响应式管理界面,后端基于Node.js+Express构建RESTful API,MySQL存储结构化数据。下面结合毕设开发实际经验,详解各模块设计与实现要点。
## 2. 技术选型解析
### 2.1 前端技术栈
选择Vue3而非React/Angular主要基于三点考量:
1. 学院教师团队有Vue2基础,TypeScript可选特性降低迁移成本
2. Composition API更适合复杂业务逻辑(如多级评审流程)
3. Element Plus组件库开箱即用的表单/表格控件,加速管理后台开发
关键配置示例(vite.config.ts):
```typescript
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
})
2.2 后端技术栈
Node.js+Express的组合在高校环境中优势明显:
- 开发效率高,适合学生团队快速迭代
- 中间件机制完美适配竞赛流程的管道式处理
- 轻量级架构节省学校服务器资源
数据库选型对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| MySQL | 学院现有运维能力支持 | 文档存储灵活性不足 |
| MongoDB | 适合非结构化数据 | 需要额外学习成本 |
| SQLite | 零配置部署 | 并发性能有限 |
最终选择MySQL 8.0,因其事务特性对评审计分等关键操作至关重要。
3. 核心功能实现
3.1 多级评审工作流
典型学科竞赛的评审流程包含:
- 学生申报 → 2. 指导教师审核 → 3. 院系初审 → 4. 专家盲审 → 5. 结果公示
使用状态机模式实现(backend/models/Workflow.js):
javascript复制class CompetitionWorkflow {
states = {
DRAFT: { to: ['SUBMITTED'] },
SUBMITTED: { to: ['APPROVED', 'REJECTED'] },
APPROVED: { to: ['PUBLISHED'] }
}
transition(current, next) {
if (!this.states[current].to.includes(next)) {
throw new Error(`非法状态转换: ${current} → ${next}`)
}
// 记录操作日志
AuditLog.create({ from: current, to: next })
}
}
3.2 证书电子签名
解决证书伪造问题的技术方案:
- 使用crypto模块生成SHA256哈希
- 将评委签名图片转为Base64编码
- 通过Canvas合成防伪二维码
关键代码(frontend/components/Certificate.vue):
vue复制<template>
<canvas ref="canvas" width="800" height="600"></canvas>
</template>
<script setup>
import { drawQR } from '@/utils/antiFake'
onMounted(() => {
const ctx = this.$refs.canvas.getContext('2d')
drawQR(ctx, `${certId}-${sha256Hash}`)
})
</script>
4. 数据库设计要点
4.1 主要表结构
sql复制CREATE TABLE competitions (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
start_date DATETIME,
end_date DATETIME,
max_team_members TINYINT DEFAULT 5
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE participants (
student_id CHAR(12) PRIMARY KEY,
competition_id INT,
team_role ENUM('leader', 'member'),
FOREIGN KEY (competition_id) REFERENCES competitions(id)
);
4.2 性能优化实践
- 为频繁查询的字段添加索引:
sql复制ALTER TABLE submissions ADD INDEX idx_competition_status (competition_id, status);
-
大文件(如作品附件)使用OSS存储,数据库只存URL
-
定期归档历史数据:
sql复制CREATE TABLE archive_participants LIKE participants;
5. 部署与运维
5.1 生产环境配置
推荐使用PM2管理Node进程:
bash复制pm2 start ecosystem.config.js --env production
典型ecosystem配置:
javascript复制module.exports = {
apps: [{
name: 'competition-api',
script: './bin/www',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}]
}
5.2 安全防护措施
- 接口防护:
- 使用helmet中间件加固HTTP头
- 速率限制(express-rate-limit)
- JWT过期时间设为2小时
- 数据安全:
- 密码字段使用bcrypt哈希
- 敏感操作记录详细审计日志
- 定期mysqldump全量备份
6. 常见问题排查
6.1 跨域问题
开发环境常见报错及解决:
bash复制Access-Control-Allow-Origin missing
解决方案:
javascript复制app.use(cors({
origin: ['http://localhost:5173'],
methods: ['GET','POST','PUT']
}))
6.2 文件上传失败
可能原因:
- Nginx默认限制上传大小
- 目录权限不足
- 文件名含特殊字符
排查命令:
bash复制# 检查Nginx配置
grep client_max_body_size /etc/nginx/nginx.conf
# 修改目录权限
chown -R node:node /uploads
7. 扩展功能建议
- 微信小程序端:便于学生随时查看进度
- 自动化短信通知:关键节点提醒
- 查重系统集成:防止作品抄袭
- 可视化看板:实时展示参赛数据
实现小程序对接的要点:
javascript复制// 获取微信openid
router.post('/wechat/auth', async (ctx) => {
const { code } = ctx.request.body
const res = await axios.get(`https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=${code}`)
ctx.body = { openid: res.data.openid }
})
在真实项目部署时,建议先用10-20人的小型竞赛试运行。我们首次上线就因低估并发量导致MySQL连接池爆满,后来通过增加连接数和引入Redis缓存解决了性能瓶颈。
code复制