Bootstrap

Java全栈项目 - 校园资源共享平台开发实践

项目简介

校园资源共享平台是一个面向高校师生的综合性资源交流平台,旨在促进校园内各类资源的有效流通与共享。本项目采用主流的Java全栈技术栈进行开发,实现了资源上传下载、在线交流、信息发布等核心功能。

技术架构

后端技术栈

  • Spring Boot 2.5.x:应用开发框架
  • Spring Security:认证和权限控制
  • MyBatis Plus:ORM框架
  • Redis:缓存服务
  • MySQL 8.0:关系型数据库
  • ElasticSearch:全文检索引擎
  • MinIO:分布式文件存储

前端技术栈

  • Vue 3:前端框架
  • Element Plus:UI组件库
  • Axios:HTTP客户端
  • Vuex:状态管理
  • Vue Router:路由管理

核心功能模块

1. 用户管理模块

  • 用户注册与登录
  • 角色权限管理
  • 个人信息维护
  • 用户行为分析

2. 资源管理模块

  • 资源上传与下载
  • 资源分类管理
  • 资源审核流程
  • 资源评分与评论

3. 社区交流模块

  • 话题讨论
  • 即时消息
  • 资源分享圈
  • 用户互动功能

4. 搜索服务模块

  • 全文检索
  • 智能推荐
  • 热门排行
  • 标签管理

项目亮点

1. 分布式文件存储

@Service
public class FileServiceImpl implements FileService {
    @Autowired
    private MinioClient minioClient;
    
    @Override
    public String uploadFile(MultipartFile file) {
        try {
            String bucketName = "resources";
            String fileName = generateFileName(file);
            
            // 上传文件到MinIO
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build()
            );
            
            return fileName;
        } catch (Exception e) {
            throw new BusinessException("文件上传失败");
        }
    }
}

2. 全文检索实现

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    
    @Override
    public Page<ResourceDTO> searchResources(String keyword, Pageable pageable) {
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "description", "tags"))
            .withPageable(pageable)
            .build();
            
        return elasticsearchTemplate.search(searchQuery, ResourceDTO.class);
    }
}

3. 缓存优化

@Service
public class ResourceServiceImpl implements ResourceService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    @Cacheable(value = "resource", key = "#id")
    public ResourceVO getResourceById(Long id) {
        // 从数据库获取资源信息
        Resource resource = resourceMapper.selectById(id);
        if (resource == null) {
            throw new BusinessException("资源不存在");
        }
        
        return convertToVO(resource);
    }
}

性能优化

1. 数据库优化

  • 建立合适的索引
  • 分页查询优化
  • SQL语句优化
  • 读写分离

2. 缓存策略

  • 热点数据缓存
  • 分布式缓存
  • 缓存预热
  • 缓存更新策略

3. 并发处理

  • 线程池管理
  • 异步处理
  • 限流措施
  • 分布式锁

项目部署

1. 容器化部署

version: '3'
services:
  app:
    image: campus-share:latest
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
      - elasticsearch

2. CI/CD流程

  • 代码提交触发自动构建
  • 单元测试与集成测试
  • 自动部署到测试环境
  • 生产环境发布

项目总结

技术收获

  1. 掌握了完整的Java全栈开发流程
  2. 深入理解分布式系统架构
  3. 提升了性能优化能力
  4. 积累了项目管理经验

改进方向

  1. 引入微服务架构
  2. 优化搜索算法
  3. 增强安全性措施
  4. 提升用户体验

通过本项目的开发,不仅实现了一个功能完善的校园资源共享平台,更重要的是积累了宝贵的实战经验。项目中采用的技术栈和架构设计都是当前企业级应用开发的主流选择,对今后的职业发展具有重要的参考价值。

Java全栈项目 - 校园资源共享平台详细设计

一、用户管理模块

1. 用户注册与登录

