30 KiB
30 KiB
社团管理系统技术架构设计
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或自建错误收集系统
这个技术架构设计为社团管理系统的开发提供了完整的技术指导,涵盖了从数据库设计到前后端架构、系统集成、性能优化等各个方面。请根据实际项目需求进行适当调整。