当前位置:首页 > 技术 > 正文内容

基于Spring Boot与Vue的高校社团管理平台架构设计与实现

访客 技术 2026年9月16日 10

项目概述

随着高校信息化建设的深入,传统的人工社团管理模式已无法满足日益增长的数据处理需求。信息孤岛、数据冗余以及审批流程繁琐等问题凸显。为此,设计并实现一套现代化的社团管理平台至关重要。该平台旨在通过数字化手段重塑社团管理流程,实现学生、团长与管理员之间的高效协同,提升信息流转效率与数据安全性。

技术栈选型

本系统采用前后端分离的架构设计,以确保系统的高内聚、低耦合及良好的可扩展性。

  • 后端框架:选用 Spring Boot。其"约定优于配置"的核心理念大幅简化了 Spring 应用的初始搭建与开发过程。通过自动配置和内嵌服务器,开发者可专注于业务逻辑的实现。同时,结合 MyBatis-Plus 作为持久层框架,提升数据库操作效率。
  • 前端框架:采用 Vue.js 配合 Element UI 构建单页面应用(SPA)。Vue 的组件化开发模式和响应式数据绑定机制,使得复杂交互界面的开发更加高效,同时保证了用户体验的流畅性。
  • 数据库:选用 MySQL 关系型数据库。其体积小、速度快、开源免费的特点,非常适合中小型Web应用的数据存储需求,且能完美支持复杂的关联查询与事务处理。

系统需求与业务流程

在系统开发前期,对核心业务流程进行了梳理与建模,确保系统逻辑严密且符合用户操作直觉。

核心操作流程

系统采用基于 Token 的身份认证机制。用户访问受保护资源时,需先通过登录接口获取凭证,后续请求携带该凭证进行鉴权。

系统操作流程图

登录鉴权流程

登录模块负责校验用户身份。系统接收客户端提交的凭证,与数据库中的哈希密码进行比对,校验通过后生成 JWT Token 并返回。

登录鉴权流程图

数据生命周期管理

系统对核心数据(如社团信息、活动申请)实行严格的增删改查及逻辑删除机制,确保数据的可追溯性与安全性。

数据删除流程图 数据添加流程图

数据库模型设计

数据库设计遵循第三范式,减少数据冗余。以下为核心实体的 E-R 模型及物理表结构设计。

核心实体关系

学生实体属性图 社团信息实体属性图 活动信息实体属性图

物理表结构

表1:系统用户表 (sys_user)
字段名数据类型约束描述
idBIGINTPK, Auto Increment主键ID
accountVARCHAR(50)Unique, Not Null登录账号
credentialVARCHAR(100)Not Null加密密码
role_codeVARCHAR(20)Not Null角色标识
created_atDATETIMEDefault CURRENT_TIMESTAMP创建时间

表2:社团信息表 (club_info)
字段名数据类型约束描述
idBIGINTPK, Auto Increment主键ID
club_codeVARCHAR(32)Unique社团编号
club_nameVARCHAR(100)Not Null社团名称
category_idINTFK社团分类ID
leader_idBIGINTFK团长用户ID
is_deletedTINYINTDefault 0逻辑删除标记

核心模块代码实现

以下展示系统核心模块的后端接口与前端组件实现。代码经过重构,采用了更现代的命名规范与设计模式。

1. 身份认证控制器 (AuthController)

负责处理用户的登录、注册及会话管理,采用 RESTful 风格设计。

package com.clubmanage.controller;

import com.clubmanage.common.ApiResponse;
import com.clubmanage.dto.LoginRequest;
import com.clubmanage.entity.SysUser;
import com.clubmanage.service.AccountService;
import com.clubmanage.util.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {

    @Autowired
    private AccountService accountService;

    @Autowired
    private JwtTokenProvider tokenProvider;

    @PostMapping("/login")
    public ApiResponse<?> authenticateUser(@RequestBody LoginRequest request) {
        SysUser user = accountService.findByAccount(request.getAccount());
        if (user == null || !accountService.verifyPassword(request.getCredential(), user.getCredential())) {
            return ApiResponse.error(401, "账号或密码错误");
        }
        
        String token = tokenProvider.generateToken(user.getId(), user.getAccount(), user.getRoleCode());
        return ApiResponse.success("登录成功")
                .put("accessToken", token)
                .put("role", user.getRoleCode());
    }

    @PostMapping("/register")
    public ApiResponse<?> registerUser(@RequestBody SysUser newUser) {
        if (accountService.existsByAccount(newUser.getAccount())) {
            return ApiResponse.error(400, "该账号已被注册");
        }
        accountService.createUser(newUser);
        return ApiResponse.success("注册成功");
    }

    @PostMapping("/logout")
    public ApiResponse<?> invalidateSession(@RequestHeader("Authorization") String token) {
        tokenProvider.blacklistToken(token);
        return ApiResponse.success("已安全退出");
    }
}