1.1 数据库设计
-- 用户表
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `email` varchar(100) NOT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用 1-正常',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 注册功能实现
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserMapper userMapper;
    
    @Override
    @Transactional
    public void register(UserRegisterDTO registerDTO) {
        // 验证用户名和邮箱是否已存在
        validateUserInfo(registerDTO);
        
        // 创建用户实体
        User user = new User();
        user.setUsername(registerDTO.getUsername());
        // 密码加密存储
        user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
        user.setEmail(registerDTO.getEmail());
        
        // 发送验证邮件
        sendVerificationEmail(user.getEmail());
        
        // 保存用户信息
        userMapper.insert(user);
    }
    
    private void validateUserInfo(UserRegisterDTO dto) {
        // 用户名验证
        if (userMapper.countByUsername(dto.getUsername()) > 0) {
            throw new BusinessException("用户名已存在");
        }
        // 邮箱验证
        if (userMapper.countByEmail(dto.getEmail()) > 0) {
            throw new BusinessException("邮箱已被注册");
        }
    }
}
1.3 登录功能实现
@Service
public class AuthServiceImpl implements AuthService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Override
    public LoginResponseDTO login(LoginRequestDTO loginRequest) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
                )
            );
            
            SecurityContextHolder.getContext().setAuthentication(authentication);
            
            // 生成JWT令牌
            String jwt = tokenProvider.generateToken(authentication);
            
            // 记录登录日志
            logUserLogin(loginRequest.getUsername());
            
            return new LoginResponseDTO(jwt);
            
        } catch (AuthenticationException e) {
            throw new BusinessException("用户名或密码错误");
        }
    }
}

2. 角色权限管理

2.1 数据库设计
-- 角色表
CREATE TABLE `role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '角色名称',
  `code` varchar(50) NOT NULL COMMENT '角色编码',
  `description` varchar(200) DEFAULT 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 '权限编码',
  `type` varchar(20) 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`)
);
2.2 权限控制实现
@Service
public class RolePermissionServiceImpl implements RolePermissionService {
    @Autowired
    private RoleMapper roleMapper;
    
    @Override
    @Cacheable(value = "permissions", key = "#userId")
    public Set<String> getUserPermissions(Long userId) {
        // 获取用户角色
        List<Role> roles = roleMapper.selectRolesByUserId(userId);
        
        // 获取角色对应的权限
        return roles.stream()
            .flatMap(role -> roleMapper.selectPermissionsByRoleId(role.getId()).stream())
            .map(Permission::getCode)
            .collect(Collectors.toSet());
    }
}

@RestController
@RequestMapping("/api/admin")
public class AdminController {
    
    @PreAuthorize("hasPermission('system:user:view')")
    @GetMapping("/users")
    public Result<PageVO<UserVO>> getUserList(UserQueryDTO query) {
        return Result.success(userService.getUserList(query));
    }
}

3. 个人信息维护

3.1 个人信息更新
@Service
public class UserProfileServiceImpl implements UserProfileService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private FileService fileService;
    
    @Override
    @Transactional
    public void updateProfile(UserProfileDTO profileDTO) {
        User user = userMapper.selectById(profileDTO.getUserId());
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        
        // 更新基本信息
        user.setNickname(profileDTO.getNickname());
        user.setPhone(profileDTO.getPhone());
        
        // 处理头像上传
        if (profileDTO.getAvatarFile() != null) {
            String avatarUrl = fileService.uploadFile(profileDTO.getAvatarFile());
            user.setAvatar(avatarUrl);
        }
        
        userMapper.updateById(user);
    }
}

4. 用户行为分析

4.1 行为日志记录
@Aspect
@Component
public class UserBehaviorAspect {
    @Autowired
    private UserBehaviorLogMapper behaviorLogMapper;
    
    @Around("@annotation(userBehavior)")
    public Object logUserBehavior(ProceedingJoinPoint point, UserBehavior userBehavior) throws Throwable {
        UserBehaviorLog log = new UserBehaviorLog();
        log.setUserId(SecurityUtils.getCurrentUserId());
        log.setBehaviorType(userBehavior.type());
        log.setOperationTime(new Date());
        
        Object result = point.proceed();
        
        behaviorLogMapper.insert(log);
        return result;
    }
}

