项目简介
校园社团活动展示平台是一个面向高校社团组织的综合管理系统。该平台旨在为校园社团提供活动展示、成员管理、活动报名等功能,提升社团管理效率,促进校园文化建设。
技术栈
后端
- Spring Boot 2.7.0
- Spring Security
- MyBatis Plus
- MySQL 8.0
- Redis
- JWT
前端
- Vue 3
- Element Plus
- Axios
- Vuex
- Vue Router
核心功能
1. 用户管理
- 用户注册/登录
- 角色权限控制(管理员、社团负责人、普通用户)
- 个人信息管理
2. 社团管理
- 社团信息展示
- 社团成员管理
- 社团活动发布
- 社团公告管理
3. 活动管理
- 活动信息发布
- 活动报名
- 活动签到
- 活动评论与互动
- 活动数据统计
4. 信息展示
- 首页轮播展示
- 活动日历
- 社团风采展示
- 活动照片墙
数据库设计
用户表(user)
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) COMMENT '真实姓名',`phone` varchar(20) COMMENT '手机号',`email` varchar(100) COMMENT '邮箱',`avatar` varchar(200) COMMENT '头像',`role` tinyint NOT NULL COMMENT '角色:0-管理员,1-社团负责人,2-普通用户',`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
社团表(club)
CREATE TABLE `club` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(100) NOT NULL COMMENT '社团名称',`description` text COMMENT '社团简介',`logo` varchar(200) COMMENT '社团logo',`leader_id` bigint COMMENT '负责人id',`member_count` int DEFAULT 0 COMMENT '成员数量',`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
活动表(activity)
CREATE TABLE `activity` (`id` bigint NOT NULL AUTO_INCREMENT,`title` varchar(200) NOT NULL COMMENT '活动标题',`content` text COMMENT '活动内容',`club_id` bigint NOT NULL COMMENT '所属社团id',`start_time` datetime COMMENT '开始时间',`end_time` datetime COMMENT '结束时间',`location` varchar(200) COMMENT '活动地点',`max_number` int DEFAULT 0 COMMENT '最大参与人数',`sign_up_count` int DEFAULT 0 COMMENT '报名人数',`status` tinyint DEFAULT 0 COMMENT '状态:0-未开始,1-进行中,2-已结束',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心接口设计
1. 用户相关接口
@RestController
@RequestMapping("/api/user")
public class UserController {// 用户注册@PostMapping("/register")public Result register(@RequestBody UserRegisterDTO dto) {// ...}// 用户登录@PostMapping("/login") public Result login(@RequestBody UserLoginDTO dto) {// ...}// 获取用户信息@GetMapping("/info")public Result getUserInfo() {// ...}
}
2. 社团相关接口
@RestController
@RequestMapping("/api/club")
public class ClubController {// 创建社团@PostMappingpublic Result createClub(@RequestBody ClubDTO dto) {// ...}// 获取社团列表@GetMapping("/list")public Result getClubList(ClubQuery query) {// ...}// 获取社团详情@GetMapping("/{id}")public Result getClubDetail(@PathVariable Long id) {// ...}
}
3. 活动相关接口
@RestController
@RequestMapping("/api/activity")
public class ActivityController {// 发布活动@PostMappingpublic Result publishActivity(@RequestBody ActivityDTO dto) {// ...}// 活动报名@PostMapping("/sign-up/{id}")public Result signUp(@PathVariable Long id) {// ...}// 获取活动列表@GetMapping("/list")public Result getActivityList(ActivityQuery query) {// ...}
}
项目亮点
- 权限控制
- 基于RBAC模型实现细粒度的权限控制
- 使用JWT实现无状态的身份认证
- 结合Redis实现token刷新机制
- 性能优化
- Redis缓存热点数据
- MyBatis Plus分页插件
- 图片云存储与CDN加速
- 安全性
- XSS防御
- SQL注入防护
- 密码加密存储
- 接口频率限制
- 可扩展性
- 模块化设计
- 统一响应处理
- 全局异常处理
- 完善的日志系统
项目总结
本项目采用主流的Java全栈技术栈,实现了一个功能完整的校园社团活动展示平台。通过该项目,不仅能够帮助校园社团提升管理效率,也能够为学生提供更好的社团活动体验。
在开发过程中,注重代码质量和项目架构,采用了多项优化措施来提升系统性能和安全性。同时项目具有良好的可扩展性,为后续功能迭代提供了便利。
后续优化方向
- 引入消息队列处理异步任务
- 添加实时通知功能
- 优化移动端适配
- 引入微服务架构
- 添加数据分析功能
- 优化搜索功能,引入Elasticsearch
Java全栈项目详解 - 校园社团平台的用户与社团管理模块
一、用户管理模块
1. 数据库设计
用户表(user)
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) COMMENT '真实姓名',`student_id` varchar(20) COMMENT '学号',`phone` varchar(20) COMMENT '手机号',`email` varchar(100) COMMENT '邮箱',`avatar` varchar(200) COMMENT '头像',`role` tinyint NOT NULL COMMENT '角色:0-管理员,1-社团负责人,2-普通用户',`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',`last_login_time` datetime COMMENT '最后登录时间',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`),UNIQUE KEY `uk_student_id` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
角色权限表(role_permission)
CREATE TABLE `role_permission` (`id` bigint NOT NULL AUTO_INCREMENT,`role` tinyint NOT NULL COMMENT '角色',`permission` varchar(100) NOT NULL COMMENT '权限标识',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_role_permission` (`role`, `permission`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 核心功能实现
2.1 用户注册
@Service
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate UserMapper userMapper;@Override@Transactional(rollbackFor = Exception.class)public void register(UserRegisterDTO dto) {// 1. 参数校验validateRegisterParams(dto);// 2. 检查用户名是否已存在if (userMapper.selectByUsername(dto.getUsername()) != null) {throw new BusinessException("用户名已存在");}// 3. 检查学号是否已存在if (userMapper.selectByStudentId(dto.getStudentId()) != null) {throw new BusinessException("学号已被注册");}// 4. 密码加密String encodedPassword = passwordEncoder.encode(dto.getPassword());// 5. 构建用户对象User user = User.builder().username(dto.getUsername()).password(encodedPassword).realName(dto.getRealName()).studentId(dto.getStudentId()).phone(dto.getPhone()).email(dto.getEmail()).role(UserRoleEnum.NORMAL_USER.getCode()).status(UserStatusEnum.ENABLED.getCode()).build();// 6. 保存用户信息userMapper.insert(user);// 7. 发送注册成功邮件sendRegisterSuccessEmail(user);}
}
2.2 用户登录
@Service
public class AuthServiceImpl implements AuthService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic LoginVO login(LoginDTO dto) {// 1. 查询用户User user = userMapper.selectByUsername(dto.getUsername());if (user == null) {throw new BusinessException("用户名或密码错误");}// 2. 校验密码if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) {throw new BusinessException("用户名或密码错误");}// 3. 校验用户状态if (UserStatusEnum.DISABLED.getCode().equals(user.getStatus())) {throw new BusinessException("账号已被禁用");}// 4. 生成tokenString token = tokenProvider.generateToken(user);// 5. 更新登录时间userMapper.updateLastLoginTime(user.getId());// 6. 缓存用户信息cacheUserInfo(user);return new LoginVO(token, user);}private void cacheUserInfo(User user) {String key = RedisKeyConstants.USER_INFO_PREFIX + user.getId();redisTemplate.opsForValue().set(key, user, 24, TimeUnit.HOURS);}
}
2.3 权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 公开接口.antMatchers("/api/auth/**").permitAll().antMatchers("/api/public/**").permitAll()// 管理员接口.antMatchers("/api/admin/**").hasRole("ADMIN")// 社团负责人接口.antMatchers("/api/club/manage/**").hasRole("CLUB_LEADER")// 需要登录的接口.anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}
2.4 个人信息管理
@RestController
@RequestMapping("/api/user")
public class UserController {@Autowiredprivate UserService userService;// 获取个人信息@GetMapping("/info")public Result<UserVO> getUserInfo() {return Result.success(userService.getCurrentUserInfo());}// 更新个人信息@PutMapping("/info")public Result updateUserInfo(@RequestBody @Valid UserUpdateDTO dto) {userService.updateUserInfo(dto);return Result.success();}// 修改密码@PutMapping("/password")public Result updatePassword(@RequestBody @Valid PasswordUpdateDTO dto) {userService.updatePassword(dto);return Result.success();}// 上传头像@PostMapping("/avatar")public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {String avatarUrl = userService.uploadAvatar(file);return Result.success(avatarUrl);}
}
二、社团管理模块
1. 数据库设计
社团表(club)
CREATE TABLE `club` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(100) NOT NULL COMMENT '社团名称',`description` text COMMENT '社团简介',`logo` varchar(200) COMMENT '社团logo',`leader_id` bigint COMMENT '负责人id',`member_count` int DEFAULT 0 COMMENT '成员数量',`category` varchar(50) COMMENT '社团类别',`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
社团成员表(club_member)
CREATE TABLE `club_member` (`id` bigint NOT NULL AUTO_INCREMENT,`club_id` bigint NOT NULL COMMENT '社团id',`user_id` bigint NOT NULL COMMENT '用户id',`role` tinyint NOT NULL COMMENT '角色:0-负责人,1-管理员,2-普通成员',`join_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '加入时间',`status` tinyint DEFAULT 1 COMMENT '状态:0-退出,1-正常',PRIMARY KEY (`id`),UNIQUE KEY `uk_club_user` (`club_id`, `user_id`),KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
社团公告表(club_announcement)
CREATE TABLE `club_announcement` (`id` bigint NOT NULL AUTO_INCREMENT,`club_id` bigint NOT NULL COMMENT '社团id',`title` varchar(200) NOT NULL COMMENT '公告标题',`content` text NOT NULL COMMENT '公告内容',`publisher_id` bigint NOT NULL COMMENT '发布人id',`status` tinyint DEFAULT 1 COMMENT '状态:0-下架,1-发布',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_club_id` (`club_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 核心功能实现
2.1 社团信息管理
@Service
@Transactional(rollbackFor = Exception.class)
public class ClubServiceImpl implements ClubService {@Autowiredprivate ClubMapper clubMapper;@Autowiredprivate ClubMemberMapper memberMapper;@Overridepublic void createClub(ClubCreateDTO dto) {// 1. 校验社团名称是否已存在if (clubMapper.selectByName(dto.getName()) != null) {throw new BusinessException("社团名称已存在");}// 2. 保存社团信息Club club = Club.builder().name(dto.getName()).description(dto.getDescription()).category(dto.getCategory()).leaderId(SecurityUtils.getCurrentUserId()).status(ClubStatusEnum.ENABLED.getCode()).build();clubMapper.insert(club);// 3. 添加社团负责人ClubMember leader = ClubMember.builder().clubId(club.getId()).userId(club.getLeaderId()).role(ClubMemberRoleEnum.LEADER.getCode()).status(ClubMemberStatusEnum.NORMAL.getCode()).build();memberMapper.insert(leader);}@Overridepublic void updateClub(ClubUpdateDTO dto) {// 1. 校验社团是否存在Club club = clubMapper.selectById(dto.getId());if (club == null) {throw new BusinessException("社团不存在");}// 2. 校验权限if (!isClubLeader(club.getId())) {throw new BusinessException("无权限修改");}// 3. 更新社团信息Club updateClub = Club.builder().id(dto.getId()).name(dto.getName()).description(dto.getDescription()).category(dto.getCategory()).build();clubMapper.updateById(updateClub);}
}
2.2 社团成员管理
@Service
public class ClubMemberServiceImpl implements ClubMemberService {@Autowiredprivate ClubMemberMapper memberMapper;@Overridepublic void addMember(Long clubId, Long userId) {// 1. 校验是否已是成员if (memberMapper.selectByClubAndUser(clubId, userId) != null) {throw new BusinessException("已是社团成员");}// 2. 添加成员ClubMember member = ClubMember.builder().clubId(clubId).userId(userId).role(ClubMemberRoleEnum.NORMAL.getCode()).status(ClubMemberStatusEnum.NORMAL.getCode()).build();memberMapper.insert(member);// 3. 更新社团成员数量memberMapper.incrementMemberCount(clubId);}@Overridepublic void removeMember(Long clubId, Long userId) {// 1. 校验成员是否存在ClubMember member = memberMapper.selectByClubAndUser(clubId, userId);if (member == null) {throw new BusinessException("用户不是社团成员");}// 2. 校验是否为负责人if (ClubMemberRoleEnum.LEADER.getCode().equals(member.getRole())) {throw new BusinessException("不能移除社团负责人");}// 3. 移除成员memberMapper.updateStatus(member.getId(), ClubMemberStatusEnum.QUIT.getCode());// 4. 更新社团成员数量memberMapper.decrementMemberCount(clubId);}
}
2.3 社团公告管理
@Service
public class ClubAnnouncementServiceImpl implements ClubAnnouncementService {@Autowiredprivate ClubAnnouncementMapper announcementMapper;@Overridepublic void publishAnnouncement(AnnouncementPublishDTO dto) {// 1. 校验权限if (!isClubManager(dto.getClubId())) {throw new BusinessException("无权发布公告");}// 2. 保存公告ClubAnnouncement announcement = ClubAnnouncement.builder().clubId(dto.getClubId()).title(dto.getTitle()).content(dto.getContent()).publisherId(SecurityUtils.getCurrentUserId()).status(AnnouncementStatusEnum.PUBLISHED.getCode()).build();announcementMapper.insert(announcement);// 3. 发送公告通知sendAnnouncementNotification(announcement);}@Overridepublic PageResult<AnnouncementVO> getAnnouncementList(AnnouncementQuery query) {// 1. 构建查询条件LambdaQueryWrapper<ClubAnnouncement> wrapper = new LambdaQueryWrapper<>();wrapper.eq(ClubAnnouncement::getClubId, query.getClubId()).eq(ClubAnnouncement::getStatus, AnnouncementStatusEnum.PUBLISHED.getCode()).orderByDesc(ClubAnnouncement::getCreateTime);// 2. 分页查询Page<ClubAnnouncement> page = announcementMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), wrapper);// 3. 转换结果List<AnnouncementVO> records = page.getRecords().stream().map(this::convertToVO).collect(Collectors.toList());return new PageResult<>(records, page.getTotal());}
}
3. 接口设计
@RestController
@RequestMapping("/api/club")
@Api(tags = "社团管理接口")
public class ClubController {@Autowiredprivate ClubService clubService;@Autowiredprivate ClubMemberService memberService;@Autowiredprivate ClubAnnouncementService announcementService;@PostMapping@ApiOperation("创建社团")public Result createClub(@RequestBody @Valid ClubCreateDTO dto) {clubService.createClub(dto);return Result.success();}@GetMapping("/list")@ApiOperation("获取社团列表")public Result<PageResult<ClubVO>> getClubList(ClubQuery query) {return Result.success(clubService.getClubList(query));}@GetMapping("/{id}")@ApiOperation("获取社团详情")public Result<ClubDetailVO> getClubDetail(@PathVariable Long id) {return Result.success(clubService.getClubDetail(id));}@PostMapping("/{id}/member")@ApiOperation("添加社团成员")public Result addMember(@PathVariable Long id, @RequestParam Long userId) {memberService.addMember(id, userId);return Result.success();}@DeleteMapping("/{id}/member/{userId}")@ApiOperation("移除社团成员")public Result removeMember(@PathVariable Long id, @PathVariable Long userId) {memberService.removeMember(id, userId);return Result.success();}@PostMapping("/announcement")@ApiOperation("发布公告")public Result publishAnnouncement(@RequestBody @Valid AnnouncementPublishDTO dto) {announcementService.publishAnnouncement(dto);return Result.success();}@GetMapping("/{id}/announcement")@ApiOperation("获取社团公告列表")public Result<PageResult<AnnouncementVO>> getAnnouncementList(@PathVariable Long id, AnnouncementQuery query) {query.setClubId(id);return Result.success(announcementService.getAnnouncementList(query));}
}
4. 安全控制
@Aspect
@Component
public class ClubPermissionAspect {@Autowiredprivate ClubMemberService memberService;@Before("@annotation(clubPermission)")public void checkPermission(JoinPoint point, ClubPermission clubPermission) {// 1. 获取社团IDLong clubId = getClubId(point);// 2. 获取当前用户Long userId = SecurityUtils.getCurrentUserId();// 3. 校验权限ClubMemberRoleEnum requiredRole = clubPermission.role();if (!memberService.hasPermission(clubId, userId, requiredRole)) {throw new BusinessException("无操作权限");}}private Long getClubId(JoinPoint point) {Object[] args = point.getArgs();// ... 从方法参数中获取clubId的逻辑return clubId;}
}
这些代码展示了用户管理和社团管理模块的核心实现。包括:
- 完整的数据库表设计
- 用户注册、登录的实现
- 基于Spring Security的权限控制
- 社团的CRUD操作
- 社团成员管理
- 社团公告管理
- 接口的统一封装
- 权限校验的AOP实现
实际项目中还需要考虑:
- 数据验证
- 异常处理
- 日志记录
- 缓存优化
- 事务管理
- 接口文档
- 单元测试
Java全栈项目详解 - 校园社团平台的活动管理与信息展示模块
一、活动管理模块
1. 数据库设计
活动表(activity)
CREATE TABLE `activity` (`id` bigint NOT NULL AUTO_INCREMENT,`title` varchar(200) NOT NULL COMMENT '活动标题',`content` text NOT NULL COMMENT '活动内容',`club_id` bigint NOT NULL COMMENT '所属社团id',`location` varchar(200) COMMENT '活动地点',`start_time` datetime NOT NULL COMMENT '开始时间',`end_time` datetime NOT NULL COMMENT '结束时间',`sign_up_start` datetime COMMENT '报名开始时间',`sign_up_end` datetime COMMENT '报名截止时间',`max_participants` int DEFAULT 0 COMMENT '最大参与人数',`current_participants` int DEFAULT 0 COMMENT '当前参与人数',`cover_image` varchar(200) COMMENT '封面图片',`status` tinyint DEFAULT 0 COMMENT '状态:0-未开始,1-报名中,2-进行中,3-已结束,4-已取消',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_club_id` (`club_id`),KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
活动报名表(activity_signup)
CREATE TABLE `activity_signup` (`id` bigint NOT NULL AUTO_INCREMENT,`activity_id` bigint NOT NULL COMMENT '活动id',`user_id` bigint NOT NULL COMMENT '用户id',`status` tinyint DEFAULT 1 COMMENT '状态:0-已取消,1-已报名,2-已签到',`signup_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间',`checkin_time` datetime COMMENT '签到时间',`remark` varchar(200) COMMENT '备注',PRIMARY KEY (`id`),UNIQUE KEY `uk_activity_user` (`activity_id`, `user_id`),KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
活动评论表(activity_comment)
CREATE TABLE `activity_comment` (`id` bigint NOT NULL AUTO_INCREMENT,`activity_id` bigint NOT NULL COMMENT '活动id',`user_id` bigint NOT NULL COMMENT '评论用户id',`content` text NOT NULL COMMENT '评论内容',`parent_id` bigint DEFAULT NULL COMMENT '父评论id',`reply_user_id` bigint DEFAULT NULL COMMENT '回复用户id',`like_count` int DEFAULT 0 COMMENT '点赞数',`status` tinyint DEFAULT 1 COMMENT '状态:0-已删除,1-正常',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_activity_id` (`activity_id`),KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 核心功能实现
2.1 活动发布与管理
@Service
@Transactional(rollbackFor = Exception.class)
public class ActivityServiceImpl implements ActivityService {@Autowiredprivate ActivityMapper activityMapper;@Autowiredprivate FileService fileService;@Overridepublic void publishActivity(ActivityPublishDTO dto) {// 1. 校验活动时间validateActivityTime(dto);// 2. 上传封面图片String coverUrl = fileService.uploadImage(dto.getCoverImage());// 3. 保存活动信息Activity activity = Activity.builder().title(dto.getTitle()).content(dto.getContent()).clubId(dto.getClubId()).location(dto.getLocation()).startTime(dto.getStartTime()).endTime(dto.getEndTime()).signUpStart(dto.getSignUpStart()).signUpEnd(dto.getSignUpEnd()).maxParticipants(dto.getMaxParticipants()).coverImage(coverUrl).status(ActivityStatusEnum.NOT_STARTED.getCode()).build();activityMapper.insert(activity);// 4. 发送活动通知sendActivityNotification(activity);}@Scheduled(cron = "0 */5 * * * ?")public void updateActivityStatus() {// 定时更新活动状态Date now = new Date();// 更新为报名中activityMapper.updateStatusByTime(ActivityStatusEnum.NOT_STARTED.getCode(),ActivityStatusEnum.SIGN_UP.getCode(),now,"sign_up_start");// 更新为进行中activityMapper.updateStatusByTime(ActivityStatusEnum.SIGN_UP.getCode(),ActivityStatusEnum.IN_PROGRESS.getCode(),now,"start_time");// 更新为已结束activityMapper.updateStatusByTime(ActivityStatusEnum.IN_PROGRESS.getCode(),ActivityStatusEnum.FINISHED.getCode(),now,"end_time");}
}
2.2 活动报名与签到
@Service
public class ActivitySignupServiceImpl implements ActivitySignupService {@Autowiredprivate ActivitySignupMapper signupMapper;@Autowiredprivate ActivityMapper activityMapper;@Override@Transactional(rollbackFor = Exception.class)public void signup(Long activityId) {// 1. 校验活动状态Activity activity = activityMapper.selectById(activityId);if (!ActivityStatusEnum.SIGN_UP.getCode().equals(activity.getStatus())) {throw new BusinessException("活动不在报名期间");}// 2. 校验人数限制if (activity.getCurrentParticipants() >= activity.getMaxParticipants()) {throw new BusinessException("活动名额已满");}// 3. 校验是否重复报名Long userId = SecurityUtils.getCurrentUserId();if (signupMapper.exists(activityId, userId)) {throw new BusinessException("已报名该活动");}// 4. 保存报名信息ActivitySignup signup = ActivitySignup.builder().activityId(activityId).userId(userId).status(SignupStatusEnum.SIGNED_UP.getCode()).build();signupMapper.insert(signup);// 5. 更新活动参与人数activityMapper.incrementParticipants(activityId);}@Override@Transactional(rollbackFor = Exception.class)public void checkin(CheckinDTO dto) {// 1. 校验活动状态Activity activity = activityMapper.selectById(dto.getActivityId());if (!ActivityStatusEnum.IN_PROGRESS.getCode().equals(activity.getStatus())) {throw new BusinessException("不在活动进行时间");}// 2. 校验报名状态ActivitySignup signup = signupMapper.selectByActivityAndUser(dto.getActivityId(), dto.getUserId());if (signup == null || !SignupStatusEnum.SIGNED_UP.getCode().equals(signup.getStatus())) {throw new BusinessException("未报名该活动");}// 3. 更新签到状态signupMapper.updateStatus(signup.getId(), SignupStatusEnum.CHECKED_IN.getCode(),new Date());}
}
2.3 活动评论与互动
@Service
public class ActivityCommentServiceImpl implements ActivityCommentService {@Autowiredprivate ActivityCommentMapper commentMapper;@Autowiredprivate UserService userService;@Overridepublic void addComment(CommentAddDTO dto) {// 1. 校验活动状态Activity activity = activityMapper.selectById(dto.getActivityId());if (ActivityStatusEnum.CANCELLED.getCode().equals(activity.getStatus())) {throw new BusinessException("活动已取消");}// 2. 保存评论ActivityComment comment = ActivityComment.builder().activityId(dto.getActivityId()).userId(SecurityUtils.getCurrentUserId()).content(dto.getContent()).parentId(dto.getParentId()).replyUserId(dto.getReplyUserId()).status(CommentStatusEnum.NORMAL.getCode()).build();commentMapper.insert(comment);// 3. 发送评论通知if (dto.getReplyUserId() != null) {sendCommentNotification(comment);}}@Overridepublic PageResult<CommentVO> getCommentList(CommentQuery query) {// 1. 分页查询评论Page<ActivityComment> page = commentMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()),new QueryWrapper<ActivityComment>().eq("activity_id", query.getActivityId()).eq("status", CommentStatusEnum.NORMAL.getCode()).orderByDesc("create_time"));// 2. 查询用户信息Set<Long> userIds = new HashSet<>();page.getRecords().forEach(comment -> {userIds.add(comment.getUserId());if (comment.getReplyUserId() != null) {userIds.add(comment.getReplyUserId());}});Map<Long, UserVO> userMap = userService.getUserMapByIds(userIds);// 3. 构建评论树List<CommentVO> commentTree = buildCommentTree(page.getRecords(), userMap);return new PageResult<>(commentTree, page.getTotal());}
}
2.4 活动数据统计
@Service
public class ActivityStatisticsServiceImpl implements ActivityStatisticsService {@Autowiredprivate ActivityMapper activityMapper;@Autowiredprivate ActivitySignupMapper signupMapper;@Overridepublic ActivityStatisticsVO getActivityStatistics(Long activityId) {// 1. 基础信息统计Activity activity = activityMapper.selectById(activityId);// 2. 报名统计SignupStatisticsVO signupStats = signupMapper.selectStatistics(activityId);// 3. 签到统计CheckinStatisticsVO checkinStats = signupMapper.selectCheckinStatistics(activityId);// 4. 性别分布List<GenderDistributionVO> genderStats = signupMapper.selectGenderDistribution(activityId);// 5. 学院分布List<CollegeDistributionVO> collegeStats = signupMapper.selectCollegeDistribution(activityId);return ActivityStatisticsVO.builder().basicInfo(buildBasicInfo(activity)).signupStats(signupStats).checkinStats(checkinStats).genderDistribution(genderStats).collegeDistribution(collegeStats).build();}@Overridepublic List<ActivityTrendVO> getActivityTrend(ActivityTrendQuery query) {return activityMapper.selectActivityTrend(query.getClubId(),query.getStartDate(),query.getEndDate());}
}
二、信息展示模块
1. 数据库设计
轮播图表(banner)
CREATE TABLE `banner` (`id` bigint NOT NULL AUTO_INCREMENT,`title` varchar(100) NOT NULL COMMENT '标题',`image_url` varchar(200) NOT NULL COMMENT '图片地址',`link_url` varchar(200) COMMENT '链接地址',`sort` int DEFAULT 0 COMMENT '排序',`status` tinyint DEFAULT 1 COMMENT '状态:0-下线,1-上线',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
活动照片表(activity_photo)
CREATE TABLE `activity_photo` (`id` bigint NOT NULL AUTO_INCREMENT,`activity_id` bigint NOT NULL COMMENT '活动id',`photo_url` varchar(200) NOT NULL COMMENT '照片地址',`description` varchar(200) COMMENT '照片描述',`upload_user_id` bigint NOT NULL COMMENT '上传用户id',`sort` int DEFAULT 0 COMMENT '排序',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_activity_id` (`activity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 核心功能实现
2.1 首页轮播展示
@Service
public class BannerServiceImpl implements BannerService {@Autowiredprivate BannerMapper bannerMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic List<BannerVO> getBannerList() {// 1. 从缓存获取List<BannerVO> bannerList = (List<BannerVO>) redisTemplate.opsForValue().get(RedisKeyConstants.BANNER_LIST_KEY);if (bannerList != null) {return bannerList;}// 2. 查询数据库bannerList = bannerMapper.selectList(new QueryWrapper<Banner>().eq("status", BannerStatusEnum.ONLINE.getCode()).orderByAsc("sort")).stream().map(this::convertToVO).collect(Collectors.toList());// 3. 更新缓存redisTemplate.opsForValue().set(RedisKeyConstants.BANNER_LIST_KEY,bannerList,1,TimeUnit.HOURS);return bannerList;}@Override@CacheEvict(value = "bannerList", allEntries = true)public void updateBanner(BannerUpdateDTO dto) {Banner banner = Banner.builder().id(dto.getId()).title(dto.getTitle()).imageUrl(dto.getImageUrl()).linkUrl(dto.getLinkUrl()).sort(dto.getSort()).status(dto.getStatus()).build();bannerMapper.updateById(banner);}
}
2.2 活动日历
@Service
public class ActivityCalendarServiceImpl implements ActivityCalendarService {@Autowiredprivate ActivityMapper activityMapper;@Overridepublic List<CalendarEventVO> getCalendarEvents(CalendarQuery query) {// 1. 查询时间范围内的活动List<Activity> activities = activityMapper.selectList(new QueryWrapper<Activity>().ge(query.getStartDate() != null, "start_time", query.getStartDate()).le(query.getEndDate() != null, "end_time", query.getEndDate()).eq(query.getClubId() != null, "club_id", query.getClubId()).ne("status", ActivityStatusEnum.CANCELLED.getCode()));// 2. 转换为日历事件return activities.stream().map(activity -> CalendarEventVO.builder().id(activity.getId()).title(activity.getTitle()).start(activity.getStartTime()).end(activity.getEndTime()).color(getEventColor(activity.getStatus())).build()).collect(Collectors.toList());}private String getEventColor(Integer status) {// 根据活动状态返回不同颜色switch (ActivityStatusEnum.valueOf(status)) {case NOT_STARTED:return "#409EFF";case SIGN_UP:return "#67C23A";case IN_PROGRESS:return "#E6A23C";case FINISHED:return "#909399";default:return "#303133";}}
}
2.3 活动照片墙
@Service
public class ActivityPhotoServiceImpl implements ActivityPhotoService {@Autowiredprivate ActivityPhotoMapper photoMapper;@Autowiredprivate FileService fileService;@Overridepublic void uploadPhotos(PhotoUploadDTO dto) {List<ActivityPhoto> photos = dto.getPhotos().stream().map(photo -> {// 1. 上传图片String photoUrl = fileService.uploadImage(photo.getFile());// 2. 构建照片对象return ActivityPhoto.builder().activityId(dto.getActivityId()).photoUrl(photoUrl).description(photo.getDescription()).uploadUserId(SecurityUtils.getCurrentUserId()).sort(photo.getSort()).build();}).collect(Collectors.toList());// 3. 批量保存photoMapper.insertBatch(photos);}@Overridepublic PageResult<PhotoVO> getPhotoWall(PhotoQuery query) {// 1. 分页查询Page<ActivityPhoto> page = photoMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()),new QueryWrapper<ActivityPhoto>().eq(query.getActivityId() != null, "activity_id", query.getActivityId()).orderByDesc("create_time"));// 2. 查询上传用户信息Set<Long> userIds = page.getRecords().stream().map(ActivityPhoto::getUploadUserId).collect(Collectors.toSet());Map<Long, UserVO> userMap = userService.getUserMapByIds(userIds);// 3. 转换结果List<PhotoVO> records = page.getRecords().stream().map(photo -> convertToVO(photo, userMap.get(photo.getUploadUserId()))).collect(Collectors.toList());return new PageResult<>(records, page.getTotal());}
}
3. 前端实现
3.1 活动日历组件
<template><div class="calendar-container"><FullCalendar:options="calendarOptions"@eventClick="handleEventClick"/></div>
</template><script>
import { defineComponent } from 'vue'
import FullCalendar from '@fullcalendar/vue3'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from '@fullcalendar/interaction'export default defineComponent({name: 'ActivityCalendar',components: {FullCalendar},data() {return {calendarOptions: {plugins: [dayGridPlugin, interactionPlugin],initialView: 'dayGridMonth',locale: 'zh-cn',headerToolbar: {left: 'prev,next today',center: 'title',right: 'dayGridMonth,dayGridWeek'},events: [],eventClick: this.handleEventClick}}},methods: {async loadEvents() {try {const response = await this.$api.activity.getCalendarEvents({startDate: this.startDate,endDate: this.endDate})this.calendarOptions.events = response.data} catch (error) {this.$message.error('加载活动失败')}},handleEventClick(info) {this.$router.push(`/activity/${info.event.id}`)}},mounted() {this.loadEvents()}
})
</script>
3.2 照片墙组件
<template><div class="photo-wall"><el-row :gutter="20"><el-col v-for="photo in photos" :key="photo.id" :xs="24" :sm="12" :md="8" :lg="6"><el-card :body-style="{ padding: '0px' }" class="photo-card"><el-image:src="photo.photoUrl"fit="cover"@click="handlePreview(photo)"><template #error><div class="image-slot"><i class="el-icon-picture-outline"></i></div></template></el-image><div class="photo-info"><span class="description">{{ photo.description }}</span><div class="uploader-info"><el-avatar :size="24" :src="photo.uploader.avatar"></el-avatar><span>{{ photo.uploader.username }}</span></div></div></el-card></el-col></el-row><!-- 图片预览 --><el-image-viewerv-if="showViewer":url-list="[currentPhoto.photoUrl]"@close="showViewer = false"/><!-- 加载更多 --><div class="load-more" v-if="hasMore"><el-button type="text" @click="loadMore">加载更多</el-button></div></div>
</template><script>
export default {name: 'PhotoWall',data() {return {photos: [],currentPage: 1,pageSize: 12,total: 0,showViewer: false,currentPhoto: null}},computed: {hasMore() {return this.photos.length < this.total}},methods: {async loadPhotos() {try {const response = await this.$api.activity.getPhotoWall({pageNum: this.currentPage,pageSize: this.pageSize,activityId: this.activityId})this.photos.push(...response.data.records)this.total = response.data.total} catch (error) {this.$message.error('加载照片失败')}},handlePreview(photo) {this.currentPhoto = photothis.showViewer = true},loadMore() {this.currentPage++this.loadPhotos()}},mounted() {this.loadPhotos()}
})
</script><style scoped>
.photo-wall {padding: 20px;
}.photo-card {margin-bottom: 20px;transition: all 0.3s;
}.photo-card:hover {transform: translateY(-5px);box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}.photo-info {padding: 10px;
}.description {display: block;margin-bottom: 10px;color: #303133;
}.uploader-info {display: flex;align-items: center;color: #909399;font-size: 12px;
}.uploader-info .el-avatar {margin-right: 8px;
}.load-more {text-align: center;margin-top: 20px;
}
</style>
这些代码展示了活动管理和信息展示模块的核心实现,包括:
- 完整的数据库设计
- 活动的CRUD操作
- 报名和签到功能
- 评论互动系统
- 数据统计功能
- 首页轮播图管理
- 活动日历展示
- 照片墙功能
实际项目中还需要注意:
- 图片上传的安全性
- 评论内容的过滤
- 缓存的合理使用
- 大数据量下的性能优化
- 移动端适配
- 用户体验优化
- 安全性考虑
这些都是保证项目质量的重要环节。