2. Web MVC 拦截器配置 (WebMvcConfig)

通过实现 WebMvcConfigurer 接口,优雅地注册自定义拦截器并配置静态资源映射,避免覆盖 Spring Boot 的默认自动配置。

package com.clubmanage.config;

import com.clubmanage.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/v1/auth/**", "/static/**", "/error");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**")
                .addResourceLocations("classpath:/static/assets/")
                .addResourceLocations("classpath:/public/uploads/");
    }
}

3. 公告管理业务控制器 (AnnouncementController)

使用 MyBatis-Plus 的 LambdaQueryWrapper 构建类型安全的查询条件,实现公告的分页查询、详情获取及逻辑删除。

package com.clubmanage.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clubmanage.common.ApiResponse;
import com.clubmanage.entity.Announcement;
import com.clubmanage.service.AnnouncementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/announcements")
public class AnnouncementController {

    @Autowired
    private AnnouncementService announcementService;

    @GetMapping("/page")
    public ApiResponse<Page<Announcement>> getPageData(@RequestParam Map<String, Object> params) {
        int current = Integer.parseInt(params.getOrDefault("page", "1").toString());
        int size = Integer.parseInt(params.getOrDefault("limit", "10").toString());
        
        LambdaQueryWrapper<Announcement> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Announcement::getIsDeleted, 0);
        
        if (params.containsKey("title")) {
            wrapper.like(Announcement::getTitle, params.get("title"));
        }
        wrapper.orderByDesc(Announcement::getCreatedAt);

        Page<Announcement> pageResult = announcementService.page(new Page<>(current, size), wrapper);
        return ApiResponse.success(pageResult);
    }

    @PostMapping
    public ApiResponse<?> createAnnouncement(@RequestBody Announcement announcement) {
        announcement.setCreatedAt(new Date());
        announcement.setIsDeleted(0);
        announcementService.save(announcement);
        return ApiResponse.success("发布成功");
    }

    @DeleteMapping("/{id}")
    public ApiResponse<?> removeAnnouncement(@PathVariable Long id) {
        Announcement entity = new Announcement();
        entity.setId(id);
        entity.setIsDeleted(1); // 逻辑删除
        announcementService.updateById(entity);
        return ApiResponse.success("删除成功");
    }
}

4. 前端递归菜单组件 (RecursiveMenu.vue)

采用 Vue 3 Composition API 重构侧边栏菜单组件,利用递归调用实现无限层级的动态路由菜单渲染。

<template>
  <el-sub-menu v-if="node.children && node.children.length" :index="String(node.id)">
    <template #title>
      <el-icon v-if="node.icon"><component :is="node.icon" /></el-icon>
      <span>{{ node.label }}</span>
    </template>
    <recursive-menu 
      v-for="child in node.children" 
      :key="child.id" 
      :node="child" 
    />
  </el-sub-menu>
  
  <el-menu-item v-else :index="String(node.id)" @click="handleRouteJump(node)">
    <el-icon v-if="node.icon"><component :is="node.icon" /></el-icon>
    <span>{{ node.label }}</span>
  </el-menu-item>
</template>

<script setup>
import { useRouter } from 'vue-router';

const props = defineProps({
  node: {
    type: Object,
    required: true
  }
});

const router = useRouter();

const handleRouteJump = (menuNode) => {
  if (menuNode.routePath) {
    router.push({ path: menuNode.routePath });
  }
};
</script>

<style scoped>
/* 菜单样式微调 */
</style>

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。