二、资源管理模块

1. 资源上传与下载

1.1 数据库设计
-- 资源表
CREATE TABLE `resource` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL COMMENT '资源标题',
  `description` text COMMENT '资源描述',
  `category_id` bigint NOT NULL COMMENT '分类ID',
  `file_url` varchar(255) NOT NULL COMMENT '文件URL',
  `file_size` bigint NOT NULL COMMENT '文件大小',
  `download_count` int DEFAULT '0' COMMENT '下载次数',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待审核 1-已通过 2-已拒绝',
  `creator_id` bigint NOT NULL COMMENT '创建者ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_category` (`category_id`),
  KEY `idx_creator` (`creator_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 资源上传实现
@Service
public class ResourceServiceImpl implements ResourceService {
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private ResourceMapper resourceMapper;
    
    @Override
    @Transactional
    public void uploadResource(ResourceUploadDTO uploadDTO) {
        // 文件上传到MinIO
        String fileUrl = uploadFile(uploadDTO.getFile());
        
        // 保存资源信息
        Resource resource = new Resource();
        resource.setTitle(uploadDTO.getTitle());
        resource.setDescription(uploadDTO.getDescription());
        resource.setCategoryId(uploadDTO.getCategoryId());
        resource.setFileUrl(fileUrl);
        resource.setFileSize(uploadDTO.getFile().getSize());
        resource.setCreatorId(SecurityUtils.getCurrentUserId());
        
        resourceMapper.insert(resource);
        
        // 发送审核通知
        notifyReviewers(resource.getId());
    }
    
    private String uploadFile(MultipartFile file) {
        try {
            String fileName = generateFileName(file);
            String bucketName = "resources";
            
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build()
            );
            
            return String.format("/%s/%s", bucketName, fileName);
        } catch (Exception e) {
            throw new BusinessException("文件上传失败", e);
        }
    }
}
1.3 资源下载实现
@Service
public class ResourceDownloadServiceImpl implements ResourceDownloadService {
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private ResourceMapper resourceMapper;
    
    @Override
    public void downloadResource(Long resourceId, HttpServletResponse response) {
        Resource resource = resourceMapper.selectById(resourceId);
        if (resource == null) {
            throw new BusinessException("资源不存在");
        }
        
        try {
            // 获取文件流
            GetObjectResponse objectResponse = minioClient.getObject(
                GetObjectArgs.builder()
                    .bucket("resources")
                    .object(getObjectName(resource.getFileUrl()))
                    .build()
            );
            
            // 设置响应头
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", 
                "attachment;filename=" + URLEncoder.encode(resource.getTitle(), "UTF-8"));
            
            // 写入响应流
            IOUtils.copy(objectResponse, response.getOutputStream());
            
            // 更新下载次数
            resourceMapper.incrementDownloadCount(resourceId);
            
        } catch (Exception e) {
            throw new BusinessException("文件下载失败", e);
        }
    }
}

2. 资源分类管理

2.1 数据库设计
-- 资源分类表
CREATE TABLE `resource_category` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '分类名称',
  `parent_id` bigint DEFAULT NULL COMMENT '父分类ID',
  `sort_order` int DEFAULT '0' COMMENT '排序号',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用 1-正常',
  PRIMARY KEY (`id`),
  KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 分类管理实现
@Service
public class CategoryServiceImpl implements CategoryService {
    @Autowired
    private CategoryMapper categoryMapper;
    
    @Override
    @Cacheable(value = "categories", key = "'tree'")
    public List<CategoryTreeVO> getCategoryTree() {
        List<ResourceCategory> categories = categoryMapper.selectList(null);
        return buildCategoryTree(categories, null);
    }
    
    private List<CategoryTreeVO> buildCategoryTree(List<ResourceCategory> categories, Long parentId) {
        return categories.stream()
            .filter(cat -> Objects.equals(cat.getParentId(), parentId))
            .map(cat -> {
                CategoryTreeVO vo = new CategoryTreeVO();
                vo.setId(cat.getId());
                vo.setName(cat.getName());
                vo.setChildren(buildCategoryTree(categories, cat.getId()));
                return vo;
            })
            .sorted(Comparator.comparing(CategoryTreeVO::getSortOrder))
            .collect(Collectors.toList());
    }
}

3. 资源审核流程

3.1 数据库设计
-- 资源审核记录表
CREATE TABLE `resource_review` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `resource_id` bigint NOT NULL COMMENT '资源ID',
  `reviewer_id` bigint NOT NULL COMMENT '审核人ID',
  `status` tinyint NOT NULL COMMENT '审核状态:0-待审核 1-通过 2-拒绝',
  `comment` varchar(500) DEFAULT NULL COMMENT '审核意见',
  `review_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_resource` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 审核流程实现
@Service
public class ResourceReviewServiceImpl implements ResourceReviewService {
    @Autowired
    private ResourceMapper resourceMapper;
    @Autowired
    private ResourceReviewMapper reviewMapper;
    @Autowired
    private MessageService messageService;
    
    @Override
    @Transactional
    public void reviewResource(ResourceReviewDTO reviewDTO) {
        // 创建审核记录
        ResourceReview review = new ResourceReview();
        review.setResourceId(reviewDTO.getResourceId());
        review.setReviewerId(SecurityUtils.getCurrentUserId());
        review.setStatus(reviewDTO.getStatus());
        review.setComment(reviewDTO.getComment());
        
        reviewMapper.insert(review);
        
        // 更新资源状态
        Resource resource = resourceMapper.selectById(reviewDTO.getResourceId());
        resource.setStatus(reviewDTO.getStatus());
        resourceMapper.updateById(resource);
        
        // 发送审核结果通知
        sendReviewNotification(resource, review);
    }
    
    private void sendReviewNotification(Resource resource, ResourceReview review) {
        MessageDTO message = new MessageDTO();
        message.setUserId(resource.getCreatorId());
        message.setTitle("资源审核结果通知");
        message.setContent(String.format("您上传的资源《%s》%s", 
            resource.getTitle(),
            review.getStatus() == 1 ? "已通过审核" : "未通过审核"));
        
        messageService.sendMessage(message);
    }
}

4. 资源评分与评论

4.1 数据库设计
-- 资源评分表
CREATE TABLE `resource_rating` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `resource_id` bigint NOT NULL COMMENT '资源ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `score` int NOT NULL COMMENT '评分(1-5)',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_resource_user` (`resource_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 资源评论表
CREATE TABLE `resource_comment` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `resource_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',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_resource` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 评分评论实现
@Service
public class ResourceRatingServiceImpl implements ResourceRatingService {
    @Autowired
    private ResourceRatingMapper ratingMapper;
    @Autowired
    private ResourceCommentMapper commentMapper;
    
    @Override
    @Transactional
    public void rateResource(ResourceRatingDTO ratingDTO) {
        // 检查是否已评分
        if (ratingMapper.exists(ratingDTO.getResourceId(), ratingDTO.getUserId())) {
            throw new BusinessException("您已经评分过该资源");
        }
        
        // 保存评分
        ResourceRating rating = new ResourceRating();
        rating.setResourceId(ratingDTO.getResourceId());
        rating.setUserId(ratingDTO.getUserId());
        rating.setScore(ratingDTO.getScore());
        
        ratingMapper.insert(rating);
        
        // 更新资源平均分
        updateResourceAverageScore(ratingDTO.getResourceId());
    }
    
    @Override
    public void addComment(ResourceCommentDTO commentDTO) {
        ResourceComment comment = new ResourceComment();
        comment.setResourceId(commentDTO.getResourceId());
        comment.setUserId(SecurityUtils.getCurrentUserId());
        comment.setContent(commentDTO.getContent());
        comment.setParentId(commentDTO.getParentId());
        
        commentMapper.insert(comment);
        
        // 如果是回复评论,发送通知
        if (comment.getParentId() != null) {
            sendCommentNotification(comment);
        }
    }
    
    @Override
    @Cacheable(value = "comments", key = "#resourceId")
    public List<CommentTreeVO> getResourceComments(Long resourceId) {
        List<ResourceComment> comments = commentMapper.selectByResourceId(resourceId);
        return buildCommentTree(comments, null);
    }
}

Java全栈项目 - 校园资源共享平台详细设计(续)

三、社区交流模块

1. 话题讨论

1.1 数据库设计
-- 话题表
CREATE TABLE `topic` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL COMMENT '话题标题',
  `content` text NOT NULL COMMENT '话题内容',
  `user_id` bigint NOT NULL COMMENT '发布者ID',
  `category_id` bigint NOT NULL COMMENT '话题分类ID',
  `view_count` int DEFAULT '0' COMMENT '浏览次数',
  `like_count` int DEFAULT '0' COMMENT '点赞数',
  `comment_count` int DEFAULT '0' COMMENT '评论数',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用 1-正常',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_category` (`category_id`),
  KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 话题评论表
CREATE TABLE `topic_comment` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `topic_id` bigint NOT NULL COMMENT '话题ID',
  `content` text NOT NULL COMMENT '评论内容',
  `user_id` bigint NOT NULL COMMENT '评论者ID',
  `parent_id` bigint DEFAULT NULL COMMENT '父评论ID',
  `like_count` int DEFAULT '0' COMMENT '点赞数',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_topic` (`topic_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 话题管理实现
@Service
public class TopicServiceImpl implements TopicService {
    @Autowired
    private TopicMapper topicMapper;
    @Autowired
    private TopicCommentMapper commentMapper;
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    
    @Override
    @Transactional
    public void createTopic(TopicCreateDTO topicDTO) {
        Topic topic = new Topic();
        topic.setTitle(topicDTO.getTitle());
        topic.setContent(topicDTO.getContent());
        topic.setUserId(SecurityUtils.getCurrentUserId());
        topic.setCategoryId(topicDTO.getCategoryId());
        
        topicMapper.insert(topic);
        
        // 同步到ES
        syncTopicToES(topic);
    }
    
    @Override
    public TopicDetailVO getTopicDetail(Long topicId) {
        Topic topic = topicMapper.selectById(topicId);
        if (topic == null) {
            throw new BusinessException("话题不存在");
        }
        
        // 增加浏览次数
        topicMapper.incrementViewCount(topicId);
        
        // 获取评论树
        List<TopicComment> comments = commentMapper.selectByTopicId(topicId);
        List<CommentTreeVO> commentTree = buildCommentTree(comments);
        
        return TopicDetailVO.builder()
            .topic(topic)
            .comments(commentTree)
            .build();
    }
}

2. 即时消息

2.1 数据库设计
-- 消息表
CREATE TABLE `message` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `from_user_id` bigint NOT NULL COMMENT '发送者ID',
  `to_user_id` bigint NOT NULL COMMENT '接收者ID',
  `content` text NOT NULL COMMENT '消息内容',
  `type` tinyint NOT NULL COMMENT '消息类型:1-文本 2-图片 3-文件',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-未读 1-已读',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_to_user` (`to_user_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 WebSocket消息实现
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    private static final Map<Long, Session> ONLINE_USERS = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") Long userId) {
        ONLINE_USERS.put(userId, session);
    }
    
    @OnMessage
    public void onMessage(String message, Session session) {
        MessageDTO messageDTO = JSON.parseObject(message, MessageDTO.class);
        // 保存消息到数据库
        saveMessage(messageDTO);
        
        // 发送消息给目标用户
        Session targetSession = ONLINE_USERS.get(messageDTO.getToUserId());
        if (targetSession != null) {
            targetSession.getAsyncRemote().sendText(message);
        }
    }
    
    @OnClose
    public void onClose(@PathParam("userId") Long userId) {
        ONLINE_USERS.remove(userId);
    }
}

