温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片!

摘要:本文围绕一个基于 Spring Boot 与 Vue3 的专业知识问答社区系统展开,从需求分析、技术选型、系统架构、数据库设计到核心功能实现与部署上线,完整梳理一套可落地的开发方案。系统采用前后端分离架构,后端基于 Spring Boot 提供 RESTful API,前端使用 Vue3 + Vite 构建单页应用,结合 JWT 鉴权、Redis 缓存、Elasticsearch 全文检索等技术,实现提问、回答、评论、点赞、标签分类、内容审核与消息通知等核心能力。

一、项目背景与目标

在垂直技术领域,开发者往往面临信息分散、检索效率低、答案质量参差不齐等问题。通用搜索引擎难以精准匹配专业问题,而传统论坛又缺乏结构化的问答组织方式。因此,构建一个面向专业知识领域的问答社区,具有明确的实用价值。

本项目的核心目标包括:

  • 结构化问答:以「问题—回答—评论」三层结构组织内容,支持采纳最佳答案。
  • 知识沉淀:通过标签体系与全文检索,让优质内容可被持续复用。
  • 社区治理:提供内容审核、举报、积分与权限机制,保障社区质量。
  • 良好体验:前端采用 Vue3 组合式 API,实现流畅的交互与响应式布局。

二、技术选型

2.1 后端技术栈

技术版本用途
Spring Boot3.2.x应用基础框架,自动装配与内嵌容器
Spring Security6.x认证授权、JWT 过滤器链
MyBatis-Plus3.5.xORM 持久层,简化 CRUD
MySQL8.0核心业务数据存储
Redis7.x缓存、点赞计数、验证码、会话
Elasticsearch8.x问题与回答的全文检索
RabbitMQ3.x异步消息通知与审核任务

2.2 前端技术栈

技术版本用途
Vue3.4.x视图框架,组合式 API
Vite5.x构建工具与开发服务器
Pinia2.x状态管理
Vue Router4.x路由与权限守卫
Element Plus2.xUI 组件库
Axios1.xHTTP 请求封装与拦截器

三、系统架构设计

系统采用前后端分离的分层架构,整体分为表现层、网关层、业务层、数据层与基础设施层。

flowchart TD
    A[Vue3 前端 SPA] --> B[Nginx 静态资源与反向代理]
    B --> C[Spring Boot API 服务]
    C --> D[Spring Security + JWT 鉴权]
    C --> E[业务模块 Service]
    E --> F[(MySQL 主库)]
    E --> G[(Redis 缓存)]
    E --> H[(Elasticsearch 检索)]
    E --> I[RabbitMQ 消息队列]
    I --> J[通知与审核消费者]

各层职责说明:

  • 表现层:Vue3 单页应用,负责页面渲染、路由跳转与交互逻辑。
  • 网关层:Nginx 负责静态资源托管、反向代理与负载均衡。
  • 业务层:Spring Boot 按领域拆分为用户、问题、回答、评论、标签、通知等模块。
  • 数据层:MySQL 存储核心数据,Redis 承担热点缓存与计数,Elasticsearch 提供检索能力。

四、数据库设计

核心表围绕用户与问答内容展开,主要表结构如下:

CREATE TABLE `user` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL UNIQUE,
  `password` VARCHAR(100) NOT NULL,
  `nickname` VARCHAR(50),
  `avatar` VARCHAR(255),
  `reputation` INT DEFAULT 0,
  `status` TINYINT DEFAULT 1,
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE question (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
view_count INT DEFAULT 0,
answer_count INT DEFAULT 0,
accepted_answer_id BIGINT,
status TINYINT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE answer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
question_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
vote_count INT DEFAULT 0,
is_accepted TINYINT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE tag (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255)
);
CREATE TABLE question_tag (
question_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL,
PRIMARY KEY (question_id, tag_id)
);

设计要点:

  • 问题与标签通过中间表建立多对多关系,便于按标签聚合内容。
  • 采纳答案通过 accepted_answer_id 与 is_accepted 双重标记,保证查询效率。
  • 点赞、浏览等高频计数优先写入 Redis,定时或异步回写 MySQL。

五、核心功能实现

5.1 用户认证与鉴权

