Java全栈项目实战:校园论坛社交平台开发
项目简介
本文将分享一个基于 Spring Boot + Vue.js 开发的校园论坛社交平台项目的开发经验。该平台旨在为校园师生提供一个交流互动的线上空间,支持发帖、评论、私信等核心社交功能。
技术栈
后端
- Spring Boot 2.x
- Spring Security
- MyBatis Plus
- Redis
- MySQL 8.0
- ElasticSearch
前端
- Vue.js 2.x
- Element UI
- Axios
- Vuex
- Vue Router
核心功能模块
1. 用户管理
- 注册/登录
- 个人信息管理
- 头像上传
- 权限控制
2. 帖子管理
- 发布帖子
- 帖子分类
- 帖子点赞/收藏
- 富文本编辑器
- 图片上传
3. 评论系统
- 发表评论
- 回复评论
- 评论点赞
- 楼中楼回复
4. 私信系统
- 发送私信
- 私信列表
- 未读消息提醒
- 实时通知
5. 搜索功能
- 全文检索
- 热门话题
- 智能推荐
技术难点解决方案
1. 性能优化
// 使用 Redis 缓存热门帖子
@Cacheable(value = "hot_posts", key = "#page")
public List<Post> getHotPosts(int page) {
// 从缓存获取数据
// 如果缓存未命中则查询数据库
}
2. 消息队列应用
// 使用 RabbitMQ 处理点赞消息
@RabbitListener(queues = "like.queue")
public void handleLikeMessage(LikeMessage message) {
// 异步处理点赞逻辑
likeService.processLike(message);
}
3. 分布式会话管理
// 使用 Redis 存储会话信息
@Configuration
public class SessionConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate() {
// Redis 配置
}
}
项目亮点
-
高并发处理
- 使用 Redis 缓存热点数据
- 采用分布式锁处理并发请求
- 实现请求限流保护
-
实时通讯
- WebSocket 实现即时消息推送
- 心跳检测保持连接稳定
- 离线消息处理机制
-
搜索优化
- ElasticSearch 全文检索
- 分词器优化
- 搜索结果权重排序
项目部署
1. 容器化部署
version: '3'
services:
backend:
image: campus-forum-backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
image: campus-forum-frontend
ports:
- "80:80"
2. 性能监控
- 使用 Spring Boot Admin 监控应用
- 集成 Prometheus + Grafana 监控系统指标
- 接入 ELK 日志分析平台
开发心得
-
技术选型
- 选择稳定可靠的主流技术栈
- 考虑团队技术水平和学习成本
- 预留技术升级空间
-
项目管理
- 采用 Git Flow 工作流
- 制定代码规范
- 持续集成/持续部署
-
用户体验
- 注重页面交互设计
- 优化加载速度
- 完善错误处理
后续优化方向
- 引入微服务架构
- 优化移动端适配
- 增加更多社交功能
- 提升安全性能
- 优化推荐算法
总结
通过这个项目,不仅实践了全栈开发技能,也深入理解了分布式系统设计、性能优化等重要概念。项目开发过程中遇到的问题和解决方案,都为今后的开发工作提供了宝贵经验。
Java全栈项目:校园论坛社交平台核心功能详细设计
一、用户管理模块
1. 用户注册与登录
1.1 数据库设计
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(128) NOT NULL COMMENT '密码',
`email` varchar(128) NOT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用 1-正常',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 注册流程
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
public void register(UserRegisterDTO dto) {
// 1. 验证用户名和邮箱是否已存在
validateUserInfo(dto);
// 2. 密码加密
String encodedPassword = passwordEncoder.encode(dto.getPassword());
// 3. 保存用户信息
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(encodedPassword);
user.setEmail(dto.getEmail());
// 4. 发送验证邮件
sendVerificationEmail(dto.getEmail());
userMapper.insert(user);
}
}
1.3 登录认证
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginProcessingUrl("/api/login")
.successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler())
.and()
.csrf().disable();
}
}
2. 个人信息管理
2.1 信息更新接口
@RestController
@RequestMapping("/api/user")
public class UserController {
@PutMapping("/profile")
public Result updateProfile(@RequestBody UserProfileDTO dto) {
// 更新用户信息
userService.updateProfile(dto);
return Result.success();
}
@PutMapping("/password")
public Result updatePassword(@RequestBody PasswordUpdateDTO dto) {
// 修改密码
userService.updatePassword(dto);
return Result.success();
}
}
2.2 头像上传
@Service
public class FileService {
@Value("${upload.path}")
private String uploadPath;
public String uploadAvatar(MultipartFile file) {
// 1. 校验文件类型和大小
validateImage(file);
// 2. 生成文件名
String fileName = generateFileName(file);
// 3. 保存文件
String filePath = uploadPath + fileName;
file.transferTo(new File(filePath));
// 4. 返回访问URL
return getAccessUrl(fileName);
}
}
3. 权限控制
3.1 RBAC模型设计
-- 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '角色名称',
`code` varchar(50) NOT NULL COMMENT '角色编码',
PRIMARY KEY (`id`)
);
-- 权限表
CREATE TABLE `permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '权限名称',
`code` varchar(50) NOT NULL COMMENT '权限编码',
PRIMARY KEY (`id`)
);
-- 用户角色关联表
CREATE TABLE `user_role` (
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`user_id`, `role_id`)
);
3.2 权限注解
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public Result listUsers() {
return Result.success(userService.listUsers());
}
@PreAuthorize("hasPermission(#id, 'POST', 'DELETE')")
@DeleteMapping("/posts/{id}")
public Result deletePost(@PathVariable Long id) {
postService.deletePost(id);
return Result.success();
}
二、帖子管理模块
1. 帖子发布
1.1 数据库设计
CREATE TABLE `post` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '发帖人ID',
`category_id` bigint NOT NULL COMMENT '分类ID',
`title` varchar(255) NOT NULL COMMENT '标题',
`content` text NOT NULL COMMENT '内容',
`status` tinyint DEFAULT '1' COMMENT '状态:0-删除 1-正常',
`view_count` int DEFAULT '0' COMMENT '浏览量',
`like_count` int DEFAULT '0' COMMENT '点赞数',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 发帖接口
@RestController
@RequestMapping("/api/posts")
public class PostController {
@PostMapping
public Result createPost(@RequestBody PostCreateDTO dto) {
// 1. 参数校验
validatePost(dto);
// 2. 处理富文本内容
String content = processRichText(dto.getContent());
// 3. 保存帖子
Post post = new Post();
post.setTitle(dto.getTitle());
post.setContent(content);
post.setCategoryId(dto.getCategoryId());
post.setUserId(getCurrentUserId());
postService.createPost(post);
return Result.success();
}
}
2. 帖子分类管理
2.1 分类树形结构
@Data
public class CategoryTree {
private Long id;
private String name;
private List<CategoryTree> children;
public static List<CategoryTree> buildTree(List<Category> categories) {
// 构建树形结构
Map<Long, CategoryTree> nodeMap = new HashMap<>();
List<CategoryTree> roots = new ArrayList<>();
// 构建节点映射
categories.forEach(category -> {
CategoryTree node = new CategoryTree();
node.setId(category.getId());
node.setName(category.getName());
nodeMap.put(category.getId(), node);
});
// 构建树形关系
categories.forEach(category -> {
if (category.getParentId() == 0) {
roots.add(nodeMap.get(category.getId()));
} else {
CategoryTree parent = nodeMap.get(category.getParentId());
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(nodeMap.get(category.getId()));
}
}
});
return roots;
}
}
3. 点赞/收藏功能
3.1 Redis实现点赞
@Service
public class LikeService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LIKE_KEY = "post:like:";
public void like(Long postId, Long userId) {
String key = LIKE_KEY + postId;
// 判断是否已点赞
Boolean isMember = redisTemplate.opsForSet().isMember(key, userId.toString());
if (Boolean.TRUE.equals(isMember)) {
// 取消点赞
redisTemplate.opsForSet().remove(key, userId.toString());
// 点赞数-1
redisTemplate.opsForValue().decrement(LIKE_COUNT_KEY + postId);
} else {
// 点赞
redisTemplate.opsForSet().add(key, userId.toString());
// 点赞数+1
redisTemplate.opsForValue().increment(LIKE_COUNT_KEY + postId);
}
}
}
三、评论系统
1. 评论数据结构
1.1 数据库设计
CREATE TABLE `comment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`post_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_post_id` (`post_id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 评论树形结构
2.1 评论树构建
@Data
public class CommentTreeNode {
private Long id;
private String content;
private UserInfo user;
private UserInfo replyUser;
private Date createTime;
private Integer likeCount;
private List<CommentTreeNode> children;
public static List<CommentTreeNode> buildTree(List<Comment> comments) {
// 构建评论树
Map<Long, CommentTreeNode> nodeMap = new HashMap<>();
List<CommentTreeNode> roots = new ArrayList<>();
// 转换评论对象
comments.forEach(comment -> {
CommentTreeNode node = convertToNode(comment);
nodeMap.put(comment.getId(), node);
if (comment.getParentId() == null) {
roots.add(node);
} else {
CommentTreeNode parent = nodeMap.get(comment.getParentId());
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(node);
}
}
});
return roots;
}
}
3. 评论分页查询
3.1 分页接口
@Service
public class CommentService {
public PageResult<CommentVO> pageComments(Long postId, Integer pageNum, Integer pageSize) {
// 1. 查询一级评论
Page<Comment> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<Comment>()
.eq(Comment::getPostId, postId)
.isNull(Comment::getParentId)
.orderByDesc(Comment::getCreateTime);
Page<Comment> commentPage = commentMapper.selectPage(page, wrapper);
// 2. 查询子评论
List<Comment> comments = commentPage.getRecords();
if (!comments.isEmpty()) {
List<Long> parentIds = comments.stream()
.map(Comment::getId)
.collect(Collectors.toList());
List<Comment> children = commentMapper.selectList(
new LambdaQueryWrapper<Comment>()
.in(Comment::getParentId, parentIds)
.orderByAsc(Comment::getCreateTime)
);
// 3. 构建评论树
List<CommentTreeNode> trees = CommentTreeNode.buildTree(
Stream.concat(comments.stream(), children.stream())
.collect(Collectors.toList())
);
// 4. 转换为VO
List<CommentVO> vos = convertToVO(trees);
return new PageResult<>(vos, commentPage.getTotal());
}
return new PageResult<>(Collections.emptyList(), 0L);
}
}
4. 评论点赞
4.1 点赞实现
@Service
public class CommentLikeService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String COMMENT_LIKE_KEY = "comment:like:";
@Transactional
public void likeComment(Long commentId, Long userId) {
String key = COMMENT_LIKE_KEY + commentId;
// 1. 检查是否已点赞
Boolean hasLiked = redisTemplate.opsForSet().isMember(key, userId.toString());
if (Boolean.TRUE.equals(hasLiked)) {
// 2. 取消点赞
redisTemplate.opsForSet().remove(key, userId.toString());
// 3. 更新点赞数
commentMapper.decrementLikeCount(commentId);
} else {
// 4. 添加点赞
redisTemplate.opsForSet().add(key, userId.toString());
// 5. 更新点赞数
commentMapper.incrementLikeCount(commentId);
// 6. 发送点赞通知
sendLikeNotification(commentId, userId);
}
}
}
以上是校园论坛社交平台核心模块的详细设计和实现。每个模块都包含了数据库设计、核心业务逻辑实现、性能优化考虑等方面。实际开发中还需要注意:
-
数据安全性:
- 敏感数据加密
- XSS防护
- SQL注入防护
- CSRF防护
-
性能优化:
- 合理使用缓存
- 索引优化
- 大数据量分页优化
-
可扩展性:
- 模块解耦
- 统一异常处理
- 统一返回格式
- 参数校验
-
用户体验:
- 防重复提交
- 友好的错误提示
- 异步处理耗时操作
这些都是实际项目中需要重点考虑的方面。
Java全栈项目:校园论坛私信系统与搜索功能详解
一、私信系统
1. 数据库设计
1.1 私信表设计
-- 私信会话表
CREATE TABLE `message_conversation` (
`id` bigint NOT NULL AUTO_INCREMENT,
`sender_id` bigint NOT NULL COMMENT '发送者ID',
`receiver_id` bigint NOT NULL COMMENT '接收者ID',
`last_message` varchar(255) DEFAULT NULL COMMENT '最后一条消息',
`unread_count` int DEFAULT '0' COMMENT '未读消息数',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sender_receiver` (`sender_id`,`receiver_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 私信内容表
CREATE TABLE `message_content` (
`id` bigint NOT NULL AUTO_INCREMENT,
`conversation_id` bigint NOT NULL COMMENT '会话ID',
`sender_id` bigint NOT NULL COMMENT '发送者ID',
`content` text NOT NULL COMMENT '消息内容',
`type` tinyint DEFAULT '0' COMMENT '消息类型:0-文本 1-图片 2-文件',
`status` tinyint DEFAULT '0' COMMENT '状态:0-未读 1-已读',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_conversation_id` (`conversation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. WebSocket实时通讯
2.1 WebSocket配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(messageHandler(), "/ws/message")
.setAllowedOrigins("*")
.addInterceptors(new WebSocketInterceptor());
}
@Bean
public WebSocketHandler messageHandler() {
return new MessageWebSocketHandler();
}
}
2.2 消息处理器
@Component
public class MessageWebSocketHandler extends TextWebSocketHandler {
private static final Map<Long, WebSocketSession> USER_SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
Long userId = (Long) session.getAttributes().get("userId");
USER_SESSIONS.put(userId, session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
// 处理接收到的消息
MessageDTO messageDTO = JSON.parseObject(message.getPayload(), MessageDTO.class);
messageService.sendMessage(messageDTO);
// 推送消息给接收者
WebSocketSession receiverSession = USER_SESSIONS.get(messageDTO.getReceiverId());
if (receiverSession != null && receiverSession.isOpen()) {
receiverSession.sendMessage(new TextMessage(JSON.toJSONString(messageDTO)));
}
}
}
3. 消息服务实现
3.1 发送私信
@Service
public class MessageService {
@Autowired
private MessageMapper messageMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Transactional
public void sendMessage(MessageDTO dto) {
// 1. 获取或创建会话
MessageConversation conversation = getOrCreateConversation(
dto.getSenderId(),
dto.getReceiverId()
);
// 2. 保存消息内容
MessageContent content = new MessageContent();
content.setConversationId(conversation.getId());
content.setSenderId(dto.getSenderId());
content.setContent(dto.getContent());
content.setType(dto.getType());
messageMapper.insertContent(content);
// 3. 更新会话信息
conversation.setLastMessage(dto.getContent());
conversation.setUnreadCount(conversation.getUnreadCount() + 1);
messageMapper.updateConversation(conversation);
// 4. 发送未读消息提醒
sendUnreadNotification(dto.getReceiverId());
}
}
3.2 私信列表查询
@Service
public class MessageService {
public PageResult<ConversationVO> listConversations(Long userId, Integer pageNum, Integer pageSize) {
// 1. 分页查询会话列表
Page<MessageConversation> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<MessageConversation> wrapper = new LambdaQueryWrapper<MessageConversation>()
.and(w -> w.eq(MessageConversation::getSenderId, userId)
.or()
.eq(MessageConversation::getReceiverId, userId))
.orderByDesc(MessageConversation::getUpdateTime);
Page<MessageConversation> conversationPage = messageMapper.selectPage(page, wrapper);
// 2. 转换为VO对象
List<ConversationVO> vos = conversationPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(vos, conversationPage.getTotal());
}
public List<MessageContentVO> listMessages(Long conversationId, Long userId) {
// 1. 查询消息内容
List<MessageContent> contents = messageMapper.selectList(
new LambdaQueryWrapper<MessageContent>()
.eq(MessageContent::getConversationId, conversationId)
.orderByAsc(MessageContent::getCreateTime)
);
// 2. 更新未读状态
messageMapper.updateMessageStatus(conversationId, userId);
// 3. 转换为VO对象
return contents.stream()
.map(this::convertToContentVO)
.collect(Collectors.toList());
}
}
4. 未读消息提醒
4.1 消息提醒实现
@Service
public class NotificationService {
private static final String UNREAD_COUNT_KEY = "unread:count:";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void sendUnreadNotification(Long userId) {
// 1. 增加未读消息计数
String key = UNREAD_COUNT_KEY + userId;
redisTemplate.opsForValue().increment(key);
// 2. 推送未读提醒
WebSocketSession session = MessageWebSocketHandler.USER_SESSIONS.get(userId);
if (session != null && session.isOpen()) {
NotificationDTO notification = new NotificationDTO();
notification.setType("unread");
notification.setCount(getUnreadCount(userId));
session.sendMessage(new TextMessage(JSON.toJSONString(notification)));
}
}
public Integer getUnreadCount(Long userId) {
String key = UNREAD_COUNT_KEY + userId;
Object count = redisTemplate.opsForValue().get(key);
return count == null ? 0 : (Integer) count;
}
}
二、搜索功能
1. ElasticSearch配置
1.1 索引配置
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient elasticsearchClient() {
return new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
}
// 创建帖子索引
public void createPostIndex() {
CreateIndexRequest request = new CreateIndexRequest("posts");
request.mapping(
"{\n" +
" \"properties\": {\n" +
" \"title\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
" \"content\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
" \"userId\": {\n" +
" \"type\": \"long\"\n" +
" },\n" +
" \"createTime\": {\n" +
" \"type\": \"date\"\n" +
" }\n" +
" }\n" +
"}", XContentType.JSON
);
client.indices().create(request, RequestOptions.DEFAULT);
}
}
2. 全文检索实现
2.1 搜索服务
@Service
public class SearchService {
@Autowired
private RestHighLevelClient client;
public PageResult<PostVO> searchPosts(String keyword, Integer pageNum, Integer pageSize) {
// 1. 构建搜索请求
SearchRequest request = new SearchRequest("posts");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(
QueryBuilders.multiMatchQuery(keyword, "title", "content")
.field("title", 3.0f) // 标题权重更高
);
sourceBuilder.from((pageNum - 1) * pageSize);
sourceBuilder.size(pageSize);
sourceBuilder.highlighter(
new HighlightBuilder()
.field("title")
.field("content")
.preTags("<em>")
.postTags("</em>")
);
request.source(sourceBuilder);
// 2. 执行搜索
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 3. 处理结果
List<PostVO> posts = new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
PostVO post = JSON.parseObject(hit.getSourceAsString(), PostVO.class);
// 设置高亮内容
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (highlightFields.containsKey("title")) {
post.setTitle(highlightFields.get("title").getFragments()[0].string());
}
if (highlightFields.containsKey("content")) {
post.setContent(highlightFields.get("content").getFragments()[0].string());
}
posts.add(post);
}
return new PageResult<>(posts, response.getHits().getTotalHits().value);
}
}
3. 热门话题
3.1 话题热度计算
@Service
public class TopicService {
private static final String TOPIC_SCORE_KEY = "topic:score";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void incrementTopicScore(String topic) {
// 1. 更新话题分数
redisTemplate.opsForZSet().incrementScore(TOPIC_SCORE_KEY, topic, 1);
}
public List<TopicVO> getHotTopics(int limit) {
// 1. 获取热门话题
Set<ZSetOperations.TypedTuple<Object>> typedTuples =
redisTemplate.opsForZSet().reverseRangeWithScores(TOPIC_SCORE_KEY, 0, limit - 1);
// 2. 转换为VO对象
return typedTuples.stream()
.map(tuple -> {
TopicVO vo = new TopicVO();
vo.setName((String) tuple.getValue());
vo.setScore(tuple.getScore());
return vo;
})
.collect(Collectors.toList());
}
}
4. 智能推荐
4.1 基于用户行为的推荐
@Service
public class RecommendService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String USER_VIEW_KEY = "user:view:";
private static final String USER_LIKE_KEY = "user:like:";
public List<PostVO> recommendPosts(Long userId) {
// 1. 获取用户行为数据
Set<Object> viewPosts = redisTemplate.opsForSet().members(USER_VIEW_KEY + userId);
Set<Object> likePosts = redisTemplate.opsForSet().members(USER_LIKE_KEY + userId);
// 2. 获取相似用户
List<Long> similarUsers = findSimilarUsers(userId, viewPosts, likePosts);
// 3. 获取推荐帖子
return getRecommendPosts(userId, similarUsers);
}
private List<Long> findSimilarUsers(Long userId, Set<Object> viewPosts, Set<Object> likePosts) {
// 基于协同过滤算法找到相似用户
// 1. 计算用户行为向量
// 2. 计算用户相似度
// 3. 返回相似度最高的用户列表
return similarUsers;
}
private List<PostVO> getRecommendPosts(Long userId, List<Long> similarUsers) {
// 1. 获取相似用户的帖子
List<Post> candidatePosts = postMapper.selectList(
new LambdaQueryWrapper<Post>()
.in(Post::getUserId, similarUsers)
.orderByDesc(Post::getCreateTime)
);
// 2. 过滤和排序
return filterAndSortPosts(userId, candidatePosts);
}
}
4.2 推荐结果缓存
@Service
public class RecommendService {
private static final String RECOMMEND_CACHE_KEY = "recommend:";
private static final long RECOMMEND_CACHE_TTL = 3600L; // 1小时
public List<PostVO> getCachedRecommendPosts(Long userId) {
String key = RECOMMEND_CACHE_KEY + userId;
// 1. 尝试从缓存获取
List<PostVO> cachedPosts = (List<PostVO>) redisTemplate.opsForValue().get(key);
if (cachedPosts != null) {
return cachedPosts;
}
// 2. 计算推荐结果
List<PostVO> recommendPosts = recommendPosts(userId);
// 3. 存入缓存
redisTemplate.opsForValue().set(key, recommendPosts, RECOMMEND_CACHE_TTL, TimeUnit.SECONDS);
return recommendPosts;
}
}
5. 性能优化
-
搜索优化:
- 使用ES的分词器优化
- 结果缓存
- 异步索引更新
-
实时通讯优化:
- 心跳检测
- 消息压缩
- 连接池管理
-
推荐系统优化:
- 离线计算
- 定时更新
- 多级缓存
6. 安全考虑
-
私信安全:
- 消息加密
- 频率限制
- 敏感词过滤
-
WebSocket安全:
- 身份认证
- 连接限制
- 消息验证
-
数据安全:
- 索引权限控制
- 数据脱敏
- 访问控制
这些功能的实现需要考虑:
-
可扩展性:
- 模块化设计
- 接口抽象
- 配置化管理
-
可维护性:
- 代码规范
- 完善注释
- 异常处理
-
监控告警:
- 性能监控
- 错误日志
- 业务指标
-
测试验证:
- 单元测试
- 压力测试
- 集成测试