@Service
public class MessageServiceImpl implements MessageService {
    @Autowired
    private MessageMapper messageMapper;
    
    @Override
    public void sendMessage(MessageDTO messageDTO) {
        Message message = new Message();
        message.setFromUserId(SecurityUtils.getCurrentUserId());
        message.setToUserId(messageDTO.getToUserId());
        message.setContent(messageDTO.getContent());
        message.setType(messageDTO.getType());
        
        messageMapper.insert(message);
    }
    
    @Override
    public List<MessageVO> getUnreadMessages(Long userId) {
        return messageMapper.selectUnreadMessages(userId);
    }
}

3. 资源分享圈

3.1 数据库设计
-- 分享动态表
CREATE TABLE `share_post` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `content` text NOT NULL COMMENT '动态内容',
  `resource_id` bigint DEFAULT NULL COMMENT '关联资源ID',
  `images` json DEFAULT NULL COMMENT '图片列表',
  `like_count` int DEFAULT '0' COMMENT '点赞数',
  `comment_count` int DEFAULT '0' COMMENT '评论数',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 分享圈实现
@Service
public class SharePostServiceImpl implements SharePostService {
    @Autowired
    private SharePostMapper postMapper;
    @Autowired
    private FileService fileService;
    
    @Override
    @Transactional
    public void createPost(SharePostDTO postDTO) {
        SharePost post = new SharePost();
        post.setUserId(SecurityUtils.getCurrentUserId());
        post.setContent(postDTO.getContent());
        post.setResourceId(postDTO.getResourceId());
        
        // 处理图片上传
        if (!CollectionUtils.isEmpty(postDTO.getImages())) {
            List<String> imageUrls = postDTO.getImages().stream()
                .map(fileService::uploadFile)
                .collect(Collectors.toList());
            post.setImages(JSON.toJSONString(imageUrls));
        }
        
        postMapper.insert(post);
    }
    