采用 JWT 无状态认证。用户登录成功后签发 Token,前端在 Axios 请求拦截器中统一携带,后端通过 Spring Security 过滤器校验。

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validate(token)) {
String username = jwtUtil.getUsername(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}

5.2 问题发布与标签绑定

发布问题时,后端在同一事务中写入问题记录与标签关联,并将问题同步到 Elasticsearch 索引。

@Service
@RequiredArgsConstructor
public class QuestionService {
private final QuestionMapper questionMapper;
private final QuestionTagMapper questionTagMapper;
private final QuestionSearchService searchService;
@Transactional(rollbackFor = Exception.class)
public Long publish(QuestionPublishDTO dto, Long userId) {
Question question = new Question();
question.setUserId(userId);
question.setTitle(dto.getTitle());
question.setContent(dto.getContent());
questionMapper.insert(question);
for (Long tagId : dto.getTagIds()) {
    QuestionTag qt = new QuestionTag();
    qt.setQuestionId(question.getId());
    qt.setTagId(tagId);
    questionTagMapper.insert(qt);
}
searchService.index(question);
return question.getId();
}
}

5.3 回答与采纳

回答列表按「是否被采纳 + 投票数 + 时间」排序。采纳操作仅允许提问者执行,且同一问题只能有一个采纳答案。

@Transactional(rollbackFor = Exception.class)
public void acceptAnswer(Long questionId, Long answerId, Long currentUserId) {
    Question question = questionMapper.selectById(questionId);
    if (question == null || !question.getUserId().equals(currentUserId)) {
        throw new BizException("无权采纳该答案");
    }
    if (question.getAcceptedAnswerId() != null) {
        throw new BizException("该问题已采纳答案");
    }
    question.setAcceptedAnswerId(answerId);
    questionMapper.updateById(question);
Answer answer = new Answer();
answer.setId(answerId);
answer.setIsAccepted(1);
answerMapper.updateById(answer);
}

5.4 点赞与计数缓存

点赞使用 Redis 的 Set 结构记录用户与目标的对应关系,避免重复点赞,同时用 Hash 维护计数。

public boolean toggleVote(String type, Long targetId, Long userId) {
    String userKey = "vote:" + type + ":" + targetId;
    String countKey = "vote:count:" + type;
    Boolean added = redisTemplate.opsForSet().add(userKey, userId) > 0;
    if (Boolean.TRUE.equals(added)) {
        redisTemplate.opsForHash().increment(countKey, String.valueOf(targetId), 1);
        return true;
    }
    redisTemplate.opsForSet().remove(userKey, userId);
    redisTemplate.opsForHash().increment(countKey, String.valueOf(targetId), -1);
    return false;
}

5.5 全文检索

基于 Elasticsearch 对问题标题、正文与标签建立索引,支持关键词高亮与相关性排序。

public PageResult<QuestionDoc> search(String keyword, int page, int size) {
    NativeQuery query = NativeQuery.builder()
            .withQuery(q -> q.multiMatch(m -> m
                    .query(keyword)
                    .fields("title^3", "content", "tags")))
            .withPageable(PageRequest.of(page, size))
            .build();
    SearchHits<QuestionDoc> hits = elasticsearchTemplate.search(query, QuestionDoc.class);
    List<QuestionDoc> list = hits.stream()
            .map(h -> h.getContent())
            .collect(Collectors.toList());
    return new PageResult<>(list, hits.getTotalHits());
}

5.6 前端路由与权限守卫

前端通过 Vue Router 的全局守卫校验登录状态与角色权限,未登录用户访问受限页面时跳转登录页。

import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: () => import('@/views/Home.vue') },
{ path: '/question/:id', component: () => import('@/views/QuestionDetail.vue') },
{ path: '/publish', component: () => import('@/views/Publish.vue'), meta: { requiresAuth: true } }
]
})
router.beforeEach((to) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.token) {
return { path: '/login', query: { redirect: to.fullPath } }
}
return true
})
export default router

5.7 请求封装与统一错误处理

import axios from 'axios'
import { ElMessage } from 'element-plus'
const request = axios.create({ baseURL: '/api', timeout: 10000 })
request.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = Bearer ${token}
return config
})
request.interceptors.response.use(
(res) => res.data,
(err) => {
ElMessage.error(err.response?.data?.message || '请求失败')
return Promise.reject(err)
}
)
export default request

六、关键难点与解决方案

  • 高并发点赞:采用 Redis 计数 + 定时任务批量回写 MySQL,降低数据库压力。
  • 检索一致性:问题变更通过 RabbitMQ 发送索引更新消息,保证最终一致。
  • 内容安全:接入敏感词过滤与人工审核队列,问题发布后先进入待审核状态。
  • 权限控制:后端以注解 + 方法级校验为主,前端守卫仅作体验优化,不作为安全边界。

七、部署与运维

推荐使用 Docker Compose 编排各组件,生产环境可迁移至 Kubernetes。

version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: qa_community
    ports:
      - "3306:3306"
  redis:
    image: redis:7
    ports:
      - "6379:6379"
  elasticsearch:
    image: elasticsearch:8.11.0
    environment:
      discovery.type: single-node
      xpack.security.enabled: "false"
    ports:
      - "9200:9200"
  backend:
    build: ./backend
    depends_on:
      - mysql
      - redis
      - elasticsearch
    ports:
      - "8080:8080"
  frontend:
    build: ./frontend
    ports:
      - "80:80"

八、总结与展望

本文从架构、数据模型到核心代码,完整介绍了一个基于 Spring Boot + Vue3 的专业知识问答社区系统。系统通过前后端分离、缓存与检索分离的设计,兼顾了开发效率与运行性能。

后续可扩展的方向包括:

  • 引入推荐算法,根据用户兴趣与历史行为推送问题。
  • 增加富文本编辑器与代码高亮,提升技术内容的表达力。
  • 构建积分与徽章体系,激励高质量内容产出。
  • 接入大模型能力,实现相似问题推荐与答案摘要。
Logo

更多推荐