ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot3+Vue3+MySQL构建全栈科普网站实战指南

SpringBoot3+Vue3+MySQL构建全栈科普网站实战指南 很多学 Java 的同学在准备毕业设计或简历项目时都会陷入一个尴尬的循环整天在教程里写“增删改查”但面试官一问项目亮点却说不清模块如何拆分、接口如何设计、数据如何流转。相比之下一个“技术栈完整、业务场景清楚、能展示前后端协作能力”的全栈项目往往比写十个管理系统都有说服力。这篇文章就以“国之动力科普网站”为例演示如何用 Java SpringBoot3 Vue.js3 MySQL 从零搭出一个前后端分离的科普内容平台。整个项目既不是“玩具级 CRUD”也不是脱离业务堆砌概念的“八股工程”而是把一个真实网站需要的核心能力拆开讲透内容分类、文章浏览、留言反馈、后台管理、接口联调、异常处理、部署排错。读完这篇文章你会获得一套可以落到本地开发环境的完整代码结构而不是只看到碎片化配置。你还会知道哪些环节是新手最容易卡住的“隐形坑”以及在实际项目中更推荐的工程做法。无论你是准备课程设计、毕业设计还是想拿一个项目来加深 SpringBoot3 和 Vue3 的熟练度这条路线都值得跟着走一遍。1. 为什么用 SpringBoot3 Vue3 MySQL 组合先回答一个很多新手困惑的问题技术栈这么多为什么科普网站这类项目普遍选择 SpringBoot3 Vue.js3 MySQL第一是前后端分离已成主流。SpringBoot3 只负责提供 RESTful APIVue3 负责页面渲染和交互两者通过 JSON 通信。这样一来前端同学和后端同学可以并行开发也方便未来把手机 App、小程序复用同一套后端接口。对单体老项目来说这种架构改变的是团队协作和部署方式而不仅仅是一个技术选型。第二是 Java 生态的稳定性。SpringBoot3 是 Spring 官方在 2022 年底发布的新一代框架底层基于 Spring Framework 6默认要求 JDK17 及以上。相比 SpringBoot2它把原来挂在javax命名空间下的 Servlet、Validation 等 API 全部迁移到了jakarta命名空间因此很多 SpringBoot2 时代的旧依赖不能直接照搬。这也意味着如果简历上写的是 SpringBoot3说明你已经在跟进较新的技术版本而不是停留在几年前的老教程。第三是 Vue3 的工程化能力。Vue3 的组合式 APIComposition API、script setup语法、Vite 构建工具让前端代码的可维护性比 Vue2 时代提高了一大截。配合 Element Plus 组件库可以在很短时间内完成后台管理界面、列表页、详情页、表单页的搭建。至于 MySQL它依然是国内中小型项目最稳妥的关系型数据库选择。科普网站的数据模型并不复杂核心就是文章、分类、评论留言用 MySQL 存储绰绰有余同时生态工具成熟Navicat、DataGrip、mysqldump 等都能顺畅使用。这个组合的适用场景也很清晰如果你要做的是内容型 Web 系统、后台管理系统、中小型业务平台并且希望代码结构清晰、能快速交付那么它就是一个非常务实的选择。而如果项目包含大量实时推送、高并发秒杀或复杂推荐算法那可能还需要引入 Redis、消息队列或更专业的搜索组件这就超出了本文的讨论范围。2. 科普网站的系统架构与功能拆解“国之动力科普网站”这个词可以拆成两层理解一类是面向公众的科普内容展示平台一类是带有运营后台的内容管理系统。它本质上是一个“内容型站点”核心不是复杂的业务规则而是内容的生产、展示和互动。从用户侧看网站需要提供科普文章浏览、分类导航、文章搜索、留言互动等功能。从运营侧看管理员需要登录后台、维护文章分类、发布和编辑文章、审核留言。理解了这两端你就知道该建哪些表、写哪些接口。整个系统建议采用经典的前后端分离分层架构前端 Vue3页面渲染与交互 ↓ HTTP JSON 后端 SpringBoot3提供 RESTful API ↓ MyBatis-Plus / JDBC MySQL分类表、文章表、留言表、管理员表后端代码按职责分成 Controller、Service、Mapper、Entity 四层。Controller 只做参数接收和结果返回Service 负责业务逻辑Mapper 负责数据库操作Entity 映射数据表。这种分层的好处是职责单一出了问题能快速定位。项目功能模块可以这样划分模块功能说明典型接口分类展示前台展示科普栏目GET /api/category/list文章发布后台发布、编辑、删除文章POST /api/admin/article文章浏览前台分页查看文章列表和详情GET /api/article/page、GET /api/article/{id}留言互动用户提交留言管理员后台审核POST /api/message、GET /api/message/page管理员登录后台身份认证POST /api/admin/login设计数据库时四个核心表可以这样规划category栏目分类表article科普文章表message访客留言表admin_user后台管理员表后面我们建表时会给出完整 SQL先不要把字段想得太复杂内容型网站重点在于文章表如何关联分类、留言表如何记录审核状态即可。3. MySQL 数据库设计先创建一个数据库命名为power_science字符集选择utf8mb4。科普网站的文章和留言都可能包含中文utf8mb4能够完整支持中文和部分特殊字符比utf8更稳妥。数据库连接信息在后续后端配置中会用到这里要记住用户名和密码。以下 SQL 可以在 Navicat 或命令行中执行。CREATE DATABASE IF NOT EXISTS power_science DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE power_science; -- 分类表 CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 分类ID, name VARCHAR(50) NOT NULL COMMENT 分类名称, sort INT DEFAULT 0 COMMENT 排序号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT科普分类表; -- 文章表 CREATE TABLE article ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 文章ID, category_id BIGINT NOT NULL COMMENT 分类ID, title VARCHAR(200) NOT NULL COMMENT 文章标题, summary VARCHAR(500) COMMENT 文章摘要, content LONGTEXT COMMENT 文章内容, cover_image VARCHAR(500) COMMENT 封面图URL, view_count INT DEFAULT 0 COMMENT 浏览量, status TINYINT DEFAULT 1 COMMENT 状态 1发布 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 发布时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, KEY idx_category_id (category_id) ) ENGINEInnoDB COMMENT科普文章表; -- 留言表 CREATE TABLE message ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 留言ID, nickname VARCHAR(50) NOT NULL COMMENT 昵称, content VARCHAR(1000) NOT NULL COMMENT 留言内容, audit_status TINYINT DEFAULT 0 COMMENT 审核状态 0待审核 1通过 2拒绝, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 留言时间 ) ENGINEInnoDB COMMENT访客留言表; -- 管理员表 CREATE TABLE admin_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 管理员ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码加密存储, nickname VARCHAR(50) COMMENT 昵称 ) ENGINEInnoDB COMMENT后台管理员表; INSERT INTO category (name, sort) VALUES (大国工程, 1); INSERT INTO category (name, sort) VALUES (前沿科技, 2); INSERT INTO category (name, sort) VALUES (科学人物, 3); INSERT INTO admin_user (username, password, nickname) VALUES (admin, $2a$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW, 系统管理员);建表时有两个容易忽略的点。第一文章表的content字段用LONGTEXT因为科普文章正文可能较长VARCHAR很难满足。第二status字段默认值为1只允许发布或下架两种状态避免出现“草稿、待审核、已发布”等过于复杂的流程对起步项目来说会简单很多。管理员表里预置的密码是 BCrypt 加密后的123456后面做登录时可以直接校验不需要再手动生成。4. 后端项目搭建SpringBoot3 基础工程后端工程建议使用 IDEA 初始化。创建 Spring Boot 工程时需要注意SpringBoot3 默认不支持 JDK8必须要选择 JDK17 或更高版本。用 Maven 创建工程后pom.xml 中最核心的依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.1.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies !-- Web 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 的 SpringBoot3 专用 starter -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- Lombok简化实体类编码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意 MyBatis-Plus 的 starter 名称中带有spring-boot3字样不能直接使用旧版的mybatis-plus-boot-starter。很多同学在 SpringBoot3 项目里引入旧版依赖后启动报错原因就在这里。接下来配置application.yml写入 MySQL 连接和 MyBatis-Plus 的基本配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/power_science?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里的map-underscore-to-camel-case作用是将数据库字段的create_time自动映射为实体类属性createTime省去大量手写映射配置。如果你的数据库密码不是123456请改成实际值。启动类上方需要加MapperScan注解让 MyBatis-Plus 扫描到 Mapper 接口package com.example.powerscience; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication MapperScan(com.example.powerscience.mapper) public class PowerScienceApplication { public static void main(String[] args) { SpringApplication.run(PowerScienceApplication.class, args); } }如果这一步不添加MapperScan运行后访问接口时会提示找不到对应的 Mapper Bean新手很容易在这里卡住。5. 后端核心代码统一返回、实体类与 Mapper一个合格的接口工程不能把数据原样裸返回需要设计统一的响应结构。项目的接口一般只要返回业务数据而把状态码、提示信息放在外层。这里定义一个简单的Result类package com.example.powerscience.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }统一返回结构的好处是前端axios拦截器只需要判断code是否为 200就可以决定进入成功回调还是弹出错误提示不需要每次在页面里单独判断后端返没返回数据。接着是实体类。以Article为例package com.example.powerscience.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(article) public class Article { TableId(type IdType.AUTO) private Long id; private Long categoryId; private String title; private String summary; private String content; private String coverImage; private Integer viewCount; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }Data来自 Lombok自动生成 getter/setter。TableName指定实体类对应的数据库表。实体类字段的camelCase属性名会和数据库下划线字段自动对齐。Mapper 接口非常简单package com.example.powerscience.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.powerscience.entity.Article; public interface ArticleMapper extends BaseMapperArticle { }只要继承了BaseMapperMyBatis-Plus 就会为你提供insert、deleteById、selectById、updateById等基础方法不用再手写 XML。对于起步项目来说这一层能减少大量重复代码。6. 接口实现文章分页查询与详情展示科普网站前台最核心的接口是文章列表和文章详情。列表要以分页形式返回并支持按分类筛选详情则需要在返回文章内容的同时把浏览量加一。先写 Service 层。为了避免控制器里写太多业务代码这里把查询逻辑放在 Service 实现类中package com.example.powerscience.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.powerscience.entity.Article; import com.example.powerscience.mapper.ArticleMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Service public class ArticleService { Autowired private ArticleMapper articleMapper; public PageArticle pageArticles(long current, long size, Long categoryId) { LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.eq(Article::getStatus, 1); if (categoryId ! null) { wrapper.eq(Article::getCategoryId, categoryId); } wrapper.orderByDesc(Article::getCreateTime); return articleMapper.selectPage(new Page(current, size), wrapper); } public Article getArticleDetail(Long id) { Article article articleMapper.selectById(id); if (article ! null) { article.setViewCount(article.getViewCount() 1); articleMapper.updateById(article); } return article; } }在 SpringBoot3 中MyBatis-Plus 分页需要额外的分页插件配置。如果不配置selectPage查出来的数据不会真的执行LIMIT分页而是把全表数据都查出来。新建一个配置类package com.example.powerscience.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(100L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }设置setMaxLimit(100L)是为了防止有人传入超大size一次性拉走全库数据这是接口层面一个很简单的保护手段。Controller 层的代码如下package com.example.powerscience.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.powerscience.common.Result; import com.example.powerscience.entity.Article; import com.example.powerscience.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/article) public class ArticleController { Autowired private ArticleService articleService; GetMapping(/page) public ResultPageArticle page(RequestParam(defaultValue 1) long current, RequestParam(defaultValue 10) long size, RequestParam(required false) Long categoryId) { return Result.success(articleService.pageArticles(current, size, categoryId)); } GetMapping(/{id}) public ResultArticle detail(PathVariable Long id) { return Result.success(articleService.getArticleDetail(id)); } }这里体现了一个很常见的 RESTful 设计思路查询类接口用GET列表接口用/page操作单个资源用/{id}。前端在请求/api/article/page?current1size10categoryId2时就会拿到第二分类下面第一页的文章。7. 留言接口与后台管理员登录科普网站允许访客提交留言。留言功能虽然简单却非常适合演示参数校验和表单提交是面试时能讲清楚“完整请求链路”的好例子。留言实体不复杂核心是nickname、content、audit_status。后端要校验两项昵称不能为空且不超过 50 个字符留言内容不能为空且不超过 1000 个字符。package com.example.powerscience.controller; import com.example.powerscience.common.Result; import com.example.powerscience.entity.Message; import com.example.powerscience.mapper.MessageMapper; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/message) public class MessageController { Autowired private MessageMapper messageMapper; Data public static class MessageRequest { NotBlank(message 昵称不能为空) Size(max 50, message 昵称长度不能超过50) private String nickname; NotBlank(message 留言内容不能为空) Size(max 1000, message 留言内容过长) private String content; } PostMapping(/submit) public ResultVoid submit(RequestBody Valid MessageRequest request) { Message message new Message(); message.setNickname(request.getNickname()); message.setContent(request.getContent()); message.setAuditStatus(0); messageMapper.insert(message); return Result.success(null); } }注意在 SpringBoot3 中Valid和校验注解NotBlank都来自jakarta.validation命名空间不再是过去的javax.validation。如果代码里继续写javax.validation.constraints.NotBlank编译器会直接报找不到类。后台管理员登录则需要使用 BCrypt 校验密码。Spring Security 中的BCryptPasswordEncoder可以单独拿出来用不引入完整的安全框架package com.example.powerscience.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.powerscience.common.Result; import com.example.powerscience.entity.AdminUser; import com.example.powerscience.mapper.AdminUserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/admin) public class AdminController { Autowired private AdminUserMapper adminUserMapper; private final BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); PostMapping(/login) public ResultString login(RequestBody LoginRequest request) { AdminUser adminUser adminUserMapper.selectOne( new LambdaQueryWrapperAdminUser() .eq(AdminUser::getUsername, request.getUsername()) ); if (adminUser null || !encoder.matches(request.getPassword(), adminUser.getPassword())) { return Result.error(401, 用户名或密码错误); } return Result.success(adminUser.getNickname()); } Data public static class LoginRequest { private String username; private String password; } }这里有个约定俗成的要点数据库中的密码绝不能是明文。上面演示的校验使用 BCrypt 加密前端提交过来的是用户输入的明文密码后端用matches方法和密文做比对。如果要在真实项目里继续扩展下一步通常会引入 JWT 或 Session 保持登录态并把登录后的用户信息返回给前端。8. 前端项目搭建Vue3 Vite Element Plus现在进入前端部分。前端工程使用 Vite 创建比 Vue CLI 更快也是 Vue3 官方推荐的构建工具。打开命令行执行以下命令npm create vitelatest science-web -- --template vue cd science-web npm install npm install axios element-plus vue-router4 npm run dev安装完成后前端项目结构大致如下science-web/ ├── src/ │ ├── api/ // axios 请求封装 │ ├── router/ // 路由配置 │ ├── views/ │ │ ├── HomeView.vue │ │ ├── ArticleListView.vue │ │ └── ArticleDetailView.vue │ ├── App.vue │ └── main.js ├── index.html └── package.json先封装 axios 请求避免每个组件都重复写baseURL和拦截器。这里把统一处理放在src/api/request.jsimport axios from axios; import { ElMessage } from element-plus; const request axios.create({ baseURL: http://localhost:8080, timeout: 10000 }); // 响应拦截器处理后端统一的 Result 结构 request.interceptors.response.use( (response) { const res response.data; if (res.code ! 200) { ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); } return res.data; }, (error) { ElMessage.error(网络异常请稍后重试); return Promise.reject(error); } ); export default request;这样在 Vue 页面中调用接口时拿到的直接就是Result.data里的业务数据不再需要每页都解包response.data.data。在main.js中注册 Element Plus 和路由import { createApp } from vue; import ElementPlus from element-plus; import element-plus/dist/index.css; import App from ./App.vue; import router from ./router; const app createApp(App); app.use(ElementPlus); app.use(router); app.mount(#app);为了打通前后端接口还需要在 Vite 开发服务器里配置代理。否则浏览器请求http://localhost:8080会面临跨域问题。最稳妥的方案不是开启后端 CORS而是让前端开发服务器把/api开头的请求代理到后端地址这样开发时浏览器看到的是同源请求自然不存在跨域限制。在vite.config.js中添加import { defineConfig } from vite; import vue from vitejs/plugin-vue; export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });配置代理之后后端接口地址就统一改为相对路径/api/...不再写死 IP 和端口。9. 前端页面示例文章列表与详情页文章列表页是整个网站的门面它负责从后端加载数据、渲染卡片、支持下拉分页或点击分页。这里直接使用 Element Plus 的分页组件并调用后端/api/article/page接口。src/api/article.jsimport request from ./request; export function getArticlePage(params) { return request({ url: /api/article/page, method: get, params }); } export function getArticleDetail(id) { return request({ url: /api/article/${id}, method: get }); }ArticleListView.vue中的关键逻辑template div classarticle-list el-card v-foritem in list :keyitem.id classarticle-item h3 clickgoDetail(item.id){{ item.title }}/h3 p{{ item.summary }}/p div classmeta span{{ item.categoryId }}/span span浏览 {{ item.viewCount }}/span span{{ formatTime(item.createTime) }}/span /div /el-card el-pagination background layoutprev, pager, next :totaltotal :page-sizequery.size v-model:current-pagequery.current current-changeloadData / /div /template script setup import { ref, onMounted } from vue; import { useRouter } from vue-router; import { getArticlePage } from ../api/article; const router useRouter(); const list ref([]); const total ref(0); const query ref({ current: 1, size: 6 }); async function loadData() { const data await getArticlePage(query.value); list.value data.records || []; total.value data.total || 0; } function goDetail(id) { router.push(/article/${id}); } function formatTime(time) { return time ? time.replace(T, ).substring(0, 16) : ; } onMounted(loadData); /script详情页会根据路由参数加载文章 ID然后调用详情接口。这里建议使用v-html渲染后端返回的富文本内容如果文章内容是 Markdown则需要额外引入 Markdown 渲染插件本文从简处理默认后端返回 HTML 片段。这一步也是科普网站内容展示的关键。10. 完整演示从建库到页面跑通为了让你对整个工程有更清晰的画面这里整理出一条从零到页面跑通的完整路径第一步用 Navicat 或命令行执行第三节的建表 SQL确认power_science数据库存在且四张表创建成功。如果执行报错大概率是建表顺序问题先建 category 再建 article因为 article 外键关联 category。第二步在 IDEA 中新建 SpringBoot 工程把 Maven 依赖、application.yml、实体类、Mapper、Service、Controller 按包结构写好后启动。控制台出现Started PowerScienceApplication就说明后端成功。第三步在浏览器中访问http://localhost:8080/api/article/page?current1size6。如果返回类似下面结构的 JSON说明后端接口已经可用{ code: 200, message: 操作成功, data: { records: [ { id: 1, categoryId: 1, title: 高速列车为什么这么快, summary: 从牵引动力到空气动力学解读高速列车背后的科学原理。, viewCount: 3, status: 1, createTime: 2025-01-10T12:00:00 } ], total: 1, size: 6, current: 1 } }第四步启动前端工程npm run dev打开http://localhost:5173进入首页后能看到文章列表。点击文章标题跳转到详情页能正常显示正文内容和浏览量增加。第五步在前台留言区提交一条留言后端接收后返回code: 200打开数据库检查message表会看到一条audit_status 0的新记录。这说明前台到数据库的整个链路是通的。如果在这些步骤中任意一个环节出现异常不要急着改前端或数据库。先确认上一步产物是否正确建表是否成功、后端接口用浏览器/Postman 是否能返回 JSON、前端请求是否发出、浏览器 Network 面板里请求是否报错。逐层排查是最快的。11. 常见问题与排查很多开发者在第一次跑 SpringBoot3 Vue3 前后端分离项目时遇到的坑高度相似。下面这张表整理了最常见的几类问题问题现象可能原因排查方式解决方案后端启动类报java.lang.NoClassDefFoundError: javax/servlet/...引入的依赖还是 SpringBoot2 时代的旧版本查看 pom.xml 依赖版本使用支持 SpringBoot3 的依赖版本如 MyBatis-Plus 的mybatis-plus-spring-boot3-starter数据库连接失败Access denied for user用户名或密码错误检查 application.yml 和 MySQL 登录信息修改成正确的数据库账号密码前端请求始终报 404后端接口路径或前端 baseURL 不匹配打开浏览器 Network 查看请求 URL确认接口路径完整带/api前缀浏览器报跨域错误没有配置代理或者后端未开启跨域查看 vite.config.js 代理配置优先使用 Vite proxy 或在后端添加 CORS 配置selectPage返回整表数据而非分页MyBatis-Plus 分页插件未配置检查是否存在 MybatisPlusConfig添加分页插件配置类时间字段返回格式带 T默认使用 ISO 格式输出 LocalDateTime检查返回 JSON在配置中统一 Jackson 格式或前端格式化文章详情页图片不显示图片 URL 是局域网地址前端环境访问不了检查封面图地址使用可访问的 URL 或后端静态资源路径端口 8080 被占用本机已有进程占用执行netstat -ano查找占用端口修改 server.port 或关闭占用进程这里特别强调配置管理。数据库中连接密码、前端接口地址这类配置不要硬编码在组件里。后端建议区分application-dev.yml、application-prod.yml前端则通过.env.development和.env.production管理环境变量。工程上这叫“环境与配置分离”能避免项目换环境部署时翻代码改参数。11.1 跨域问题只有一个标准答案吗前后端分离项目基本都会遇到跨域。网上很多教程会让你在后端写一个WebMvcConfigurer的 CORS 配置这种方法只是“允许浏览器跨域访问”但它也有风险如果配置成allowedOrigins(*)意味着任意网站都能调用你的后端接口。对于纯开发调试可以生产环境必须限定具体的来源域名。更推荐的做法是前端通过 Vite 代理转发。因为前端开发服务器和后端之间是服务端发起请求不受浏览器同源策略限制。部署到 Nginx 后再由 Nginx 把/api反向代理到 Java 服务同样能避免跨域。这样后端接口不轻易向全网开放安全性更好。如果团队内部确实需要给第三方系统开放接口再考虑在后端精确配置 CORS 白名单而不是把跨域交给代码全局放开。12. 最佳实践与工程建议整个项目跑通只是第一步。如果想把“国之动力科普网站”这个项目变成能写进简历的亮点建议再往以下几个方向做工程化优化。关于密码安全后台管理员的密码一定要加密存储。示例里使用 BCrypt 加密这是目前 Spring 生态中最常见的密码散列算法之一它会自动加盐相同明文每次加密结果也完全不同。用户留言这类提交接口也要做输入校验防止超长内容打满数据库或插入恶意脚本。关于日志记录不要在所有方法里到处System.out.println。引入slf4j日志在 Controller、Service 层输出入参、出参和关键异常。出了线上问题好的日志能让你少熬好几个夜。SpringBoot3 中可以使用Slf4j注解快速获得日志对象。关于后端接口安全当前的登录接口只是最简单的用户名密码校验。实际生产环境通常需要引入 JWT 或 Spring Security 做身份认证和权限控制。对于科普网站而言后台新增、编辑、删除文章这三个接口绝不能被匿名用户直接调用至少要加登录拦截器。关于内容生产科普网站的价值在内容。后台管理页面要支持富文本编辑数据表建议增加update_time并记录最近编辑人。文章要有封面图、作者、浏览量统计才能支撑前台的信息卡片展示效果。未来还可以加上文章搜索、按浏览量排序、相关推荐等功能。关于缓存优化热点文章在被大量访问时详情接口每次都会updateById把浏览量写回数据库并发高时会带来不必要的行锁压力。更理想的方案是先用 Redis 记录浏览量再定时回写或者使用乐观锁机制更新浏览计数字段。这一步可以等并发量真实上来了再优化现阶段知道方向即可。13. 项目扩展方向从科普网站到公共内容平台“国之动力科普网站”只是一个起点它背后代表的是一类内容型 Web 系统的通用玩法。这套架构只要稍作扩展就能变成新闻门户、企业官网、个人博客、知识库系统。如果你有余力建议按下面顺序扩展项目第一把后台管理页面补完整。目前文章管理界面可以通过 Element Plus 的表格、表单、Dialog 快速搭建再接入文件上传功能管理封面图。这样前台展示和后台管理形成一个完整的运维闭环而不是只能靠直接改数据库来维护内容。第二引入 Markdown 或富文本编辑器。科普文章可能需要大量图文混排使用wangEditor或vditor这样的组件在后台发布文章时会顺手很多。后端只需保存编辑器生成的 HTML前端v-html展示即可。第三把搜索功能从数据库LIKE查询升级为全文检索。文章数量少时MySQL 的LIKE %关键词%没有问题但内容变多之后建议用 Elasticsearch。大部分项目用不到这一层但简历上如果能讲清楚“CRUD 到搜索引擎”的演进过程会是一个不错的加分项。第四加入统计报表。记录每天的访问量、文章 PV、热门栏目排行数据可以从article.view_count聚合得到也可以通过引入简单埋点来采集。对运营人员来说这是内容平台不可或缺的管理能力。当然扩展的功能并不意味着越多越好。对起步项目而言把“分类 → 文章 → 留言 → 后台管理”这一条主线做完做到接口规范、界面干净、部署顺畅已经能体现扎实的全栈能力。在此之上再挑一个亮点做深入比堆积半成品功能更有价值。最后给一个实用提醒不要只照着文章把代码敲完就结束。找一个自己感兴趣的细分领域把建表字段改一改、页面文案换一换把它变成一个真正“自己的项目”。然后试着把后端打包成 Jar 包把前端npm run build产物放到 Nginx 里跑一次完整部署。这个过程做完你对 SpringBoot3、Vue3、MySQL 的理解会完全不一样。技术没有太多玄学无非是链路跑通、细节看清、问题会查而已。
返回列表