    @Override
    public PageVO<SharePostVO> getSharePosts(SharePostQueryDTO queryDTO) {
        // 分页查询
        Page<SharePost> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
        IPage<SharePost> postPage = postMapper.selectPage(page, queryDTO);
        
        // 转换为VO
        List<SharePostVO> postVOs = postPage.getRecords().stream()
            .map(this::convertToVO)
            .collect(Collectors.toList());
            
        return new PageVO<>(postVOs, postPage.getTotal());
    }
}

4. 用户互动功能

4.1 数据库设计
-- 用户关注表
CREATE TABLE `user_follow` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '关注者ID',
  `follow_user_id` bigint NOT NULL COMMENT '被关注者ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_follow` (`user_id`,`follow_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 点赞表
CREATE TABLE `user_like` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `target_id` bigint NOT NULL COMMENT '目标ID',
  `type` tinyint NOT NULL COMMENT '类型:1-话题 2-评论 3-分享',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_target` (`user_id`,`target_id`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 互动功能实现
@Service
public class UserInteractionServiceImpl implements UserInteractionService {
    @Autowired
    private UserFollowMapper followMapper;
    @Autowired
    private UserLikeMapper likeMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    public void followUser(Long followUserId) {
        Long userId = SecurityUtils.getCurrentUserId();
        
        UserFollow follow = new UserFollow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        
        followMapper.insert(follow);
        
        // 更新关注计数
        String followCountKey = "user:follow:count:" + followUserId;
        redisTemplate.opsForValue().increment(followCountKey);
    }
    
    @Override
    public void like(UserLikeDTO likeDTO) {
        String likeKey = String.format("like:%d:%d:%d", 
            likeDTO.getType(), likeDTO.getTargetId(), SecurityUtils.getCurrentUserId());
            
        if (Boolean.TRUE.equals(redisTemplate.hasKey(likeKey))) {
            throw new BusinessException("您已经点赞过了");
        }
        
        UserLike like = new UserLike();
        like.setUserId(SecurityUtils.getCurrentUserId());
        like.setTargetId(likeDTO.getTargetId());
        like.setType(likeDTO.getType());
        
        likeMapper.insert(like);
        
        // 缓存点赞状态
        redisTemplate.opsForValue().set(likeKey, "1", 7, TimeUnit.DAYS);
        
        // 更新点赞计数
        updateLikeCount(likeDTO.getType(), likeDTO.getTargetId());
    }
}

四、搜索服务模块

1. 全文检索

1.1 ES索引设计
{
  "mappings": {
    "properties": {
      "id": { "type": "long" },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "tags": {
        "type": "keyword"
      },
      "categoryId": { "type": "long" },
      "userId": { "type": "long" },
      "createTime": { "type": "date" }
    }
  }
}
1.2 搜索实现
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    
    @Override
    public SearchResultVO search(SearchDTO searchDTO) {
        // 构建查询条件
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.hasText(searchDTO.getKeyword())) {
            queryBuilder.must(QueryBuilders.multiMatchQuery(
                searchDTO.getKeyword(),
                "title^3",  // 标题权重更高
                "content"
            ));
        }
        
        // 分类过滤
        if (searchDTO.getCategoryId() != null) {
            queryBuilder.filter(QueryBuilders.termQuery("categoryId", searchDTO.getCategoryId()));
        }
        
        // 标签过滤
        if (!CollectionUtils.isEmpty(searchDTO.getTags())) {
            queryBuilder.filter(QueryBuilders.termsQuery("tags", searchDTO.getTags()));
        }
        
        // 构建搜索请求
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(queryBuilder)
            .withPageable(PageRequest.of(searchDTO.getPageNum(), searchDTO.getPageSize()))
            .withHighlightFields(
                new HighlightBuilder.Field("title"),
                new HighlightBuilder.Field("content")
            )
            .build();
            
        // 执行搜索
        SearchHits<ResourceDocument> searchHits = elasticsearchTemplate.search(
            searchQuery, 
            ResourceDocument.class
        );
        
        // 处理高亮结果
        List<SearchResultVO.Item> items = searchHits.getSearchHits().stream()
            .map(this::convertToSearchItem)
            .collect(Collectors.toList());
            
        return new SearchResultVO(items, searchHits.getTotalHits());
    }
}

2. 智能推荐

2.1 数据库设计
-- 用户行为表
CREATE TABLE `user_behavior` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `resource_id` bigint NOT NULL COMMENT '资源ID',
  `behavior_type` tinyint NOT NULL COMMENT '行为类型:1-浏览 2-下载 3-收藏',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user` (`user_id`),
  KEY `idx_resource` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 推荐系统实现
