项目简介
校园志愿者服务平台是一个面向高校的志愿服务管理系统,旨在提供志愿活动发布、报名、签到、时长统计等功能,促进校园志愿服务的规范化管理和高效开展。本文将详细介绍该项目的技术架构、核心功能实现以及开发过程中的经验总结。
技术栈
后端技术
- Spring Boot 2.7.0
- Spring Security
- MyBatis Plus
- MySQL 8.0
- Redis
- JWT
前端技术
- Vue 3
- Element Plus
- Axios
- Vuex
- Vue Router
核心功能模块
1. 用户认证与授权
- 基于JWT的登录认证
- 基于RBAC的权限控制
- 角色分为管理员、志愿者管理员、普通志愿者
2. 志愿活动管理
- 活动发布与审核
- 活动报名与取消
- 活动签到打卡
- 活动评价反馈
3. 志愿时长管理
- 自动统计志愿时长
- 时长审核与确认
- 志愿者排行榜
4. 消息通知
- 活动提醒
- 系统公告
- 站内信
数据库设计
核心表结构
-- 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`real_name` varchar(50),
`phone` varchar(20),
`email` varchar(100),
`role_id` bigint,
`create_time` datetime,
`update_time` datetime,
PRIMARY KEY (`id`)
);
-- 活动表
CREATE TABLE `activity` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`description` text,
`start_time` datetime,
`end_time` datetime,
`location` varchar(200),
`max_participants` int,
`status` tinyint,
`create_user_id` bigint,
`create_time` datetime,
PRIMARY KEY (`id`)
);
-- 报名表
CREATE TABLE `registration` (
`id` bigint NOT NULL AUTO_INCREMENT,
`activity_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`status` tinyint,
`register_time` datetime,
PRIMARY KEY (`id`)
);
关键功能实现
1. 活动报名流程
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private ActivityMapper activityMapper;
@Autowired
private RegistrationMapper registrationMapper;
@Transactional
public Result register(Long activityId, Long userId) {
// 检查活动是否存在且开放报名
Activity activity = activityMapper.selectById(activityId);
if (activity == null || activity.getStatus() != ActivityStatus.OPEN) {
return Result.fail("活动不存在或未开放报名");
}
// 检查是否已报名
Registration exist = registrationMapper.selectByActivityAndUser(activityId, userId);
if (exist != null) {
return Result.fail("已报名该活动");
}
// 检查人数限制
int count = registrationMapper.countByActivityId(activityId);
if (count >= activity.getMaxParticipants()) {
return Result.fail("活动名额已满");
}
// 创建报名记录
Registration registration = new Registration();
registration.setActivityId(activityId);
registration.setUserId(userId);
registration.setStatus(RegistrationStatus.REGISTERED);
registration.setRegisterTime(new Date());
registrationMapper.insert(registration);
return Result.ok();
}
}
2. 签到打卡功能
@Service
public class CheckinServiceImpl implements CheckinService {
@Autowired
private RedisTemplate redisTemplate;
public Result checkin(Long activityId, Long userId, String location) {
// 生成签到key
String key = String.format("checkin:%d:%d", activityId, userId);
// 防止重复签到
if (redisTemplate.hasKey(key)) {
return Result.fail("今日已签到");
}
// 验证位置信息
if (!validateLocation(activityId, location)) {
return Result.fail("不在活动范围内");
}
// 记录签到
redisTemplate.opsForValue().set(key, "1", 24, TimeUnit.HOURS);
return Result.ok();
}
}
性能优化
1. Redis缓存优化
- 使用Redis缓存热点活动数据
- 实现分布式Session
- 防止重复提交
2. MySQL优化
- 合理建立索引
- 分页查询优化
- 大数据量批量操作
3. 前端优化
- 路由懒加载
- 组件按需加载
- 图片懒加载
项目部署
1. 环境准备
- JDK 1.8+
- MySQL 8.0
- Redis 6.0
- Nginx 1.18
2. 部署步骤
# 后端打包
mvn clean package
# 前端打包
npm run build
# 启动服务
nohup java -jar volunteer-service.jar &
# Nginx配置
server {
listen 80;
server_name volunteer.example.com;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
}
}
项目总结
技术亮点
- 采用前后端分离架构,提高开发效率
- 使用Redis实现高并发场景优化
- 实现基于RBAC的权限控制
- 统一异常处理和接口规范
遇到的问题及解决方案
- 签到并发问题:使用Redis分布式锁解决
- 大数据量导出:异步任务+分片处理
- 前端性能优化:路由懒加载、组件缓存
项目收获
- 加深对Spring Boot全家桶的理解
- 提升系统设计和优化能力
- 积累项目开发最佳实践
未来展望
- 引入微服务架构
- 添加数据分析功能
- 开发移动端应用
- 接入第三方平台
结语
通过这个项目的开发,不仅实现了校园志愿服务的信息化管理,也积累了宝贵的全栈开发经验。项目中的很多技术方案和最佳实践,都可以在其他项目中借鉴使用。
校园志愿者服务平台核心功能详解
一、用户认证与授权模块
1. JWT认证流程实现
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtTokenUtil jwtTokenUtil;
public Result login(String username, String password) {
// 1. 验证用户
User user = userMapper.selectByUsername(username);
if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
return Result.fail("用户名或密码错误");
}
// 2. 生成Token
String token = jwtTokenUtil.generateToken(user);
// 3. 返回用户信息和Token
Map<String, Object> result = new HashMap<>();
result.put("token", token);
result.put("user", UserVO.fromUser(user));
return Result.ok(result);
}
}
// JWT工具类
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
claims.put("username", user.getUsername());
claims.put("role", user.getRole());
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
2. RBAC权限控制
// 权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
String value();
}
// 权限拦截器
@Component
public class RoleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 获取方法上的注解
RequiresRole requiresRole = ((HandlerMethod) handler).getMethodAnnotation(RequiresRole.class);
if (requiresRole == null) {
return true;
}
// 获取用户角色
String userRole = SecurityContextHolder.getContext().getAuthentication().getAuthorities().iterator().next().getAuthority();
// 验证权限
if (!requiresRole.value().equals(userRole)) {
throw new AccessDeniedException("无权限访问");
}
return true;
}
}
3. 角色权限表设计
-- 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) NOT NULL,
`role_code` varchar(50) NOT NULL,
`description` varchar(200),
`create_time` datetime,
PRIMARY KEY (`id`)
);
-- 权限表
CREATE TABLE `permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`permission_name` varchar(100) NOT NULL,
`permission_code` varchar(100) NOT NULL,
`url` varchar(200),
`method` varchar(10),
PRIMARY KEY (`id`)
);
-- 角色权限关联表
CREATE TABLE `role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`permission_id` bigint NOT NULL,
PRIMARY KEY (`id`)
);
二、志愿活动管理模块
1. 活动发布与审核
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private ActivityMapper activityMapper;
@Autowired
private MessageService messageService;
@RequiresRole("ADMIN")
public Result publishActivity(ActivityDTO activityDTO) {
// 1. 参数校验
validateActivityParams(activityDTO);
// 2. 保存活动信息
Activity activity = new Activity();
BeanUtils.copyProperties(activityDTO, activity);
activity.setStatus(ActivityStatus.PENDING);
activity.setCreateTime(new Date());
activityMapper.insert(activity);
// 3. 发送审核通知
messageService.sendAuditNotification(activity);
return Result.ok();
}
@RequiresRole("ADMIN")
public Result auditActivity(Long activityId, Integer auditStatus, String remark) {
Activity activity = activityMapper.selectById(activityId);
if (activity == null) {
return Result.fail("活动不存在");
}
// 更新审核状态
activity.setStatus(auditStatus);
activity.setAuditRemark(remark);
activity.setAuditTime(new Date());
activityMapper.updateById(activity);
// 发送审核结果通知
messageService.sendAuditResultNotification(activity);
return Result.ok();
}
}
2. 活动报名管理
@Service
public class RegistrationServiceImpl implements RegistrationService {
@Autowired
private RegistrationMapper registrationMapper;
@Autowired
private RedisTemplate redisTemplate;
@Transactional
public Result register(RegistrationDTO registrationDTO) {
// 1. 检查活动名额
String quotaKey = "activity:quota:" + registrationDTO.getActivityId();
Long remainQuota = redisTemplate.opsForValue().decrement(quotaKey);
if (remainQuota < 0) {
redisTemplate.opsForValue().increment(quotaKey);
return Result.fail("活动名额已满");
}
try {
// 2. 创建报名记录
Registration registration = new Registration();
BeanUtils.copyProperties(registrationDTO, registration);
registration.setStatus(RegistrationStatus.REGISTERED);
registration.setRegisterTime(new Date());
registrationMapper.insert(registration);
return Result.ok();
} catch (Exception e) {
// 3. 发生异常时恢复名额
redisTemplate.opsForValue().increment(quotaKey);
throw e;
}
}
public Result cancelRegistration(Long registrationId) {
Registration registration = registrationMapper.selectById(registrationId);
if (registration == null) {
return Result.fail("报名记录不存在");
}
// 检查是否可以取消
if (registration.getStatus() != RegistrationStatus.REGISTERED) {
return Result.fail("当前状态不可取消");
}
// 更新状态
registration.setStatus(RegistrationStatus.CANCELED);
registration.setCancelTime(new Date());
registrationMapper.updateById(registration);
// 恢复活动名额
String quotaKey = "activity:quota:" + registration.getActivityId();
redisTemplate.opsForValue().increment(quotaKey);
return Result.ok();
}
}
3. 活动签到功能
@Service
public class CheckinServiceImpl implements CheckinService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private VolunteerHoursService volunteerHoursService;
public Result checkin(CheckinDTO checkinDTO) {
// 1. 生成签到key
String checkinKey = String.format("checkin:%d:%d:%s",
checkinDTO.getActivityId(),
checkinDTO.getUserId(),
DateUtil.format(new Date(), "yyyy-MM-dd"));
// 2. 防止重复签到
Boolean success = redisTemplate.opsForValue().setIfAbsent(checkinKey, "1", 24, TimeUnit.HOURS);
if (!success) {
return Result.fail("今日已签到");
}
// 3. 验证位置信息
if (!validateLocation(checkinDTO)) {
redisTemplate.delete(checkinKey);
return Result.fail("不在签到范围内");
}
// 4. 记录志愿时长
volunteerHoursService.recordHours(checkinDTO);
return Result.ok();
}
private boolean validateLocation(CheckinDTO checkinDTO) {
// 获取活动位置
ActivityLocation activityLocation = getActivityLocation(checkinDTO.getActivityId());
// 计算距离
double distance = LocationUtil.calculateDistance(
checkinDTO.getLatitude(),
checkinDTO.getLongitude(),
activityLocation.getLatitude(),
activityLocation.getLongitude()
);
// 判断是否在有效范围内(如500米)
return distance <= 500;
}
}
4. 活动评价反馈
@Service
public class FeedbackServiceImpl implements FeedbackService {
@Autowired
private FeedbackMapper feedbackMapper;
@Autowired
private ActivityMapper activityMapper;
public Result submitFeedback(FeedbackDTO feedbackDTO) {
// 1. 验证活动状态
Activity activity = activityMapper.selectById(feedbackDTO.getActivityId());
if (activity == null || !ActivityStatus.FINISHED.equals(activity.getStatus())) {
return Result.fail("活动未结束,暂不能评价");
}
// 2. 检查是否已评价
if (feedbackMapper.existsByActivityAndUser(
feedbackDTO.getActivityId(),
feedbackDTO.getUserId())) {
return Result.fail("已提交过评价");
}
// 3. 保存评价
Feedback feedback = new Feedback();
BeanUtils.copyProperties(feedbackDTO, feedback);
feedback.setCreateTime(new Date());
feedbackMapper.insert(feedback);
// 4. 更新活动评分
updateActivityRating(feedbackDTO.getActivityId());
return Result.ok();
}
private void updateActivityRating(Long activityId) {
// 计算平均分
Double avgRating = feedbackMapper.calculateAverageRating(activityId);
// 更新活动评分
Activity activity = new Activity();
activity.setId(activityId);
activity.setRating(avgRating);
activityMapper.updateById(activity);
}
}
5. 相关数据表设计
-- 活动评价表
CREATE TABLE `feedback` (
`id` bigint NOT NULL AUTO_INCREMENT,
`activity_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`rating` int NOT NULL COMMENT '评分1-5',
`content` text COMMENT '评价内容',
`images` varchar(500) COMMENT '图片地址,多个逗号分隔',
`create_time` datetime,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_activity_user` (`activity_id`,`user_id`)
);
-- 签到记录表
CREATE TABLE `checkin` (
`id` bigint NOT NULL AUTO_INCREMENT,
`activity_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`checkin_time` datetime NOT NULL,
`latitude` decimal(10,6),
`longitude` decimal(10,6),
`location_description` varchar(200),
PRIMARY KEY (`id`),
KEY `idx_activity_user` (`activity_id`,`user_id`)
);
-- 志愿时长记录表
CREATE TABLE `volunteer_hours` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`activity_id` bigint NOT NULL,
`hours` decimal(5,2) NOT NULL,
`record_date` date NOT NULL,
`status` tinyint NOT NULL COMMENT '0-待审核 1-已审核 2-已驳回',
`create_time` datetime,
`audit_time` datetime,
`audit_user_id` bigint,
`audit_remark` varchar(200),
PRIMARY KEY (`id`),
KEY `idx_user_date` (`user_id`,`record_date`)
);
校园志愿者服务平台功能详解(二)
一、志愿时长管理模块
1. 时长统计服务
@Service
public class VolunteerHoursServiceImpl implements VolunteerHoursService {
@Autowired
private VolunteerHoursMapper hoursMapper;
@Autowired
private CheckinMapper checkinMapper;
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void calculateDailyHours() {
// 获取昨天的日期
LocalDate yesterday = LocalDate.now().minusDays(1);
// 获取昨天的所有签到记录
List<Checkin> checkins = checkinMapper.selectByDate(yesterday);
for (Checkin checkin : checkins) {
// 计算志愿时长
BigDecimal hours = calculateHours(checkin);
// 创建时长记录
VolunteerHours record = new VolunteerHours();
record.setUserId(checkin.getUserId());
record.setActivityId(checkin.getActivityId());
record.setHours(hours);
record.setRecordDate(yesterday);
record.setStatus(HoursStatus.PENDING);
record.setCreateTime(new Date());
hoursMapper.insert(record);
}
}
private BigDecimal calculateHours(Checkin checkin) {
// 获取活动信息
Activity activity = activityMapper.selectById(checkin.getActivityId());
// 计算实际参与时长
Duration duration = Duration.between(
checkin.getCheckinTime().toInstant(),
activity.getEndTime().toInstant()
);
// 转换为小时,保留一位小数
return BigDecimal.valueOf(duration.toMinutes())
.divide(BigDecimal.valueOf(60), 1, RoundingMode.HALF_UP);
}
}
2. 时长审核功能
@Service
public class HoursAuditServiceImpl implements HoursAuditService {
@Autowired
private VolunteerHoursMapper hoursMapper;
@Autowired
private MessageService messageService;
@Transactional
public Result auditHours(HoursAuditDTO auditDTO) {
// 批量获取时长记录
List<VolunteerHours> hoursList = hoursMapper.selectBatchIds(auditDTO.getHoursIds());
// 验证所有记录是否都是待审核状态
if (!validateStatus(hoursList)) {
return Result.fail("存在已审核的记录");
}
// 批量更新审核状态
Date now = new Date();
for (VolunteerHours hours : hoursList) {
hours.setStatus(auditDTO.getStatus());
hours.setAuditUserId(auditDTO.getAuditorId());
hours.setAuditTime(now);
hours.setAuditRemark(auditDTO.getRemark());
hoursMapper.updateById(hours);
// 发送审核结果通知
messageService.sendHoursAuditNotification(hours);
}
return Result.ok();
}
private boolean validateStatus(List<VolunteerHours> hoursList) {
return hoursList.stream()
.allMatch(hours -> HoursStatus.PENDING.equals(hours.getStatus()));
}
}
3. 志愿者排行榜
@Service
public class VolunteerRankServiceImpl implements VolunteerRankService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private VolunteerHoursMapper hoursMapper;
private static final String RANK_KEY = "volunteer:rank";
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点更新排行榜
public void updateRankList() {
// 清除旧的排行榜数据
redisTemplate.delete(RANK_KEY);
// 获取志愿者总时长
List<VolunteerRankVO> rankList = hoursMapper.selectTotalHoursByUser();
// 更新Redis排行榜
for (VolunteerRankVO rank : rankList) {
redisTemplate.opsForZSet().add(RANK_KEY,
rank.getUserId().toString(),
rank.getTotalHours().doubleValue());
}
}
public List<VolunteerRankVO> getTopRanks(int limit) {
// 获取排名前N的志愿者
Set<ZSetOperations.TypedTuple<String>> topSet =
redisTemplate.opsForZSet().reverseRangeWithScores(RANK_KEY, 0, limit - 1);
// 转换为VO对象
List<VolunteerRankVO> result = new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<String> item : topSet) {
VolunteerRankVO vo = new VolunteerRankVO();
vo.setUserId(Long.valueOf(item.getValue()));
vo.setTotalHours(BigDecimal.valueOf(item.getScore()));
vo.setRank(rank++);
// 获取用户信息
User user = userMapper.selectById(vo.getUserId());
vo.setUsername(user.getUsername());
vo.setRealName(user.getRealName());
result.add(vo);
}
return result;
}
}
二、消息通知模块
1. 消息服务实现
@Service
public class MessageServiceImpl implements MessageService {
@Autowired
private MessageMapper messageMapper;
@Autowired
private WebSocketService webSocketService;
@Async
public void sendActivityReminder(Activity activity) {
// 获取所有报名用户
List<Long> userIds = registrationMapper.selectUserIdsByActivity(activity.getId());
// 创建消息内容
Message message = new Message();
message.setType(MessageType.ACTIVITY_REMINDER);
message.setTitle("活动提醒");
message.setContent(String.format("活动【%s】将在%s开始,请准时参加!",
activity.getTitle(),
DateUtil.format(activity.getStartTime(), "MM-dd HH:mm")));
// 批量保存消息
List<Message> messages = userIds.stream()
.map(userId -> {
Message userMessage = new Message();
BeanUtils.copyProperties(message, userMessage);
userMessage.setUserId(userId);
userMessage.setCreateTime(new Date());
return userMessage;
})
.collect(Collectors.toList());
messageMapper.insertBatch(messages);
// 实时推送消息
messages.forEach(msg ->
webSocketService.sendMessage(msg.getUserId(), msg));
}
}
2. WebSocket消息推送
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
private static final Map<Long, Session> SESSION_MAP = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("userId") Long userId) {
SESSION_MAP.put(userId, session);
}
@OnClose
public void onClose(@PathParam("userId") Long userId) {
SESSION_MAP.remove(userId);
}
public void sendMessage(Long userId, Object message) {
Session session = SESSION_MAP.get(userId);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(
JsonUtil.toJsonString(message));
} catch (IOException e) {
log.error("发送消息失败", e);
}
}
}
}
3. 系统公告管理
@Service
public class AnnouncementServiceImpl implements AnnouncementService {
@Autowired
private AnnouncementMapper announcementMapper;
@Autowired
private MessageService messageService;
@RequiresRole("ADMIN")
public Result publishAnnouncement(AnnouncementDTO dto) {
// 创建公告
Announcement announcement = new Announcement();
BeanUtils.copyProperties(dto, announcement);
announcement.setStatus(AnnouncementStatus.PUBLISHED);
announcement.setCreateTime(new Date());
announcementMapper.insert(announcement);
// 如果需要推送通知
if (dto.getNeedNotify()) {
Message message = new Message();
message.setType(MessageType.SYSTEM_ANNOUNCEMENT);
message.setTitle("系统公告");
message.setContent(dto.getContent());
message.setRelatedId(announcement.getId());
// 获取所有活跃用户
List<Long> activeUserIds = userMapper.selectActiveUserIds();
// 批量发送通知
messageService.batchSendMessage(activeUserIds, message);
}
return Result.ok();
}
}
4. 站内信管理
@Service
public class InboxServiceImpl implements InboxService {
@Autowired
private MessageMapper messageMapper;
public Result getUnreadMessages(Long userId) {
// 获取未读消息
List<Message> messages = messageMapper.selectUnreadByUser(userId);
// 转换为VO对象
List<MessageVO> voList = messages.stream()
.map(message -> {
MessageVO vo = new MessageVO();
BeanUtils.copyProperties(message, vo);
// 根据消息类型处理额外信息
enrichMessageInfo(vo);
return vo;
})
.collect(Collectors.toList());
return Result.ok(voList);
}
@Transactional
public Result markAsRead(List<Long> messageIds, Long userId) {
// 验证消息归属
if (!validateMessageOwnership(messageIds, userId)) {
return Result.fail("无效的消息ID");
}
// 批量更新已读状态
messageMapper.updateReadStatus(messageIds, MessageStatus.READ);
return Result.ok();
}
private void enrichMessageInfo(MessageVO vo) {
switch (vo.getType()) {
case MessageType.ACTIVITY_REMINDER:
Activity activity = activityMapper.selectById(vo.getRelatedId());
vo.setExtra(ActivityVO.from(activity));
break;
case MessageType.SYSTEM_ANNOUNCEMENT:
Announcement announcement = announcementMapper.selectById(vo.getRelatedId());
vo.setExtra(AnnouncementVO.from(announcement));
break;
// 其他消息类型处理...
}
}
}
5. 相关数据表设计
-- 消息表
CREATE TABLE `message` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`type` tinyint NOT NULL COMMENT '消息类型:1-活动提醒 2-系统公告 3-志愿时长审核',
`title` varchar(100) NOT NULL,
`content` text NOT NULL,
`status` tinyint NOT NULL COMMENT '状态:0-未读 1-已读',
`related_id` bigint COMMENT '关联ID',
`create_time` datetime NOT NULL,
`read_time` datetime,
PRIMARY KEY (`id`),
KEY `idx_user_status` (`user_id`, `status`)
);
-- 系统公告表
CREATE TABLE `announcement` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`content` text NOT NULL,
`status` tinyint NOT NULL COMMENT '状态:0-草稿 1-已发布',
`publisher_id` bigint NOT NULL,
`publish_time` datetime,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
);
-- 消息模板表
CREATE TABLE `message_template` (
`id` bigint NOT NULL AUTO_INCREMENT,
`template_code` varchar(50) NOT NULL,
`template_name` varchar(100) NOT NULL,
`content` text NOT NULL,
`params` varchar(500) COMMENT '参数说明',
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`template_code`)
);
标签:Java,服务平台,public,全栈,Result,activity,return,NULL,id
From: https://blog.csdn.net/exlink2012/article/details/145105951