Files
hzdyjs/docs/需求文档/社团管理需求/社团管理技术架构设计.md

30 KiB
Raw Permalink Blame History

社团管理系统技术架构设计

1. 技术架构总览

1.1 整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   前端Web应用   │    │   移动端应用    │    │   管理后台      │
│   Vue 3 + TS   │    │   uni-app      │    │   Element Plus │
└─────────────────┘    └─────────────────┘    └─────────────────┘
          │                       │                       │
          └───────────────────────┼───────────────────────┘
                                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                        API网关 + 认证中心                        │
│                     Spring Security + Sa-Token                │
└─────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                        社团管理微服务                            │
│                     Spring Boot 3.4.6                        │
│                      MyBatis-Plus                             │
└─────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                         数据持久层                               │
│                    MySQL 8.0 + Redis                         │
│                      文件存储 + 消息队列                         │
└─────────────────────────────────────────────────────────────────┘

1.2 模块化设计

  • stgl-club-management: 社团管理核心模块
  • stgl-activity-management: 活动管理模块
  • stgl-attendance-management: 考勤签到模块
  • stgl-material-management: 材料收集模块
  • stgl-integration: 系统集成模块

2. 数据库设计方案

2.1 数据库架构

  • 主数据库: MySQL 8.0,存储业务数据
  • 缓存数据库: Redis,存储会话、缓存数据
  • 分库分表策略: 按租户分库,大表按时间分表

2.2 核心表结构设计

社团信息表(stgl_club)

