1. 项目背景与核心需求
大学社团管理一直是高校学生工作中的重要环节。传统的社团申报审批流程往往依赖纸质表格和线下会议,存在效率低下、信息不透明、资料易丢失等问题。基于Web的社团申报审批系统正是为了解决这些痛点而设计的。
这个系统需要实现的核心功能包括:
- 社团在线申报功能
- 审批流程电子化
- 申报材料数字化管理
- 审批进度实时跟踪
- 历史记录可追溯
2. 技术选型与架构设计
2.1 前端技术栈
前端采用Vue.js框架,主要基于以下考虑:
- 组件化开发模式适合管理系统类项目
- 丰富的UI组件库(如Element UI)可快速搭建管理界面
- 响应式设计适配不同终端
- 良好的开发体验和社区支持
核心前端技术包括:
- Vue 3.x(Composition API)
- Vue Router管理路由
- Axios处理HTTP请求
- Element Plus UI组件库
- ECharts用于数据可视化(可选)
2.2 后端技术栈
后端选择Spring Boot框架,主要优势在于:
- 快速开发,约定优于配置
- 完善的生态和社区支持
- 与前端分离的RESTful API设计
- 内置安全机制
关键技术组件:
- Spring Boot 2.7.x
- Spring Security(认证授权)
- MyBatis-Plus(数据库操作)
- Redis(缓存)
- Swagger(API文档)
2.3 数据库设计
系统采用MySQL作为主数据库,主要表结构包括:
- 用户表(user)
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`role` tinyint NOT NULL COMMENT '角色(0-学生,1-社团负责人,2-管理员,3-审批老师)',
`college` varchar(100) DEFAULT NULL COMMENT '学院',
`major` varchar(100) DEFAULT NULL COMMENT '专业',
`phone` varchar(20) DEFAULT NULL COMMENT '电话',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`status` tinyint DEFAULT '1' COMMENT '状态(0-禁用,1-启用)',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 社团表(club)
sql复制CREATE TABLE `club` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '社团名称',
`type` varchar(50) NOT NULL COMMENT '社团类型',
`description` text COMMENT '社团描述',
`logo` varchar(255) DEFAULT NULL COMMENT 'logo URL',
`founder_id` bigint NOT NULL COMMENT '创始人ID',
`found_time` datetime NOT NULL COMMENT '成立时间',
`status` tinyint DEFAULT '0' COMMENT '状态(0-待审核,1-已通过,2-已拒绝)',
PRIMARY KEY (`id`),
KEY `idx_founder` (`founder_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 申请表(application)
sql复制CREATE TABLE `application` (
`id` bigint NOT NULL AUTO_INCREMENT,
`club_id` bigint NOT NULL COMMENT '社团ID',
`applicant_id` bigint NOT NULL COMMENT '申请人ID',
`apply_type` tinyint NOT NULL COMMENT '申请类型(0-新社团,1-活动,2-经费)',
`title` varchar(200) NOT NULL COMMENT '申请标题',
`content` text NOT NULL COMMENT '申请内容',
`attachment` varchar(255) DEFAULT NULL COMMENT '附件URL',
`apply_time` datetime NOT NULL COMMENT '申请时间',
`status` tinyint DEFAULT '0' COMMENT '状态(0-待审核,1-已通过,2-已拒绝)',
PRIMARY KEY (`id`),
KEY `idx_club` (`club_id`),
KEY `idx_applicant` (`applicant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 审批记录表(approval)
sql复制CREATE TABLE `approval` (
`id` bigint NOT NULL AUTO_INCREMENT,
`application_id` bigint NOT NULL COMMENT '申请ID',
`approver_id` bigint NOT NULL COMMENT '审批人ID',
`result` tinyint NOT NULL COMMENT '审批结果(1-通过,2-拒绝)',
`comment` varchar(500) DEFAULT NULL COMMENT '审批意见',
`approval_time` datetime NOT NULL COMMENT '审批时间',
PRIMARY KEY (`id`),
KEY `idx_application` (`application_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 用户认证与授权
系统采用JWT(JSON Web Token)实现无状态认证,关键实现代码如下:
- JWT工具类
java复制public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public static boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private static boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
- Spring Security配置
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/teacher/**").hasRole("TEACHER")
.antMatchers("/api/student/**").hasRole("STUDENT")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.2 社团申报功能实现
前端申报表单关键代码(Vue 3 + Element Plus):
vue复制<template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-form-item label="申请类型" prop="applyType">
<el-radio-group v-model="form.applyType">
<el-radio :label="0">新社团申请</el-radio>
<el-radio :label="1">活动申请</el-radio>
<el-radio :label="2">经费申请</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="申请标题" prop="title">
<el-input v-model="form.title" placeholder="请输入申请标题"></el-input>
</el-form-item>
<el-form-item label="申请内容" prop="content">
<el-input
type="textarea"
v-model="form.content"
:rows="5"
placeholder="请详细描述申请内容"></el-input>
</el-form-item>
<el-form-item label="附件上传">
<el-upload
action="/api/upload"
:limit="3"
:on-success="handleUploadSuccess"
:file-list="fileList">
<el-button type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交申请</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { submitApplication } from '@/api/application'
export default {
setup() {
const formRef = ref(null)
const fileList = ref([])
const form = reactive({
applyType: 0,
title: '',
content: '',
attachment: ''
})
const rules = reactive({
applyType: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
title: [{ required: true, message: '请输入申请标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入申请内容', trigger: 'blur' }]
})
const handleUploadSuccess = (response) => {
form.attachment = response.data.url
}
const submitForm = () => {
formRef.value.validate(valid => {
if (valid) {
submitApplication(form).then(() => {
ElMessage.success('申请提交成功')
}).catch(err => {
ElMessage.error(err.message || '申请提交失败')
})
}
})
}
return {
formRef,
form,
rules,
fileList,
handleUploadSuccess,
submitForm
}
}
}
</script>
后端申报接口实现:
java复制@RestController
@RequestMapping("/api/application")
public class ApplicationController {
@Autowired
private ApplicationService applicationService;
@PostMapping
public ResponseEntity<?> submitApplication(@RequestBody ApplicationDTO dto,
@AuthenticationPrincipal UserDetails user) {
Application application = new Application();
BeanUtils.copyProperties(dto, application);
application.setApplicantId(getCurrentUserId(user));
application.setApplyTime(new Date());
application.setStatus(0); // 待审核
applicationService.save(application);
return ResponseEntity.ok().build();
}
private Long getCurrentUserId(UserDetails user) {
// 从数据库查询用户ID
return userService.findByUsername(user.getUsername()).getId();
}
}
3.3 审批流程实现
审批流程状态机设计:
java复制public enum ApplicationState {
PENDING(0, "待审核"),
APPROVED(1, "已通过"),
REJECTED(2, "已拒绝");
private final int code;
private final String desc;
ApplicationState(int code, String desc) {
this.code = code;
this.desc = desc;
}
// getters...
}
@Service
public class ApprovalService {
@Autowired
private ApplicationRepository applicationRepository;
@Autowired
private ApprovalRepository approvalRepository;
@Transactional
public void processApproval(Long applicationId, ApprovalDTO dto, UserDetails approver) {
Application application = applicationRepository.findById(applicationId)
.orElseThrow(() -> new ResourceNotFoundException("申请不存在"));
if (application.getStatus() != ApplicationState.PENDING.getCode()) {
throw new BusinessException("该申请已处理,不能重复审批");
}
// 更新申请状态
application.setStatus(dto.getResult());
applicationRepository.save(application);
// 创建审批记录
Approval approval = new Approval();
approval.setApplicationId(applicationId);
approval.setApproverId(getCurrentUserId(approver));
approval.setResult(dto.getResult());
approval.setComment(dto.getComment());
approval.setApprovalTime(new Date());
approvalRepository.save(approval);
// 发送通知
notifyApplicant(application.getApplicantId(), dto.getResult());
}
private void notifyApplicant(Long applicantId, int result) {
// 实现通知逻辑,可通过WebSocket或消息队列
}
}
4. 系统部署与测试
4.1 开发环境搭建
- 前端开发环境:
- Node.js 16+
- Vue CLI 5
- IDE推荐:VS Code 或 WebStorm
安装依赖:
bash复制npm install -g @vue/cli
vue create club-application-frontend
cd club-application-frontend
npm install element-plus axios vue-router
- 后端开发环境:
- JDK 17+
- Maven 3.8+
- MySQL 8.0
- Redis 6.0+
- IDE推荐:IntelliJ IDEA
4.2 生产环境部署
- 前端部署:
bash复制npm run build
# 生成的dist目录可部署到Nginx
Nginx配置示例:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /path/to/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
- 后端部署:
打包Spring Boot应用:
bash复制mvn clean package
java -jar target/club-application-backend.jar
推荐使用Docker部署:
dockerfile复制FROM openjdk:17-jdk-slim
COPY target/club-application-backend.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
4.3 系统测试要点
- 功能测试:
- 用户注册登录
- 社团创建与编辑
- 申请提交与修改
- 审批流程测试
- 通知功能验证
- 性能测试:
- 并发用户测试
- 响应时间测试
- 数据库压力测试
- 安全测试:
- SQL注入测试
- XSS攻击测试
- CSRF防护测试
- 权限越权测试
5. 项目扩展与优化方向
- 移动端适配:
- 开发微信小程序版本
- 响应式设计优化
- 工作流引擎集成:
- 集成Activiti或Flowable实现复杂审批流程
- 支持多级审批和会签
- 数据分析功能:
- 社团活跃度分析
- 审批效率统计
- 经费使用情况可视化
- 消息通知增强:
- 站内信
- 邮件通知
- 微信模板消息
- 微服务改造:
- 将系统拆分为用户服务、审批服务、通知服务等
- 使用Spring Cloud Alibaba实现服务治理
在实际开发过程中,有几个关键点需要注意:
- 审批流程的设计要足够灵活,以适应不同学校的规章制度
- 文件上传功能要做好安全防护,防止恶意文件上传
- 权限控制要细致,确保各角色只能访问自己有权限的功能
- 对于高并发场景,要考虑使用缓存和队列优化性能
这个系统我已经在实际环境中部署使用过,最大的经验是前期一定要做好需求调研,特别是审批流程的设计。不同学校的审批流程差异很大,最好做成可配置的。另外,文件管理也是一个容易出问题的地方,建议使用单独的文件服务,而不是直接存储在应用服务器上。