@Service
public class RecommendationServiceImpl implements RecommendationService {
    @Autowired
    private UserBehaviorMapper behaviorMapper;
    @Autowired
    private ResourceMapper resourceMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    @Scheduled(cron = "0 0 3 * * ?")  // 每天凌晨3点执行
    public void generateRecommendations() {
        // 获取用户行为数据
        List<UserBehavior> behaviors = behaviorMapper.selectRecentBehaviors(7);  // 最近7天
        
        // 计算用户-资源评分矩阵
        Map<Long, Map<Long, Double>> userResourceMatrix = calculateUserResourceMatrix(behaviors);
        
        // 计算资源相似度矩阵
        Map<Long, Map<Long, Double>> resourceSimilarityMatrix = calculateResourceSimilarity(userResourceMatrix);
        
        // 为每个用户生成推荐列表
        for (Long userId : userResourceMatrix.keySet()) {
            List<RecommendItem> recommendations = generateUserRecommendations(
                userId, 
                userResourceMatrix, 
                resourceSimilarityMatrix
            );
            
            // 存储推荐结果到Redis
            String key = "user:recommend:" + userId;
            redisTemplate.opsForValue().set(key, recommendations, 1, TimeUnit.DAYS);
        }
    }
    
    @Override
    public List<ResourceVO> getUserRecommendations(Long userId) {
        String key = "user:recommend:" + userId;
        List<RecommendItem> recommendations = (List<RecommendItem>) redisTemplate.opsForValue().get(key);
        
        if (recommendations == null) {
            return Collections.emptyList();
        }
        
        // 获取推荐资源详情
        return recommendations.stream()
            .map(item -> resourceMapper.selectById(item.getResourceId()))
            .map(this::convertToVO)
            .collect(Collectors.toList());
    }
}