CREATE TABLE `stgl_club` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
  `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
  
  `club_name` varchar(100) NOT NULL COMMENT '社团名称',
  `club_type` varchar(50) NOT NULL COMMENT '社团类型',
  `establish_date` date COMMENT '成立日期',
  `description` text COMMENT '社团简介',
  `email` varchar(100) COMMENT '邮箱',
  `phone` varchar(20) COMMENT '电话',
  `logo_url` varchar(500) COMMENT 'logo图片路径',
  
  `president_name` varchar(50) COMMENT '社长姓名',
  `president_student_no` varchar(20) COMMENT '社长学号',
  `president_dept` varchar(100) COMMENT '社长院系',
  `president_major` varchar(100) COMMENT '社长专业',
  `president_class` varchar(100) COMMENT '社长班级',
  `president_phone` varchar(20) COMMENT '社长联系方式',
  `vice_president_name` varchar(50) COMMENT '副社长姓名',
  `vice_president_student_no` varchar(20) COMMENT '副社长学号',
  `advisor_name` varchar(50) COMMENT '指导老师姓名',
  `advisor_dept` varchar(100) COMMENT '指导老师所属院系',
  
  `member_limit` int DEFAULT 0 COMMENT '成员规模上限',
  `departments` text COMMENT '组织架构',
  `recruit_cycle` varchar(50) COMMENT '招新周期',
  `recruit_time` varchar(50) COMMENT '招新时间',
  `activity_frequency` varchar(50) COMMENT '活动频率',
  `funding_sources` varchar(200) COMMENT '经费来源',
  `charter_url` varchar(500) COMMENT '章程文档路径',
  `honors` text COMMENT '历史荣誉',
  `status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-停用)',
  
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_club_name` (`club_name`, `tenant_id`),
  KEY `idx_club_type` (`club_type`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_tenant_deleted` (`tenant_id`, `deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='社团信息表';

活动信息表(stgl_activity)

CREATE TABLE `stgl_activity` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
  `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
  
  `activity_name` varchar(100) NOT NULL COMMENT '活动名称',
  `activity_type` varchar(50) NOT NULL COMMENT '活动类型',
  `activity_theme` varchar(100) COMMENT '活动主题',
  `start_time` datetime COMMENT '开始时间',
  `end_time` datetime COMMENT '结束时间',
  `location` varchar(200) COMMENT '活动地点',
  `description` text COMMENT '活动简介',
  `poster_url` varchar(500) COMMENT '活动海报路径',
  `related_club_id` bigint(20) COMMENT '关联社团ID',
  
  `organizer_name` varchar(100) COMMENT '主办方名称',
  `organizer_type` varchar(50) COMMENT '主办方类型',
  `organizer_contact` varchar(100) COMMENT '主办方联系人',
  `organizer_phone` varchar(20) COMMENT '主办方电话',
  `co_organizers` json COMMENT '承办方信息',
  `instructors` json COMMENT '指导老师列表',
  
  `registration_type` tinyint COMMENT '报名方式(1-个人 2-团队 3-均可)',
  `registration_start` datetime COMMENT '报名开始时间',
  `registration_end` datetime COMMENT '报名结束时间',
  `registration_status` tinyint COMMENT '报名状态',
  `max_participants` int COMMENT '总人数上限',
  `max_teams` int COMMENT '团队数量上限',
  `team_size_min` int COMMENT '每队最小人数',
  `team_size_max` int COMMENT '每队最大人数',
  `need_approval` tinyint DEFAULT 0 COMMENT '是否需要审核',
  
  `activity_status` varchar(20) COMMENT '活动状态',
  `is_finished` tinyint DEFAULT 0 COMMENT '是否为已办活动',
  `summary` text COMMENT '活动总结报告',
  `achievements` text COMMENT '活动成果',
  `import_source` varchar(200) COMMENT '导入来源信息',
  
  PRIMARY KEY (`id`),
  KEY `idx_activity_name` (`activity_name`),
  KEY `idx_activity_type` (`activity_type`),
  KEY `idx_start_time` (`start_time`),
  KEY `idx_end_time` (`end_time`),
  KEY `idx_status` (`activity_status`),
  KEY `idx_club_id` (`related_club_id`),
  KEY `idx_tenant_deleted` (`tenant_id`, `deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动信息表';

2.3 索引优化策略

  • 主键索引: 所有表使用雪花ID作为主键
  • 唯一索引: 社团名称、活动名称等业务唯一字段
  • 复合索引: tenant_id + deleted(软删除 + 多租户)
  • 时间索引: 创建时间、活动时间等时间字段
  • 状态索引: 活动状态、报名状态等枚举字段

3. 后端服务架构

3.1 分层架构设计

Controller层 (REST API)
├── ClubController          # 社团管理接口
├── ActivityController      # 活动管理接口
├── AttendanceController    # 考勤管理接口
└── MaterialController      # 材料收集接口

Service层 (业务逻辑)
├── ClubService            # 社团业务服务
├── ActivityService        # 活动业务服务
├── AttendanceService      # 考勤业务服务
└── MaterialService        # 材料收集服务

Mapper层 (数据访问)
├── ClubMapper             # 社团数据访问
├── ActivityMapper         # 活动数据访问
├── AttendanceMapper       # 考勤数据访问
└── MaterialMapper         # 材料数据访问

Domain层 (实体模型)
├── entity/                # 数据库实体
├── vo/                    # 视图对象
└── dto/                   # 数据传输对象

3.2 核心服务设计

社团管理服务 (ClubService)

@Service
public interface ClubService {
    // 基础CRUD
    R<ClubVo> getClubById(Long id);
    R<List<ClubVo>> getClubList(ClubQueryDto queryDto);
    R<Void> createClub(ClubCreateDto createDto);
    R<Void> updateClub(ClubUpdateDto updateDto);
    R<Void> deleteClub(Long id);
    
    // 成员管理
    R<List<MemberVo>> getClubMembers(Long clubId);
    R<Void> addMember(MemberAddDto addDto);
    R<Void> removeMember(Long clubId, Long userId);
    R<Void> importMembers(Long clubId, MultipartFile file);
    
    // 权限管理
    R<Void> assignRole(Long clubId, Long userId, Integer roleType);
    R<List<PermissionVo>> getClubPermissions(Long clubId);
}

活动管理服务 (ActivityService)

@Service  
public interface ActivityService {
    // 活动CRUD
    R<ActivityVo> getActivityById(Long id);
    R<TableDataInfo> getActivityList(ActivityQueryDto queryDto);
    R<Void> createActivity(ActivityCreateDto createDto);
    R<Void> updateActivity(ActivityUpdateDto updateDto);
    R<Void> deleteActivity(Long id);
    R<Void> copyActivity(Long sourceId, ActivityCreateDto createDto);
    
    // 报名管理
    R<List<RegistrationVo>> getRegistrations(Long activityId);
    R<Void> approveRegistration(Long registrationId, String remark);
    R<Void> rejectRegistration(Long registrationId, String remark);
    
    // 评分管理
    R<List<RatingVo>> getActivityRatings(Long activityId);
    R<ActivityStatVo> getActivityStats(Long activityId);
}

3.3 API接口设计规范

统一响应格式

// 分页查询响应
@Data
public class TableDataInfo {
    private long total;
    private List<Object> rows;
    private int code;
    private String msg;
}

// 普通响应
@Data  
public class R<T> {
    private int code;
    private String msg;
    private T data;
    
    public static <T> R<T> ok() { ... }
    public static <T> R<T> ok(T data) { ... }
    public static <T> R<T> fail(String msg) { ... }
}

接口URL规范

GET    /api/stgl/club/list                    # 社团列表
GET    /api/stgl/club/{id}                    # 社团详情
POST   /api/stgl/club                        # 创建社团
PUT    /api/stgl/club/{id}                   # 更新社团
DELETE /api/stgl/club/{id}                   # 删除社团

GET    /api/stgl/activity/list               # 活动列表  
POST   /api/stgl/activity                    # 创建活动
PUT    /api/stgl/activity/{id}               # 更新活动
POST   /api/stgl/activity/{id}/copy          # 复制活动

GET    /api/stgl/attendance/list             # 考勤列表
POST   /api/stgl/attendance/task             # 创建考勤任务
POST   /api/stgl/attendance/checkin          # 签到接口

GET    /api/stgl/material/list               # 材料列表
POST   /api/stgl/material/task               # 创建收集任务
POST   /api/stgl/material/submit             # 提交材料

4. 前端架构设计

4.1 Vue 3 + TypeScript 架构

src/
├── api/                    # API接口定义
│   ├── club.ts            # 社团API
│   ├── activity.ts        # 活动API
│   └── attendance.ts      # 考勤API
├── views/                  # 页面组件
│   ├── club/              # 社团管理页面
│   ├── activity/          # 活动管理页面
│   └── attendance/        # 考勤管理页面
├── components/            # 通用组件
│   ├── ClubSelector/      # 社团选择器
│   ├── ActivityCard/      # 活动卡片
│   └── AttendanceMap/     # 签到地图
├── stores/                # 状态管理
│   ├── club.ts           # 社团状态
│   └── activity.ts       # 活动状态
├── types/                 # TypeScript类型定义
│   ├── club.ts           # 社团类型
│   └── activity.ts       # 活动类型
└── utils/                 # 工具函数
    ├── request.ts        # HTTP请求
    └── common.ts         # 通用工具

4.2 核心组件设计

社团管理组件

<!-- ClubManagement.vue -->
<template>
  <div class="club-management">
    <el-card>
      <template #header>
        <div class="card-header">
          <span>社团管理</span>
          <el-button type="primary" @click="handleCreate">新增社团</el-button>
        </div>
      </template>
      
      <!-- 查询条件 -->
      <el-form :model="queryForm" inline>
        <el-form-item label="社团名称">
          <el-input v-model="queryForm.clubName" placeholder="请输入社团名称" />
        </el-form-item>
        <el-form-item label="社团类型">
          <el-select v-model="queryForm.clubType" placeholder="请选择">
            <el-option v-for="item in clubTypes" :key="item.value" 
                       :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">查询</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
      
      <!-- 数据表格 -->
      <el-table :data="tableData" style="width: 100%">
        <el-table-column prop="clubName" label="社团名称" />
        <el-table-column prop="clubType" label="社团类型" />
        <el-table-column prop="presidentName" label="社长" />
        <el-table-column prop="memberCount" label="成员数量" />
        <el-table-column prop="status" label="状态">
          <template #default="scope">
            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
              {{ scope.row.status === 1 ? '正常' : '停用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200">
          <template #default="scope">
            <el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button type="success" size="small" @click="handleMembers(scope.row)">成员</el-button>
            <el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      
      <!-- 分页 -->
      <el-pagination
        v-model:current-page="pagination.current"
        v-model:page-size="pagination.size"
        :total="pagination.total"
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleQuery"
        @current-change="handleQuery"
      />
    </el-card>
    
    <!-- 新增/编辑对话框 -->
    <ClubDialog v-model:visible="dialogVisible" :club-id="currentClubId" @success="handleQuery" />
  </div>
</template>

活动管理组件

<!-- ActivityManagement.vue -->
<template>
  <div class="activity-management">
    <!-- 筛选区域 -->
    <el-card class="filter-card">
      <el-form :model="queryForm" inline>
        <el-form-item label="活动名称">
          <el-input v-model="queryForm.activityName" placeholder="请输入活动名称" />
        </el-form-item>
        <el-form-item label="活动类型">
          <el-select v-model="queryForm.activityType" placeholder="请选择">
            <el-option v-for="item in activityTypes" :key="item.value" 
                       :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="活动状态">
          <el-select v-model="queryForm.activityStatus" placeholder="请选择">
            <el-option v-for="item in activityStatuses" :key="item.value" 
                       :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="活动时间">
          <el-date-picker
            v-model="queryForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">查询</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    
    <!-- 活动列表 -->
    <el-card>
      <template #header>
        <div class="card-header">
          <span>活动列表</span>
          <div>
            <el-button type="primary" @click="handleCreate">新增活动</el-button>
            <el-button type="success" @click="handleExport">导出</el-button>
          </div>
        </div>
      </template>
      
      <!-- 活动卡片 -->
      <div class="activity-grid">
        <ActivityCard 
          v-for="activity in activityList" 
          :key="activity.id"
          :activity="activity"
          @edit="handleEdit"
          @copy="handleCopy"
          @delete="handleDelete"
          @view-registration="handleViewRegistration"
        />
      </div>
      
      <!-- 分页 -->
      <el-pagination
        v-model:current-page="pagination.current"
        v-model:page-size="pagination.size"
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleQuery"
        @current-change="handleQuery"
      />
    </el-card>
  </div>
</template>

4.3 状态管理设计

Pinia Store 设计

// stores/club.ts
export const useClubStore = defineStore('club', {
  state: () => ({
    clubList: [] as Club[],
    currentClub: null as Club | null,
    loading: false,
    clubTypes: [
      { label: '学术科技类', value: '学术科技类' },
      { label: '文化艺术类', value: '文化艺术类' },
      { label: '体育健身类', value: '体育健身类' },
      { label: '实践技能类', value: '实践技能类' },
      { label: '兴趣爱好类', value: '兴趣爱好类' },
      { label: '思想政治类', value: '思想政治类' }
    ]
  }),
  
  actions: {
    async fetchClubList(params: ClubQueryParams) {
      this.loading = true
      try {
        const response = await getClubList(params)
        this.clubList = response.data.rows
        return response.data
      } finally {
        this.loading = false
      }
    },
    
    async createClub(club: ClubCreateDto) {
      const response = await createClub(club)
      if (response.code === 200) {
        ElMessage.success('创建成功')
        await this.fetchClubList({})
      }
      return response
    }
  }
})

5. 移动端架构设计

5.1 uni-app + uview-plus 架构

pages/
├── club/              # 社团页面
│   ├── list.vue      # 社团列表
│   ├── detail.vue    # 社团详情
│   └── join.vue      # 加入社团
├── activity/          # 活动页面
│   ├── list.vue      # 活动列表
│   ├── detail.vue    # 活动详情
│   └── register.vue  # 活动报名
└── attendance/        # 考勤页面
    ├── list.vue      # 考勤列表
    └── checkin.vue   # 签到页面

components/
├── club-card/        # 社团卡片组件
├── activity-card/    # 活动卡片组件
└── map-checkin/      # 地图签到组件

utils/
├── request.js        # 网络请求
├── location.js       # 地理位置
└── storage.js        # 本地存储

5.2 地理位置签到组件

<!-- components/map-checkin/index.vue -->
<template>
  <view class="map-checkin">
    <map
      :latitude="location.latitude"
      :longitude="location.longitude"
      :markers="markers"
      :circles="circles"
      show-location
      @tap="onMapTap"
    />
    
    <view class="checkin-info">
      <view class="distance">距离签到点: {{ distance }}米</view>
      <view class="address">当前位置: {{ address }}</view>
    </view>
    
    <view class="checkin-actions">
      <u-button 
        type="primary" 
        size="large"
        :disabled="!canCheckin"
        @click="handleCheckin"
      >
        {{ canCheckin ? '签到' : '超出签到范围' }}
      </u-button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      location: { latitude: 0, longitude: 0 },
      targetLocation: { latitude: 0, longitude: 0 },
      distance: 0,
      address: '',
      canCheckin: false,
      allowRange: 500 // 允许签到范围(米)
    }
  },
  
  onLoad() {
    this.getCurrentLocation()
  },
  
  methods: {
    async getCurrentLocation() {
      try {
        const res = await uni.getLocation({
          type: 'gcj02'
        })
        
        this.location = {
          latitude: res.latitude,
          longitude: res.longitude
        }
        
        this.calculateDistance()
        this.getAddress()
      } catch (error) {
        uni.showToast({
          title: '获取位置失败',
          icon: 'error'
        })
      }
    },
    
    calculateDistance() {
      // 计算两点间距离
      const distance = this.getDistance(
        this.location.latitude,
        this.location.longitude,
        this.targetLocation.latitude,
        this.targetLocation.longitude
      )
      
      this.distance = Math.round(distance)
      this.canCheckin = distance <= this.allowRange
    },
    
    async handleCheckin() {
      if (!this.canCheckin) {
        uni.showToast({
          title: '超出签到范围',
          icon: 'error'
        })
        return
      }
      
      try {
        const response = await this.$request.post('/api/stgl/attendance/checkin', {
          taskId: this.taskId,
          latitude: this.location.latitude,
          longitude: this.location.longitude,
          address: this.address
        })
        
        if (response.code === 200) {
          uni.showToast({
            title: '签到成功',
            icon: 'success'
          })
          
          setTimeout(() => {
            uni.navigateBack()
          }, 2000)
        }
      } catch (error) {
        uni.showToast({
          title: '签到失败',
          icon: 'error'
        })
      }
    }
  }
}
</script>

6. 系统集成方案

6.1 学生信息系统集成

@Component
public class StudentInfoIntegration {
    
    @Autowired
    private SysUserMapper userMapper;
    
    @Autowired  
    private XgStudentMapper studentMapper;
    
    @Autowired
    private XgClassMapper classMapper;
    
    /**
     * 获取学生信息
     */
    public StudentVo getStudentInfo(String studentNo) {
        // 从用户表获取基础信息
        SysUser user = userMapper.selectByUserName(studentNo);
        if (user == null) {
            throw new ServiceException("用户不存在");
        }
        
        // 从学生表获取详细信息
        XgStudent student = studentMapper.selectByStudentNo(studentNo);
        if (student == null) {
            throw new ServiceException("学生信息不存在");
        }
        
        // 从班级表获取班级信息
        XgClass classInfo = classMapper.selectById(student.getClassId());
        
        // 组装返回数据
        StudentVo studentVo = new StudentVo();
        BeanUtils.copyProperties(user, studentVo);
        BeanUtils.copyProperties(student, studentVo);
        if (classInfo != null) {
            studentVo.setClassName(classInfo.getClassName());
            studentVo.setMajorName(classInfo.getMajorName());
        }
        
        return studentVo;
    }
    
    /**
     * 批量获取班级学生
     */
    public List<StudentVo> getClassStudents(Long classId) {
        List<XgStudent> students = studentMapper.selectByClassId(classId);
        return students.stream()
                .map(student -> getStudentInfo(student.getStudentNo()))
                .collect(Collectors.toList());
    }
}

6.2 通知系统集成

@Component
public class NotificationIntegration {
    
    @Autowired
    private MessageService messageService;
    
    /**
     * 发送活动通知
     */
    public void sendActivityNotification(Long activityId, List<Long> userIds, String type) {
        Activity activity = activityService.getById(activityId);
        
        String title;
        String content;
        
        switch (type) {
            case "CREATE":
                title = "新活动通知";
                content = String.format("【%s】活动已发布,报名时间:%s - %s", 
                    activity.getActivityName(),
                    DateUtils.formatDateTime(activity.getRegistrationStart()),
                    DateUtils.formatDateTime(activity.getRegistrationEnd()));
                break;
            case "APPROVAL":
                title = "报名审核通知";
                content = String.format("您报名的【%s】活动审核已通过", activity.getActivityName());
                break;
            case "CHECKIN":
                title = "签到提醒";
                content = String.format("【%s】活动签到已开始,请及时签到", activity.getActivityName());
                break;
            default:
                return;
        }
        
        // 发送消息
        MessageDto messageDto = new MessageDto();
        messageDto.setTitle(title);
        messageDto.setContent(content);
        messageDto.setType("SYSTEM");
        messageDto.setUserIds(userIds);
        
        messageService.sendMessage(messageDto);
    }
}

7. 性能优化方案

7.1 数据库优化

  • 分页查询优化: 使用覆盖索引避免回表
  • 批量操作优化: 使用MyBatis批量插入/更新
  • 慢查询监控: 启用MySQL慢查询日志
  • 连接池优化: 合理配置HikariCP参数

7.2 缓存策略

@Service
public class ClubService {
    
    @Cacheable(value = "club", key = "#id")
    public ClubVo getClubById(Long id) {
        // 查询数据库
    }
    
    @CacheEvict(value = "club", key = "#clubDto.id")
    public void updateClub(ClubUpdateDto clubDto) {
        // 更新数据库
    }
    
    @Cacheable(value = "club:list", key = "#queryDto.hashCode()")
    public List<ClubVo> getClubList(ClubQueryDto queryDto) {
        // 查询数据库
    }
}

7.3 前端性能优化

  • 组件懒加载: 使用Vue 3的defineAsyncComponent
  • 图片懒加载: 活动海报、社团logo延迟加载
  • 虚拟滚动: 大列表使用虚拟滚动组件
  • 资源预加载: 关键路由和组件预加载

8. 安全设计方案

8.1 认证授权

@RestController
@RequestMapping("/api/stgl/club")
public class ClubController {
    
    @GetMapping("/list")
    @SaCheckPermission("stgl:club:list")
    public TableDataInfo list(ClubQueryDto queryDto) {
        // 权限检查通过后执行业务逻辑
    }
    
    @PostMapping
    @SaCheckPermission("stgl:club:create")
    public R<Void> create(@Valid @RequestBody ClubCreateDto createDto) {
        // 创建社团
    }
}

8.2 数据权限

@Component
public class DataPermissionHandler {
    
    /**
     * 根据用户权限过滤数据
     */
    public void filterDataByPermission(QueryWrapper<?> queryWrapper) {
        Long userId = SecurityUtils.getUserId();
        String userType = SecurityUtils.getUserType();
        
        if ("student".equals(userType)) {
            // 学生只能看到自己参与的社团/活动
            queryWrapper.eq("create_by", userId)
                       .or()
                       .exists("SELECT 1 FROM stgl_member WHERE relation_id = stgl_club.id AND user_id = " + userId);
        } else if ("teacher".equals(userType)) {
            // 老师可以看到自己指导的社团/活动
            queryWrapper.like("advisor_name", SecurityUtils.getUsername());
        }
        // 管理员可以看到所有数据,不需要额外过滤
    }
}

9. 部署方案

9.1 Docker容器化

# Dockerfile for Backend
FROM openjdk:17-jdk-alpine
VOLUME /tmp
COPY target/club-management.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

# Dockerfile for Frontend
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=0 /app/dist /usr/share/nginx/html

9.2 监控方案

  • 应用监控: Spring Boot Actuator + Micrometer
  • 日志监控: ELK Stack (Elasticsearch + Logstash + Kibana)
  • 性能监控: Prometheus + Grafana
  • 错误追踪: Sentry或自建错误收集系统

这个技术架构设计为社团管理系统的开发提供了完整的技术指导,涵盖了从数据库设计到前后端架构、系统集成、性能优化等各个方面。请根据实际项目需求进行适当调整。