news 2026/9/5 18:02:11

Spring Boot 3 + Vue3 + MySQL 课程网站全栈开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 3 + Vue3 + MySQL 课程网站全栈开发实战

选择“课程网站”作为毕业设计或简历项目时,很多同学最头疼的不是某个知识点学不会,而是“前端、后端、数据库要如何组合成一个能跑起来的系统”。网上搜到的资料往往是单个接口、单个页面,真正要把 Spring Boot、Vue3、MySQL 串成一条完整业务链路,还是需要自己踩不少坑。

这篇教程围绕“课程网站系统”这个典型全栈项目,从数据库设计开始,到 Spring Boot 3 后端接口,再到 Vue3 前端页面,完整走一遍课程分类、课程列表、课程新增、编辑、上下架、删除的全流程。项目既适合 JavaWeb 课程设计,也适合作为前后端分离项目的入门练手。

如果你是跟着教程从头敲一遍,最终会得到一套可本地运行的前后端分离项目:后端提供 REST 风格接口,前端通过 Axios 调用接口展示课程数据并完成管理操作。整个过程会涉及 Spring Data JPA 数据访问、统一响应体设计、跨域配置、Vue Router 路由、Element Plus 表单校验等常用开发技能。

1. 系统定位与技术选型

1.1 课程网站需要哪些核心功能

课程网站系统的本质是“课程信息的管理与展示”。用户侧需要看到的是一张课程列表,可以按分类筛选、按名称搜索,点进课程详情后能看到讲师、简介、封面图和价格等信息;管理侧则需要维护课程分类,并对课程进行上架、下架、资料修改等操作。

这看起来简单,但落到系统设计上,至少要拆分出如下功能点:

模块功能说明
课程分类分类列表查询、分类维护
课程展示公开课程列表、分类筛选、关键词搜索、课程详情
课程管理新增课程、编辑课程、上下架、删除课程
数据统计课程学习人数、课程数量统计(可作为后续扩展)
用户体系用户注册登录、角色权限分离(可作为后续扩展)

本文把重点放在主链路:分类维护、课程展示、课程 CRUD。这样代码量可控,同时覆盖了“前台查询 + 后台管理”的完整业务闭环。登录鉴权部分会在最后的生产化改进中说明。

1.2 为什么选择 Spring Boot 3 + Vue3 + MySQL

这套组合在目前的 JavaWeb 课程设计和实际项目中使用率都很高,主要有三个原因。

第一,Spring Boot 3 已经全面进入 Jakarta EE 命名空间,底层依赖 Spring Framework 6。相比早期的 Spring Boot 2.x,它强制要求 JDK 17 及以上,语法和生态都已经偏向现代 Java。对新手来说,使用 Spring Boot 3 可以避免一上来就学习过时的 Servlet API 写法。

第二,Vue3 是目前前端 Vue 生态的主力版本。配合 Vite 构建工具,开发时热更新非常快。Vue 3 的组合式 API 也让逻辑复用变得更清晰,组件代码的可维护性比 Vue 2 时代高很多。

第三,MySQL 是中小型业务系统最常用的关系型数据库。课程网站的数据模型并不复杂,用 MySQL 管理课程分类、课程、用户、学习记录等表都是很成熟的做法。而且网上关于 MySQL 安装、索引优化、事务使用的资料非常多,遇到问题容易排查。

1.3 前后端分离架构与工程目录规划

前后端分离的意思是:后端只提供 JSON 接口,不关心页面长什么样;前端只负责渲染页面,不直接操作数据库。两者通过 HTTP 接口通信。

后端项目的包结构建议如下:

course-website-backend ├── pom.xml └── src/main ├── java/com/example/course │ ├── CourseApplication.java │ ├── common │ │ ├── PageResult.java │ │ └── Result.java │ ├── config │ │ └── CorsConfig.java │ ├── controller │ ├── dto │ ├── entity │ ├── repository │ ├── service │ └── vo └── resources └── application.yml

前端项目结构会在后面的章节展开。这里先明确包结构的意义:entity 负责映射数据库表,repository 负责数据访问,service 负责业务逻辑,controller 负责接收请求和返回结果,dto 负责接收前端参数,vo 负责向后端返回页面展示数据。结构清晰之后,排查问题会变得非常轻松。

2. 环境准备与数据库设计

2.1 开发环境版本说明

开始编码前,先确认本机环境。版本需要根据实际项目调整,本教程以如下环境为基线:

组件版本说明
JDKJDK 17 及以上
MavenMaven 3.8 及以上
Spring Boot3.3.x 系列
MySQL8.x
Node.jsNode 18 及以上
前端构建工具Vite 5.x
IDEIntelliJ IDEA 或 VSCode

如果你使用的是 JDK 8,无法直接运行 Spring Boot 3 项目,必须先升级 JDK。MySQL 安装完成后,需要保证本机 3306 端口可以正常访问。Node.js 可以通过node -v命令检查版本。

2.2 数据库表结构设计

课程网站的核心是课程表。围绕课程,还需要一张课程分类表。分类和课程是一对多关系:一个分类下可以有多门课程,一门课程只能属于一个分类。

课程表设计时,除了标题、封面、讲师、简介这些展示字段,还需要关注状态字段。这里用status表示课程上下架,1 代表上架,0 代表下架。前台只能查询到上架课程,后台可以看到全部课程。

如果后续要支持用户登录和学习记录,可以再增加用户表和课程学习记录表。本文先把基础数据表建好,用户相关表放在扩展部分说明。

2.3 初始化 SQL 脚本

新建数据库,并执行以下 SQL:

CREATE DATABASE course_website DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_website; CREATE TABLE course_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '分类名称', sort INT DEFAULT 0 COMMENT '排序,数值越小越靠前', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '课程分类表'; CREATE TABLE course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT '分类ID', title VARCHAR(100) NOT NULL COMMENT '课程标题', cover_url VARCHAR(500) COMMENT '封面图地址', teacher VARCHAR(50) COMMENT '讲师姓名', summary VARCHAR(500) COMMENT '课程简介', price DECIMAL(10, 2) DEFAULT 0.00 COMMENT '课程价格,0表示免费', study_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), CONSTRAINT fk_course_category FOREIGN KEY (category_id) REFERENCES course_category (id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '课程表';

插入基础测试数据:

INSERT INTO course_category (name, sort) VALUES ('Java', 1); INSERT INTO course_category (name, sort) VALUES ('前端开发', 2); INSERT INTO course_category (name, sort) VALUES ('数据库', 3); INSERT INTO course (category_id, title, cover_url, teacher, summary, price, study_count, status) VALUES (1, 'Spring Boot 3 实战教程', 'https://example.com/course-1.png', '张老师', '从零开始掌握Spring Boot 3核心用法', 129.00, 1200, 1), (2, 'Vue3 全家桶入门', 'https://example.com/course-2.png', '李老师', '系统学习Vue3组合式API与Vite', 99.00, 800, 1), (3, 'MySQL 性能优化基础', 'https://example.com/course-3.png', '王老师', '索引、SQL优化与慢查询分析', 199.00, 300, 0);

这段 SQL 中,封面图片地址是示例值。实际项目中,封面图通常会上传到 OSS 对象存储,或者使用本地静态资源目录保存。

2.4 项目初始化方式

后端项目可以直接通过 IDEA 的 Spring Initializr 创建,选择 Java 17、Spring Web、Spring Data JPA、MySQL Driver、Validation、Lombok 这几个依赖。也可以手动创建 Maven 项目后添加 pom.xml 依赖。

前端项目使用 Vite 脚手架创建:

npm create vite@latest course-website-frontend -- --template vue cd course-website-frontend npm install npm install element-plus axios vue-router

npm create vite创建项目后,默认的模板是 Vue3 + JavaScript。如果你更习惯 TypeScript,也可以把--template vue换成--template vue-ts,本教程为了减少额外配置,使用 JavaScript 版本。

3. 后端工程搭建与核心代码实现

3.1 Maven 依赖配置

创建后端项目后,pom.xml 中的核心依赖如下:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

版本号以spring-boot-starter-parent为准,Spring Boot 会统一管理其他依赖版本,所以 mysql 驱动、Lombok 不需要手动写版本号。如果你的 IDEA 在刷新依赖时下载较慢,可以配置 Maven 国内镜像源。

3.2 application.yml 配置

src/main/resources/application.yml中写入:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/course_website?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: none show-sql: true open-in-view: false jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai

配置说明:

  • ddl-auto: none表示不让 Hibernate 自动修改表结构。既然已经手动执行了 SQL 初始化表,就不需要 JPA 自动建表,避免字段类型和设计不一致。
  • show-sql: true方便在控制台查看 JPA 生成的 SQL,排查问题时很有帮助。
  • open-in-view: false是为了避免视图层打开数据库连接。这个配置在 Spring Boot 2.x 后默认是 true,但生产环境并不推荐,所以这里先改成 false。
  • allowPublicKeyRetrieval=true是 MySQL 8 连接时常见的参数,防止出现公钥检索错误。

如果你本机 MySQL 用户密码不是123456,请务必改成自己的实际密码。

3.3 统一响应体与分页结果

后端接口返回给前端的 JSON 如果一个接口一种格式,前端封装 Axios 会很麻烦。这里统一封装一个Result<T>对象,所有接口都返回code、message、data三个字段。

// 文件路径:src/main/java/com/example/course/common/Result.java @Data public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> fail(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

分页接口需要额外的PageResult<T>对象:

// 文件路径:src/main/java/com/example/course/common/PageResult.java @Data public class PageResult<T> { private List<T> list; private long total; public static <T> PageResult<T> of(List<T> list, long total) { PageResult<T> result = new PageResult<>(); result.setList(list); result.setTotal(total); return result; } }

使用 Lombok 的@Data可以自动生成 getter/setter,减少样板代码。

3.4 实体类与 Repository

课程表对应的实体类如下:

// 文件路径:src/main/java/com/example/course/entity/Course.java @Data @Entity @Table(name = "course") public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "category_id") private Long categoryId; @Column(name = "title") private String title; @Column(name = "cover_url") private String coverUrl; @Column(name = "teacher") private String teacher; @Column(name = "summary") private String summary; @Column(name = "price") private BigDecimal price; @Column(name = "study_count") private Integer studyCount; @Column(name = "status") private Integer status; @Column(name = "create_time", updatable = false) private LocalDateTime createTime; @Column(name = "update_time") private LocalDateTime updateTime; @PrePersist public void prePersist() { LocalDateTime now = LocalDateTime.now(); createTime = now; updateTime = now; } @PreUpdate public void preUpdate() { updateTime = LocalDateTime.now(); } }

实体字段和数据库列一一对应。注意prePersist会在插入前自动填充时间,preUpdate会在更新前自动刷新更新时间,不需要在 Service 里手动 set 时间字段。

分类实体类似,可以直接复用同样结构:

// 文件路径:src/main/java/com/example/course/entity/CourseCategory.java @Data @Entity @Table(name = "course_category") public class CourseCategory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Integer sort; @Column(name = "create_time", updatable = false) private LocalDateTime createTime; }

Repository 层接口继承 JpaRepository:

// 文件路径:src/main/java/com/example/course/repository/CourseRepository.java public interface CourseRepository extends JpaRepository<Course, Long> { Page<Course> findByTitleContainingAndStatus(String title, Integer status, Pageable pageable); Page<Course> findByCategoryIdAndTitleContainingAndStatus(Long categoryId, String title, Integer status, Pageable pageable); Page<Course> findByTitleContaining(String title, Pageable pageable); }

Spring Data JPA 会根据方法名自动解析 SQL。findByTitleContaining会生成WHERE title LIKE ?的查询,前端不传关键词时,传入空字符串即可查询全部。

3.5 DTO、VO 与 Service 业务层

由于接口不能直接把实体类返回给前端,这里先定义新增和编辑时接收参数的 DTO:

// 文件路径:src/main/java/com/example/course/dto/CourseSaveDTO.java @Data public class CourseSaveDTO { @NotNull(message = "分类不能为空") private Long categoryId; @NotBlank(message = "课程标题不能为空") private String title; private String coverUrl; private String teacher; private String summary; private BigDecimal price; private Integer status; }

课程分页查询返回的 VO 需要补充分类名称,方便前台直接显示:

// 文件路径:src/main/java/com/example/course/vo/CourseVO.java @Data public class CourseVO { private Long id; private Long categoryId; private String categoryName; private String title; private String coverUrl; private String teacher; private String summary; private BigDecimal price; private Integer studyCount; private Integer status; private LocalDateTime createTime; }

CourseService 承担核心业务逻辑:

// 文件路径:src/main/java/com/example/course/service/CourseService.java @Service @RequiredArgsConstructor public class CourseService { private final CourseRepository courseRepository; private final CourseCategoryRepository categoryRepository; @Transactional(readOnly = true) public PageResult<CourseVO> pagePublic(String keyword, Long categoryId, int pageNum, int pageSize) { Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, "createTime")); Page<Course> coursePage; if (categoryId != null) { coursePage = courseRepository.findByCategoryIdAndTitleContainingAndStatus( categoryId, keyword, 1, pageable); } else { coursePage = courseRepository.findByTitleContainingAndStatus(keyword, 1, pageable); } return toPageResult(coursePage); } @Transactional(readOnly = true) public PageResult<CourseVO> pageAdmin(String keyword, int pageNum, int pageSize) { Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, "createTime")); Page<Course> coursePage = courseRepository.findByTitleContaining(keyword, pageable); return toPageResult(coursePage); } @Transactional(readOnly = true) public CourseVO detail(Long id) { Course course = courseRepository.findById(id) .orElseThrow(() -> new RuntimeException("课程不存在")); CourseVO vo = convert(course); String categoryName = categoryRepository.findById(course.getCategoryId()) .map(CourseCategory::getName) .orElse("未分类"); vo.setCategoryName(categoryName); return vo; } public Long createCourse(CourseSaveDTO dto) { Course course = new Course(); applyDto(course, dto); course.setStudyCount(0); if (course.getStatus() == null) { course.setStatus(1); } return courseRepository.save(course).getId(); } public void updateCourse(Long id, CourseSaveDTO dto) { Course course = courseRepository.findById(id) .orElseThrow(() -> new RuntimeException("课程不存在")); applyDto(course, dto); courseRepository.save(course); } public void deleteCourse(Long id) { courseRepository.deleteById(id); } private void applyDto(Course course, CourseSaveDTO dto) { course.setCategoryId(dto.getCategoryId()); course.setTitle(dto.getTitle()); course.setCoverUrl(dto.getCoverUrl()); course.setTeacher(dto.getTeacher()); course.setSummary(dto.getSummary()); course.setPrice(dto.getPrice()); course.setStatus(dto.getStatus()); } private PageResult<CourseVO> toPageResult(Page<Course> coursePage) { List<Course> courses = coursePage.getContent(); Map<Long, String> categoryMap = loadCategoryMap(courses); List<CourseVO> voList = courses.stream().map(course -> { CourseVO vo = convert(course);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 17:57:09

毕业设计工具选型全攻略:从代码到答辩的实战经验

1. 引言&#xff1a;毕业设计中的工具困境 作为一名计算机专业的学生&#xff0c;我在毕业设计的过程中面临了许多选择工具的困惑。开发、作图、文档整理和论文收尾&#xff0c;几乎每个环节都需要用到工具。工具选得好&#xff0c;事半功倍&#xff1b;选得不好&#xff0c;不…

作者头像 李华
网站建设 2026/9/5 17:56:18

Redisson 配置速查:最小可用配置、连接池调优与踩坑处理

Redisson 配置速查&#xff1a;最小可用配置、连接池调优与踩坑处理 【免费下载链接】redisson Redisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Mu…

作者头像 李华
网站建设 2026/9/5 17:49:53

用Arduino构建智能小车闭环控制系统:编码器测速与PID避障

在这个章节之前&#xff0c;你大概率已经完成过 Arduino 基础项目&#xff1a;点灯、按键输入、用 PWM 控制电机转速。看起来小车已经能“动”起来了&#xff0c;但只要你把它放到地上&#xff0c;问题立刻就会出现&#xff1a;两个电机明明写了同样的占空比&#xff0c;车却总…

作者头像 李华
网站建设 2026/9/5 17:40:16

C#源生成器在Unity UI中的真正用途:把隐式连接变成编译期契约

看到“Source Generator 用在 Unity UI”这个方向&#xff0c;我第一反应不是“终于不用写一堆属性了”&#xff0c;而是最近在项目里连续处理了两个热词级问题——TextMeshPro 被 UI 挡到、以及 UI 上动态画线后节点引用动不动断掉。这两件事表面看和源代码生成八竿子打不着&a…

作者头像 李华
网站建设 2026/9/5 17:39:52

Unity手游Lua热更实战:XLua框架搭建与避坑指南

做手游这几年&#xff0c;有一件事比换引擎、调渲染还让我上心&#xff0c;就是热更。早期项目上线遇到严重Bug&#xff0c;看着玩家在应用商店评论区骂了整整一周&#xff0c;新版本才过审&#xff0c;那种无力感到现在都记得。后来团队下定决心&#xff0c;从C#直出改成“C#壳…

作者头像 李华
网站建设 2026/9/5 17:38:58

SpringBoot3+Vue3+MySQL打造科普网站:全栈实战从零到部署

这次我们来看一个典型的全栈实战项目&#xff1a;国之动力科普网站。技术栈锁定在 Java SpringBoot3 Vue.js3 MySQL&#xff0c;前后端分离&#xff0c;核心是一套“前台科普内容展示 后台内容管理”的网站系统。这个项目最有价值的地方不是某个炫酷的 AI 功能&#xff0c;…

作者头像 李华