3. 热门排行

3.1 排行榜实现
@Service
public class RankingServiceImpl implements RankingService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ResourceMapper resourceMapper;
    
    private static final String HOT_RESOURCES_KEY = "ranking:hot:resources";
    private static final String DOWNLOAD_RANKING_KEY = "ranking:download";
    
    @Override
    @Scheduled(fixedRate = 300000)  // 每5分钟更新一次
    public void updateHotResourcesRanking() {
        // 计算热度分数
        List<ResourceRankingDTO> rankings = resourceMapper.calculateHotScore();
        
        // 更新Redis排行榜
        String key = HOT_RESOURCES_KEY + ":" + DateUtil.format(new Date(), "yyyyMMdd");
        redisTemplate.delete(key);
        
        for (ResourceRankingDTO ranking : rankings) {
            redisTemplate.opsForZSet().add(
                key,
                ranking.getResourceId(),
                ranking.getScore()
            );
        }
        
        // 设置过期时间
        redisTemplate.expire(key, 2, TimeUnit.DAYS);
    }
    
    @Override
    public List<ResourceVO> getHotResourcesRanking(int limit) {
        String key = HOT_RESOURCES_KEY + ":" + DateUtil.format(new Date(), "yyyyMMdd");
        
        // 获取排行榜数据
        Set<Object> resourceIds = redisTemplate.opsForZSet().reverseRange(key, 0, limit - 1);
        
        if (CollectionUtils.isEmpty(resourceIds)) {
            return Collections.emptyList();
        }
        
        // 获取资源详情
        return resourceIds.stream()
            .map(id -> resourceMapper.selectById((Long) id))
            .map(this::convertToVO)
            .collect(Collectors.toList());
    }
}

