SpringBoot3+Vue3+TypeScript全栈实战:图书管理系统从开发到部署

SpringBoot3+Vue3+TypeScript全栈实战:图书管理系统从开发到部署
30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度很多开发者学完 SpringBoot 和 Vue 的语法后一到自己动手整合前后端项目就卡在跨域、接口联调、数据格式、打包部署这些环节。本文将以一个完整的图书管理系统为例手把手带你走通 SpringBoot3、Vue3 和 TypeScript 这套现代全栈技术栈的完整开发流程。这不是一个炫技的 Demo而是一个从零到一、覆盖环境搭建、核心编码、前后端联调直至生产部署的实战指南。无论你是想巩固全栈技能的学生还是需要快速搭建后台管理系统的开发者都能从中获得一套可直接复用的工程化方案。1. 项目定位与技术选型1.1 为什么选择这个技术栈图书管理系统是一个经典的 CRUD 应用场景但它涵盖了现代 Web 应用开发的几乎所有核心环节数据库设计、RESTful API 构建、前端组件化开发、状态管理、路由以及最终的部署上线。选择SpringBoot3 Vue3 TypeScript组合旨在让你体验一套高效、类型安全且易于维护的全栈开发模式。SpringBoot 3.x作为后端核心它提供了极简的配置和强大的自动装配能力能快速构建出稳定、高性能的 REST API。SpringBoot 3.x 基于 Spring Framework 6要求 JDK 17带来了更好的性能和新的特性如 ProblemDetail 异常处理。Vue 3其 Composition API 带来了更灵活的逻辑复用和组织方式配合script setup语法代码更加简洁。TypeScript为 JavaScript 提供了静态类型检查能在编码阶段就发现潜在的类型错误极大地提升了前端代码的健壮性和可维护性尤其在与后端接口对接时类型定义能保证数据一致性。Vite作为新一代前端构建工具其极快的冷启动和热更新速度能显著提升开发体验。1.2 环境准备与版本确认在开始编码前请确保你的开发环境已就绪。为了避免因版本不兼容导致的“玄学”问题建议固定以下版本组合JDK: 17 或更高版本 (SpringBoot 3.x 的硬性要求)Node.js: 18.x 或更高版本npm / yarn / pnpm: 任选其一本文使用 npmMySQL: 8.x (也可使用 Docker 快速启动一个实例)IDE: IntelliJ IDEA (后端) VSCode (前端) 是高效组合构建工具: Maven 3.6 或 Gradle你可以通过以下命令快速检查环境# 检查 Java 版本 java -version # 检查 Node.js 和 npm 版本 node -v npm -v # 检查 Maven 版本 mvn -v2. 后端工程SpringBoot3 构建 REST API2.1 项目初始化与目录结构我们采用前后端分离的架构创建两个独立的项目目录并通过 Git 统一管理。推荐的项目结构如下book-management-system/ ├── backend/ # SpringBoot 后端项目 │ ├── src/main/java/com/example/bookstore │ │ ├── controller/ # 控制器层处理HTTP请求 │ │ ├── service/ # 业务逻辑层 │ │ ├── repository/ # 数据访问层 (Spring Data JPA) │ │ ├── entity/ # 实体类映射数据库表 │ │ ├── dto/ # 数据传输对象 │ │ ├── config/ # 配置类如跨域、安全 │ │ └── BookstoreApplication.java # 启动类 │ ├── src/main/resources │ │ ├── application.yml # 主配置文件 │ │ └── ... │ └── pom.xml # Maven 依赖管理 └── frontend/ # Vue 3 TypeScript 前端项目 ├── src/ │ ├── api/ # 封装所有后端API请求 │ ├── views/ # 页面级组件 │ ├── components/ # 可复用组件 │ ├── router/ # 路由配置 │ ├── store/ # 状态管理 (Pinia) │ ├── types/ # TypeScript 类型定义 │ └── main.ts # 应用入口 ├── index.html ├── vite.config.ts # Vite 配置 ├── tsconfig.json # TypeScript 配置 └── package.json使用 Spring Initializr (https://start.spring.io/) 快速生成后端项目依赖选择Spring Web,Spring Data JPA,MySQL Driver,Lombok。2.2 数据库设计与实体映射我们首先设计核心的book表并创建对应的 JPA 实体类。// 文件路径backend/src/main/java/com/example/bookstore/entity/Book.java package com.example.bookstore.entity; import jakarta.persistence.*; import lombok.Data; import java.time.LocalDate; import java.time.LocalDateTime; Data Entity Table(name book) public class Book { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, unique true) private String isbn; // 国际标准书号唯一标识 Column(nullable false) private String title; private String author; private String publisher; Column(name publish_date) private LocalDate publishDate; private Double price; Column(name total_copies) private Integer totalCopies; // 总册数 Column(name available_copies) private Integer availableCopies; // 可借册数 Column(updatable false) private LocalDateTime createTime; private LocalDateTime updateTime; PrePersist protected void onCreate() { createTime LocalDateTime.now(); updateTime LocalDateTime.now(); } PreUpdate protected void onUpdate() { updateTime LocalDateTime.now(); } }关键点解析Entity和Table: 将类标记为 JPA 实体并指定对应的数据库表名。Id和GeneratedValue: 定义主键及其生成策略这里使用数据库自增。Column: 定义字段与数据库列的映射关系可设置约束如nullable非空、unique唯一。PrePersist和PreUpdate: 生命周期回调注解用于在实体持久化插入和更新前自动填充时间字段。这是一种优雅的审计字段处理方式。2.3 数据访问层与业务逻辑层Spring Data JPA 极大地简化了数据库操作。// 文件路径backend/src/main/java/com/example/bookstore/repository/BookRepository.java package com.example.bookstore.repository; import com.example.bookstore.entity.Book; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.Optional; public interface BookRepository extends JpaRepositoryBook, Long, JpaSpecificationExecutorBook { OptionalBook findByIsbn(String isbn); boolean existsByIsbn(String isbn); }JpaRepositoryBook, Long: 继承此接口即免费获得了基本的 CRUD 方法save,findById,findAll,deleteById等。JpaSpecificationExecutorBook: 用于支持复杂的动态查询如多条件组合分页查询通过Specification构建查询条件。自定义方法findByIsbn和existsByIsbn。Spring Data JPA 会根据方法名自动推导并实现 SQL 查询逻辑这是其强大之处。业务逻辑层负责处理具体的业务规则如数据校验、事务管理等。// 文件路径backend/src/main/java/com/example/bookstore/service/BookService.java package com.example.bookstore.service; import com.example.bookstore.entity.Book; import com.example.bookstore.repository.BookRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.Optional; Service RequiredArgsConstructor // Lombok 注解为 final 字段生成构造器实现依赖注入 public class BookService { private final BookRepository bookRepository; public PageBook getBooks(Pageable pageable) { return bookRepository.findAll(pageable); } public OptionalBook getBookById(Long id) { return bookRepository.findById(id); } Transactional public Book saveBook(Book book) { // 业务校验例如ISBN不能重复 if (bookRepository.existsByIsbn(book.getIsbn())) { throw new RuntimeException(ISBN已存在); } return bookRepository.save(book); } Transactional public Book updateBook(Long id, Book bookDetails) { Book book bookRepository.findById(id) .orElseThrow(() - new RuntimeException(图书不存在)); // 部分更新避免覆盖如 createTime 等不应修改的字段 book.setTitle(bookDetails.getTitle()); book.setAuthor(bookDetails.getAuthor()); book.setPublisher(bookDetails.getPublisher()); book.setPublishDate(bookDetails.getPublishDate()); book.setPrice(bookDetails.getPrice()); book.setTotalCopies(bookDetails.getTotalCopies()); book.setAvailableCopies(bookDetails.getAvailableCopies()); // updateTime 由 PreUpdate 自动处理 return bookRepository.save(book); } Transactional public void deleteBook(Long id) { bookRepository.deleteById(id); } }关键点解析Service: 标记为 Spring 的业务层组件。Transactional:至关重要。它声明该方法需要事务管理。例如在saveBook中如果 ISBN 重复抛出异常则整个保存操作会回滚保证数据一致性。在 Service 层使用该注解是常见的最佳实践。2.4 控制器与 RESTful API 设计控制器是前后端交互的入口我们设计符合 RESTful 风格的 API。// 文件路径backend/src/main/java/com/example/bookstore/controller/BookController.java package com.example.bookstore.controller; import com.example.bookstore.entity.Book; import com.example.bookstore.service.BookService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/books) RequiredArgsConstructor public class BookController { private final BookService bookService; GetMapping public PageBook listBooks(PageableDefault(size 10, sort id) Pageable pageable) { return bookService.getBooks(pageable); } GetMapping(/{id}) public Book getBook(PathVariable Long id) { return bookService.getBookById(id) .orElseThrow(() - new RuntimeException(图书不存在)); } PostMapping ResponseStatus(HttpStatus.CREATED) public Book createBook(RequestBody Book book) { return bookService.saveBook(book); } PutMapping(/{id}) public Book updateBook(PathVariable Long id, RequestBody Book book) { return bookService.updateBook(id, book); } DeleteMapping(/{id}) ResponseStatus(HttpStatus.NO_CONTENT) public void deleteBook(PathVariable Long id) { bookService.deleteBook(id); } }关键点解析RestController: 组合了Controller和ResponseBody表示该类所有方法的返回值都会自动序列化为 JSON。RequestMapping(“/api/books”): 定义 API 的基础路径。PageableDefault: 为分页参数提供默认值每页10条按id排序。RequestBody: 将 HTTP 请求体中的 JSON 数据绑定到方法参数对象上。HTTP状态码正确使用状态码201创建成功204删除成功无内容404未找到是 RESTful 设计的重要原则。2.5 关键配置数据库、跨域与文档配置文件application.yml是项目的控制中心。# 文件路径backend/src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/book_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password # 请替换为你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境启动时根据实体更新表结构。生产环境务必改为 validate 或 none show-sql: true # 开发时在控制台打印SQL便于调试 properties: hibernate: format_sql: true # 格式化打印的SQL更易读 database-platform: org.hibernate.dialect.MySQL8Dialect server: port: 8080 # 后端服务端口 # 开发环境跨域配置生产环境建议在Nginx或网关层配置 cors: allowed-origins: “http://localhost:5173“ # Vite 默认前端开发服务器地址 allowed-methods: “*” allowed-headers: “*”为了让跨域配置生效需要创建一个配置类// 文件路径backend/src/main/java/com/example/bookstore/config/WebConfig.java package com.example.bookstore.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig { Value(“${cors.allowed-origins}“) private String allowedOrigins; Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(“/api/**“) // 对 /api 路径下的所有请求启用CORS .allowedOrigins(allowedOrigins.split(“,”)) .allowedMethods(“*“) .allowedHeaders(“*“) .allowCredentials(true); } }; } }强烈建议集成 API 文档在pom.xml中添加springdoc-openapi-starter-webmvc-ui依赖启动项目后访问http://localhost:8080/swagger-ui.html即可查看和测试所有接口极大提升前后端协作效率。至此一个具备完整 CRUD 功能的图书管理后端 API 就搭建完成了。启动应用确保能正常访问http://localhost:8080/api/books。3. 前端工程Vue3 TypeScript 构建管理界面3.1 项目初始化与依赖安装在前端目录frontend/下使用 Vite 快速初始化一个 Vue3 TypeScript 项目。# 在 frontend 目录下执行 npm create vitelatest . -- --template vue-ts安装项目所需的核心依赖npm install axios pinia vue-router4 element-plus npm install -D types/node # 提供 Node.js 类型定义方便配置路径别名axios: 用于发送 HTTP 请求。pinia: Vue 3 官方推荐的状态管理库比 Vuex 更简洁直观。vue-router: 实现前端路由。element-plus: 基于 Vue 3 的桌面端组件库能快速搭建美观的界面。3.2 配置 Vite 与解决开发跨域修改vite.config.ts配置路径别名和开发服务器代理这是解决开发阶段跨域问题的关键。// 文件路径frontend/vite.config.ts import { defineConfig } from ‘vite’ import vue from ‘vitejs/plugin-vue’ import { resolve } from ‘path’ export default defineConfig({ plugins: [vue()], resolve: { alias: { ‘’: resolve(__dirname, ‘src’), // 配置 指向 src 目录 }, }, server: { port: 5173, // 前端开发服务器端口 proxy: { // 配置代理将 /api 开头的请求转发到后端 ‘/api’: { target: ‘http://localhost:8080’, // 后端服务地址 changeOrigin: true, }, }, }, })配置代理后前端代码中请求/api/booksVite 开发服务器会自动将其代理到http://localhost:8080/api/books完美绕过浏览器的同源策略限制。3.3 类型定义与 API 封装清晰的类型定义是 TypeScript 项目的基石。首先定义与后端实体对应的接口。// 文件路径frontend/src/types/book.ts export interface Book { id?: number; isbn: string; title: string; author: string; publisher?: string; publishDate?: string; // 前端通常用字符串接收日期 price?: number; totalCopies: number; availableCopies: number; createTime?: string; updateTime?: string; } export interface PageResultT { content: T[]; totalElements: number; totalPages: number; size: number; number: number; }接着封装统一的 API 请求模块避免在组件中直接写axios调用提高代码复用性和可维护性。// 文件路径frontend/src/api/bookApi.ts import axios from ‘axios’; import type { Book, PageResult } from ‘/types/book’; // 创建 axios 实例统一配置 const apiClient axios.create({ baseURL: ‘/api’, // 利用 Vite 代理生产环境需替换为实际后端地址 timeout: 10000, headers: { ‘Content-Type’: ‘application/json’, }, }); export const bookApi { // 分页查询图书列表 getBooks(page: number 0, size: number 10): PromisePageResultBook { return apiClient.get(/books?page${page}size${size}).then(res res.data); }, // 根据ID获取图书 getBookById(id: number): PromiseBook { return apiClient.get(/books/${id}).then(res res.data); }, // 新增图书 createBook(book: Book): PromiseBook { return apiClient.post(‘/books’, book).then(res res.data); }, // 更新图书 updateBook(id: number, book: Book): PromiseBook { return apiClient.put(/books/${id}, book).then(res res.data); }, // 删除图书 deleteBook(id: number): Promisevoid { return apiClient.delete(/books/${id}); }, };3.4 状态管理使用 Pinia使用 Pinia 来管理图书列表的全局状态将数据获取逻辑与组件解耦。// 文件路径frontend/src/store/bookStore.ts import { defineStore } from ‘pinia’; import { ref } from ‘vue’; import type { Book, PageResult } from ‘/types/book’; import { bookApi } from ‘/api/bookApi’; export const useBookStore defineStore(‘book’, () { // 状态 const bookList refBook[]([]); const total ref(0); const loading ref(false); // 动作 (Actions) const fetchBooks async (page: number 0, size: number 10) { loading.value true; try { const result: PageResultBook await bookApi.getBooks(page, size); bookList.value result.content; total.value result.totalElements; } catch (error) { console.error(‘获取图书列表失败:’, error); // 在实际项目中这里可以触发一个全局的 UI 错误提示 throw error; // 将错误抛出由调用方处理 } finally { loading.value false; } }; const addBook async (book: Book) { try { await bookApi.createBook(book); // 新增成功后重新加载当前页数据 await fetchBooks(); } catch (error) { console.error(‘新增图书失败:’, error); throw error; } }; const updateBook async (id: number, book: Book) { try { await bookApi.updateBook(id, book); await fetchBooks(); // 更新后刷新列表 } catch (error) { console.error(‘更新图书失败:’, error); throw error; } }; const deleteBook async (id: number) { try { await bookApi.deleteBook(id); await fetchBooks(); // 删除后刷新列表 } catch (error) { console.error(‘删除图书失败:’, error); throw error; } }; return { // 状态 bookList, total, loading, // 动作 fetchBooks, addBook, updateBook, deleteBook, }; });3.5 页面组件与路由配置首先配置路由定义页面间的导航关系。// 文件路径frontend/src/router/index.ts import { createRouter, createWebHistory } from ‘vue-router’; import BookListView from ‘/views/BookListView.vue’; import BookFormView from ‘/views/BookFormView.vue’; const routes [ { path: ‘/’, redirect: ‘/books’ }, { path: ‘/books’, component: BookListView }, { path: ‘/books/add’, component: BookFormView }, { path: ‘/books/edit/:id’, component: BookFormView, props: true }, // 通过 props 接收路由参数 ]; const router createRouter({ history: createWebHistory(), routes, }); export default router;创建图书列表页组件展示数据并处理分页、删除等操作。!-- 文件路径frontend/src/views/BookListView.vue -- template div class“book-list” el-button type“primary” click“goToAdd”新增图书/el-button el-table :data“store.bookList” v-loading“store.loading” style“width: 100%; margin-top: 20px;” el-table-column prop“isbn” label“ISBN” width“180” / el-table-column prop“title” label“书名” / el-table-column prop“author” label“作者” / el-table-column prop“publisher” label“出版社” / el-table-column prop“availableCopies” label“可借数量” width“100” / el-table-column label“操作” width“180” template #default“scope” el-button size“small” click“editBook(scope.row)”编辑/el-button el-button size“small” type“danger” click“handleDelete(scope.row.id)”删除/el-button /template /el-table-column /el-table el-pagination current-change“handlePageChange” :page-size“pageSize” :total“store.total” :current-page“currentPage” layout“prev, pager, next” style“margin-top: 20px; justify-content: center;” / /div /template script setup lang“ts” import { onMounted, ref } from ‘vue’; import { useRouter } from ‘vue-router’; import { useBookStore } from ‘/store/bookStore’; import { ElMessage, ElMessageBox } from ‘element-plus’; import type { Book } from ‘/types/book’; const router useRouter(); const store useBookStore(); const currentPage ref(1); const pageSize 10; onMounted(() { loadBooks(); }); const loadBooks () { store.fetchBooks(currentPage.value - 1, pageSize); }; const handlePageChange (page: number) { currentPage.value page; loadBooks(); }; const goToAdd () { router.push(‘/books/add’); }; const editBook (book: Book) { router.push(/books/edit/${book.id}); }; const handleDelete async (id: number) { try { await ElMessageBox.confirm(‘确认删除该图书此操作不可撤销。’, ‘提示’, { type: ‘warning’, confirmButtonText: ‘确定’, cancelButtonText: ‘取消’, }); await store.deleteBook(id); ElMessage.success(‘删除成功’); } catch (error) { // 用户点击了取消 console.log(‘取消删除’); } }; /script创建图书表单页组件用于新增和编辑图书。!-- 文件路径frontend/src/views/BookFormView.vue -- template div class“form-container” el-card template #header span{{ isEditMode ? ‘编辑图书’ : ‘新增图书’ }}/span /template el-form :model“form” :rules“rules” ref“formRef” label-width“100px” el-form-item label“ISBN” prop“isbn” el-input v-model“form.isbn” placeholder“请输入ISBN” :disabled“isEditMode” / /el-form-item el-form-item label“书名” prop“title” el-input v-model“form.title” placeholder“请输入书名” / /el-form-item el-form-item label“作者” prop“author” el-input v-model“form.author” placeholder“请输入作者” / /el-form-item el-form-item label“出版社” el-input v-model“form.publisher” placeholder“请输入出版社” / /el-form-item el-form-item label“出版日期” el-date-picker v-model“form.publishDate” type“date” placeholder“选择日期” value-format“YYYY-MM-DD” / /el-form-item el-form-item label“价格” el-input-number v-model“form.price” :min“0” :precision“2” / /el-form-item el-form-item label“总册数” prop“totalCopies” el-input-number v-model“form.totalCopies” :min“1” / /el-form-item el-form-item label“可借册数” prop“availableCopies” el-input-number v-model“form.availableCopies” :min“0” :max“form.totalCopies” / /el-form-item el-form-item el-button type“primary” click“submitForm”提交/el-button el-button click“goBack”取消/el-button /el-form-item /el-form /el-card /div /template script setup lang“ts” import { onMounted, reactive, ref } from ‘vue’; import { useRoute, useRouter } from ‘vue-router’; import { useBookStore } from ‘/store/bookStore’; import { ElMessage, FormInstance, FormRules } from ‘element-plus’; import type { Book } from ‘/types/book’; import { bookApi } from ‘/api/bookApi’; const route useRoute(); const router useRouter(); const store useBookStore(); const formRef refFormInstance(); const isEditMode ref(false); const bookId refnumber(); const form reactiveBook({ isbn: ‘’, title: ‘’, author: ‘’, publisher: ‘’, publishDate: ‘’, price: 0, totalCopies: 1, availableCopies: 1, }); const rules reactiveFormRules({ isbn: [{ required: true, message: ‘请输入ISBN’, trigger: ‘blur’ }], title: [{ required: true, message: ‘请输入书名’, trigger: ‘blur’ }], author: [{ required: true, message: ‘请输入作者’, trigger: ‘blur’ }], totalCopies: [{ required: true, message: ‘请输入总册数’, trigger: ‘blur’ }], availableCopies: [{ required: true, message: ‘请输入可借册数’, trigger: ‘blur’ }], }); onMounted(() { if (route.params.id) { isEditMode.value true; bookId.value Number(route.params.id); loadBookData(bookId.value); } }); const loadBookData async (id: number) { try { const book await bookApi.getBookById(id); Object.assign(form, book); // 将获取的数据合并到表单对象 } catch (error) { ElMessage.error(‘加载图书信息失败’); router.push(‘/books’); } }; const submitForm async () { if (!formRef.value) return; const valid await formRef.value.validate(); if (!valid) return; try { if (isEditMode.value bookId.value) { await store.updateBook(bookId.value, form); ElMessage.success(‘更新成功’); } else { await store.addBook(form); ElMessage.success(‘新增成功’); } router.push(‘/books’); // 返回列表页 } catch (error: any) { ElMessage.error(error.response?.data?.message || ‘操作失败’); } }; const goBack () { router.push(‘/books’); }; /script style scoped .form-container { max-width: 800px; margin: 20px auto; } /style4. 前后端联调与问题排查分别启动后端和前端服务# 后端 (在 backend 目录下) mvn spring-boot:run # 或直接运行启动类 # 前端 (在 frontend 目录下) npm run dev访问http://localhost:5173你应该能看到图书列表页面并能进行增删改查操作。常见联调问题排查清单问题现象可能原因排查步骤网络错误 (Network Error) 或 404后端服务未启动代理配置错误API路径不匹配。1. 访问http://localhost:8080/api/books检查后端是否正常。2. 检查vite.config.ts中的proxy.target端口是否正确。3. 核对前端请求的 URL 和后端RequestMapping的路径是否一致注意大小写和复数。跨域错误 (CORS)后端未配置 CORS 或配置不正确Vite 代理未生效。1. 确保后端WebConfig已正确配置并生效。2. 检查前端请求的 Origin 是否在允许列表中。3. 生产环境应在 Nginx 或网关层配置 CORS。400 Bad Request请求体格式错误字段类型不匹配。1. 检查前端axios请求头Content-Type是否为application/json。2. 对比前端Book接口和后端Book实体类的字段名和类型特别是日期格式。3. 查看后端控制台日志SpringBoot 会打印详细的绑定错误信息。500 Internal Server Error后端业务逻辑异常数据库操作失败。1.首要步骤查看后端控制台输出的异常堆栈信息。2. 常见原因数据库连接失败、SQL语法错误、空指针、业务校验抛出异常未捕获。数据不显示或显示错误请求成功但数据结构不匹配前端渲染逻辑错误。1. 打开浏览器开发者工具的Network标签查看 API 响应数据是否正确。2. 检查前端Console是否有 JavaScript 或 TypeScript 错误。3. 核对前端定义的interface与后端返回的 JSON 字段名、类型是否完全一致。5. 项目打包与生产环境部署5.1 前端打包前端项目使用 Vite 进行构建生成静态资源。cd frontend npm run build命令执行后会在frontend/dist目录下生成优化后的 HTML、CSS、JavaScript 文件。这个dist文件夹就是需要部署到 Web 服务器的内容。5.2 后端打包后端使用 Maven 打包成可执行的 JAR 文件。cd backend mvn clean package -DskipTests打包成功后在backend/target目录下会生成一个类似bookstore-0.0.1-SNAPSHOT.jar的文件。5.3 生产环境部署方案方案一传统分离部署推荐这是最清晰、最常用的部署方式。部署后端将 JAR 包上传到服务器使用java -jar启动。为了进程常驻和管理推荐使用systemd创建服务。# 创建服务文件 /etc/systemd/system/bookstore.service [Unit] DescriptionBookstore Backend Service Afternetwork.target [Service] Useryour_user ExecStart/usr/bin/java -jar /path/to/bookstore.jar SuccessExitStatus143 Restartalways [Install] WantedBymulti-user.target然后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable bookstore sudo systemctl start bookstore sudo systemctl status bookstore # 查看状态部署前端将dist文件夹内的所有文件上传到服务器例如/var/www/bookstore然后配置 Nginx 作为 Web 服务器和反向代理。# /etc/nginx/conf.d/bookstore.conf server { listen 80; server_name your_domain.com; # 替换为你的域名或服务器IP # 静态资源服务 location / { root /var/www/bookstore; index index.html; try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式 } # API 反向代理 location /api/ { proxy_pass http://localhost:8080; # 代理到后端服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }重启 Nginx 使配置生效sudo systemctl restart nginx。方案二Docker 容器化部署适合需要环境一致性、快速扩展的场景。后端 Dockerfile:# backend/Dockerfile FROM openjdk:17-jdk-slim VOLUME /tmp ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [“java”, “-jar”, “/app.jar”]前端 Dockerfile:# frontend/Dockerfile FROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.confnginx.conf内容与上述 Nginx 配置类似。使用 docker-compose.yml 编排:# docker-compose.yml version: ‘3.8’ services: mysql: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_DATABASE: book_db volumes: - mysql_data:/var/lib/mysql ports: - “3306:3306” backend: build: ./backend ports: - “8080:8080” environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/book_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root_password depends_on: - mysql frontend: build: ./frontend ports: - “80:80” depends_on: - backend运行docker-compose up -d即可一键启动所有服务。6. 进阶考量与最佳实践一个可用的系统只是起点要用于生产环境还需考虑以下方面API 鉴权与安全集成 Spring Security JWT。用户登录后前端在请求头中携带Authorization: Bearer token后端校验 Token 有效性并控制接口访问权限。输入校验在后端 Controller 方法参数上使用Valid注解并在 DTO 类字段上使用NotBlank,Size,Pattern等注解进行校验防止非法数据进入业务逻辑。全局异常处理使用ControllerAdvice和ExceptionHandler创建全局异常处理器统一处理业务异常、参数校验异常等并返回结构化的错误信息给前端。日志与监控配置logback-spring.xml将日志按级别输出到文件。集成 Spring Boot Actuator 提供健康检查端点 (/actuator/health)。考虑使用 Prometheus Grafana 或 SkyWalking 进行应用性能监控 (APM)。数据库优化为高频查询字段如isbn,title建立索引。对于复杂的多表关联查询考虑使用Query编写优化后的 JPQL 或原生 SQL。前端性能对于大型列表考虑使用虚拟滚动如vue-virtual-scroller。对不变的数据使用缓存策略。利用 Vite 的代码分割功能按需加载组件。代码质量前后端分别引入 ESLint 和 Checkstyle 进行代码规范检查。使用 Prettier 统一代码格式。建立 Git 提交规范并考虑使用 CI/CD 工具如 Jenkins, GitLab CI自动化构建、测试和部署流程。通过这个完整的项目实践你不仅掌握了 SpringBoot3、Vue3 和 TypeScript 的核心用法更重要的是理解了如何将它们有机地组合起来构建一个可维护、可扩展的全栈应用。从环境搭建到线上部署每一步都力求清晰、可操作。建议你按照这个流程亲手实现一遍遇到问题正是深入学习的机会。在此基础上你可以继续扩展用户管理、借阅归还流程、数据统计图表、权限控制RBAC等模块逐步完善成一个功能齐全的管理系统。 30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度