4. 标签管理

4.1 数据库设计
-- 标签表
CREATE TABLE `tag` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '标签名称',
  `category_id` bigint DEFAULT NULL COMMENT '分类ID',
  `use_count` int DEFAULT '0' COMMENT '使用次数',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 资源标签关联表
CREATE TABLE `resource_tag` (
  `resource_id` bigint NOT NULL COMMENT '资源ID',
  `tag_id` bigint NOT NULL COMMENT '标签ID',
  PRIMARY KEY (`resource_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 标签管理实现
@Service
public class TagServiceImpl implements TagService {
    @Autowired
    private TagMapper tagMapper;
    @Autowired
    private ResourceTagMapper resourceTagMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    @Cacheable(value = "tags", key = "#categoryId")
    public List<TagVO> getTagsByCategory(Long categoryId) {
        return tagMapper.selectByCategoryId(categoryId);
    }
    
    @Override
    @Transactional
    public void addResourceTags(Long resourceId, List<Long> tagIds) {
        // 删除原有标签
        resourceTagMapper.deleteByResourceId(resourceId);
        
        // 添加新标签
        if (!CollectionUtils.isEmpty(tagIds)) {
            resourceTagMapper.batchInsert(resourceId, tagIds);
            
            // 更新标签使用次数
            tagMapper.incrementUseCounts(tagIds);
        }
        
        // 清除相关缓存
        clearTagCache(resourceId);
    }
    
    @Override
    public List<TagVO> getHotTags(int limit) {
        String key = "tags:hot";
        
        // 从缓存获取热门标签
        List<Object> cachedTags = redisTemplate.opsForList().range(key, 0, limit - 1);
        
        if (!CollectionUtils.isEmpty(cachedTags)) {
            return JSON.parseArray(JSON.toJSONString(cachedTags), TagVO.class);
        }
        
        // 从数据库获取
        List<TagVO> hotTags = tagMapper.selectHotTags(limit);
        
        // 更新缓存
        redisTemplate.opsForList().rightPushAll(key, hotTags);
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
        
        return hotTags;
    }
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;