1. 为什么选择SpringBoot+Vue来搭建图书馆管理系统?
如果你正在寻找一个能快速上手、技术栈主流、并且能完整覆盖前后端开发的实战项目,那这个SpringBoot+Vue的图书馆管理系统绝对是你的不二之选。我做了这么多年全栈开发,带过不少新人,发现很多朋友学了一堆零散的技术点,但一到自己动手做项目就懵了,前后端怎么对接?数据库怎么设计?部署上线又该怎么办?这个项目就能帮你把这些问题一次性全打通。
简单来说,这个系统就是一个模拟真实图书馆运作的Web应用。管理员可以在后台管理图书、用户、处理借阅和归还;普通用户则可以在网页前端浏览图书、借书还书、查看自己的记录。听起来是不是挺熟悉的?对,这就是一个典型的CRUD(增删改查)应用,但它麻雀虽小五脏俱全,涵盖了用户认证、权限管理、数据关联、前后端分离通信等核心企业级开发场景。
那为什么偏偏是SpringBoot和Vue呢?我跟你打个比方,SpringBoot就像是一个已经帮你配好所有调料和厨具的智能厨房。你想炒个菜(开发个后端服务),不用再自己去买锅碗瓢盆(配置各种XML、部署服务器),直接开火就行。它内嵌了Tomcat服务器,用几个简单的注解就能快速构建出稳定可靠的RESTful API,大大降低了Java后端开发的门槛。而Vue.js呢,就像是一套乐高积木。它用组件化的思想,让你可以像搭积木一样构建用户界面。一个搜索框、一个图书列表、一个表单,都可以做成独立的组件,拼装起来既灵活又高效。而且它的数据绑定特性特别友好,你改了下层的数据,页面视图会自动更新,不用再手动去操作那些繁琐的DOM。
把这两个技术结合起来,采用前后端分离的架构,优势就非常明显了。后端SpringBoot专注提供数据接口,做好业务逻辑和数据库安全;前端Vue专注呈现交互友好的界面。两者通过HTTP API(主要是JSON格式)通信,分工明确,耦合度低。这意味着前端可以独立开发调试,后端接口也可以被小程序、APP等其他客户端复用。这种架构正是现在企业里最流行的做法。接下来,我就带你从零开始,一步步把这个系统搭建起来,我会把每个环节的细节、容易踩的坑都讲清楚,保证你跟着做就能跑通。
2. 手把手搭建开发环境与项目骨架
工欲善其事,必先利其器。在开始写代码之前,咱们得先把“战场”布置好。别担心,我用的都是最主流的免费工具,跟着我的步骤来,半小时内准能搞定。
2.1 后端开发环境准备
后端我们使用Java和SpringBoot。首先,确保你的电脑上安装了JDK 1.8或以上版本。打开命令行,输入 java -version 和 javac -version 检查一下。如果没有,去Oracle官网或者AdoptOpenJDK下载安装就行。
接下来是项目管理和构建工具Maven。它负责帮你下载项目依赖的第三方库(Jar包)。去Apache官网下载Maven,解压后,需要配置一下系统的环境变量 MAVEN_HOME,并把 %MAVEN_HOME%\bin 添加到Path里。完成后,在命令行输入 mvn -v,能显示版本号就成功了。
我最推荐的集成开发环境(IDE)是 IntelliJ IDEA 的社区版,完全免费,对Java和SpringBoot的支持是顶级的。当然,你用Eclipse或者VS Code也可以,但IDEA的智能提示和快捷键能让你效率翻倍。安装好IDEA后,我们开始创建SpringBoot项目。不用去官网那个Initializr网站,IDEA内置了创建向导。点击 New Project,选择 Spring Initializr,这里有几个关键点要填对:
- Project SDK:选择你刚安装的JDK 1.8。
- Type:选择
Maven Project。 - Group:填你的组织域名倒写,比如
com.yourname。 - Artifact:填项目名,比如
library-backend。 - 在
Dependencies里,我们勾选几个核心依赖:Spring Web(用于构建Web接口)、Spring Data JPA(简化数据库操作)、MySQL Driver(连接MySQL数据库)、Lombok(用注解自动生成getter/setter等方法,让代码更简洁)。
点击创建,IDEA会自动生成项目结构并下载依赖。第一次可能会慢一点,耐心等待。项目创建好后,你会在 pom.xml 文件里看到我们刚才选的依赖。我建议再加一个 spring-boot-starter-validation 依赖,用于接口参数校验,非常实用。直接在 pom.xml 的 <dependencies> 标签里加上这段:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2 前端开发环境准备
前端我们使用Vue.js。首先需要安装 Node.js,它自带了包管理工具npm。去Node.js官网下载LTS(长期支持版)安装。安装完成后,打开命令行,输入 node -v 和 npm -v 检查是否成功。
接下来安装Vue的官方脚手架工具 Vue CLI,它能快速生成标准化的Vue项目结构。在命令行运行:npm install -g @vue/cli。安装完成后,输入 vue --version 确认。
现在,在你喜欢的位置(比如和backend项目同级目录),打开命令行,运行 vue create library-frontend。Vue CLI会交互式地让你选择配置。对于这个项目,我建议:
- 选择
Manually select features(手动选择特性)。 - 勾选
Babel(ES6语法转换)、Router(路由管理)、Vuex(状态管理)、CSS Pre-processors(CSS预处理器,比如Sass)。 - 选择
Sass/SCSS (with dart-sass)。 - 路由模式选择
History mode,输入Y。 - 配置文件位置选择
In dedicated config files。 - 是否保存为未来项目的模板,可以选
N。
等待创建完成,进入项目目录 cd library-frontend,先运行 npm run serve 试试。如果浏览器打开 http://localhost:8080 能看到Vue的欢迎页,说明前端环境也OK了。
2.3 数据库设计与初始化
系统离不开数据存储,我们选用最流行的开源数据库 MySQL。你可以安装MySQL 5.7或8.0版本,也可以使用更轻量的Docker来运行,看个人习惯。安装好后,用命令行或者Navicat、MySQL Workbench这样的图形化工具登录。
首先创建一个专门给这个项目用的数据库:CREATE DATABASE library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。这里用 utf8mb4 字符集是为了支持存储Emoji等特殊字符。
这个图书馆系统核心的表大概有这几张:
- 用户表 (user):存用户ID、用户名、密码(加密后)、角色(管理员/普通用户)、联系方式等。
- 图书分类表 (book_category):存分类ID、分类名称。
- 图书信息表 (book):存图书ID、书名、作者、出版社、ISBN、分类ID(外键关联分类表)、库存数量、封面图片地址等。
- 图书借阅记录表 (borrow_record):存记录ID、用户ID、图书ID、借阅时间、应还时间、实际归还时间、状态(借出/已归还/超期)等。
- 罚金记录表 (fine_record):存记录ID、借阅记录ID、罚金金额、是否缴纳等。
表之间的关系是:一个分类有多本图书,一个用户可以有多条借阅记录,一本图书也可以被多次借阅(通过记录关联),一条借阅记录可能产生一条罚金记录。你可以在数据库中手动建表,但我强烈建议把建表SQL语句写成文件(比如 init.sql),这样便于版本管理和重置。然后在SpringBoot的 application.yml 配置文件中,配置数据库连接信息:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root # 换成你的数据库用户名
password: yourpassword # 换成你的数据库密码
jpa:
hibernate:
ddl-auto: update # 启动时根据实体类自动更新表结构,生产环境建议用none或validate
show-sql: true # 在控制台打印SQL语句,调试时非常有用
3. 后端核心功能实现与代码精讲
环境搭好,骨架建完,现在该注入“灵魂”了。后端代码我们采用经典的MVC分层架构:Controller接收请求,Service处理业务逻辑,Repository(DAO)操作数据库。这样职责清晰,方便维护。
3.1 实体类与数据访问层
首先,根据数据库表设计,创建对应的Java实体类。以 Book(图书)实体为例,我们使用JPA注解来映射数据库字段。这里强烈推荐使用Lombok,它能用 @Data 注解省去一大堆getter、setter、toString方法的编写。
package com.yourname.library.entity;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "book")
@Data
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title; // 书名
private String author; // 作者
private String publisher; // 出版社
private String isbn; // ISBN号
@ManyToOne
@JoinColumn(name = "category_id")
private BookCategory category; // 关联分类
private Integer totalStock; // 总库存
private Integer availableStock; // 可借库存
private String coverImage; // 封面图片URL
@Column(precision = 10, scale = 2)
private BigDecimal price; // 价格
// 省略构造方法,Lombok @Data 会默认生成
}
注意 @ManyToOne 注解,它定义了图书与分类的“多对一”关系。对应的,在 BookCategory 实体中,可以用 @OneToMany(mappedBy = "category") 定义反向的“一对多”关系。
实体类建好后,创建数据访问接口。Spring Data JPA的强大之处在于,你只需要定义一个继承 JpaRepository 的接口,基本的增删改查方法它就自动给你实现了。比如 BookRepository:
package com.yourname.library.repository;
import com.yourname.library.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {
// 根据书名模糊查询
List<Book> findByTitleContaining(String title);
// 根据作者查询
List<Book> findByAuthor(String author);
// 使用JPQL自定义复杂查询:查询某个分类下库存大于0的图书
@Query("SELECT b FROM Book b WHERE b.category.id = :categoryId AND b.availableStock > 0")
List<Book> findAvailableBooksByCategory(@Param("categoryId") Long categoryId);
}
继承 JpaSpecificationExecutor 接口后,你还可以使用更灵活的动态查询条件,这在做多条件组合搜索时特别好用。
3.2 业务逻辑层与服务
Service层是业务逻辑的核心。它调用Repository获取数据,进行业务规则校验和处理,然后返回结果给Controller。这里以 BookService 和借书业务为例:
package com.yourname.library.service;
import com.yourname.library.entity.Book;
import com.yourname.library.entity.BorrowRecord;
import com.yourname.library.entity.User;
import com.yourname.library.exception.BusinessException;
import com.yourname.library.repository.BookRepository;
import com.yourname.library.repository.BorrowRecordRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor // Lombok注解,为final字段生成构造函数,实现依赖注入
public class BookService {
private final BookRepository bookRepository;
private final BorrowRecordRepository borrowRecordRepository;
// 借书业务,使用事务保证数据一致性
@Transactional
public BorrowRecord borrowBook(Long bookId, User user) {
// 1. 检查图书是否存在且可借
Book book = bookRepository.findById(bookId)
.orElseThrow(() -> new BusinessException("图书不存在"));
if (book.getAvailableStock() <= 0) {
throw new BusinessException("该图书暂无库存");
}
// 2. 检查用户是否有超期未还或罚金未缴记录(这里简化,实际可能需单独查询)
// ...
// 3. 创建借阅记录
BorrowRecord record = new BorrowRecord();
record.setBook(book);
record.setUser(user);
record.setBorrowTime(LocalDateTime.now());
record.setDueTime(LocalDateTime.now().plusDays(30)); // 假设借期30天
record.setStatus("BORROWED");
// 4. 更新图书库存
book.setAvailableStock(book.getAvailableStock() - 1);
bookRepository.save(book); // 保存库存更新
// 5. 保存借阅记录
return borrowRecordRepository.save(record);
}
// 其他方法:还书、查询图书列表、添加图书等...
}
这里有几个关键点:第一,使用了 @Transactional 注解,这意味着如果借书过程中任何一步失败(比如库存更新了但记录没存进去),整个操作会回滚,数据不会错乱。第二,我们自定义了一个 BusinessException(运行时异常),用于抛出业务错误,方便Controller层统一捕获并返回友好的错误信息给前端。第三,使用了Lombok的 @RequiredArgsConstructor 进行依赖注入,代码比传统的 @Autowired 更简洁。
3.3 控制层与RESTful API设计
Controller层负责接收HTTP请求,调用Service,并返回HTTP响应。我们遵循RESTful风格设计API,让接口清晰易懂。同时,使用 @Valid 注解配合实体类中的校验注解(如 @NotBlank)来做参数校验。
package com.yourname.library.controller;
import com.yourname.library.entity.Book;
import com.yourname.library.service.BookService;
import com.yourname.library.vo.BookVO;
import com.yourname.library.vo.ResultVO;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/books")
@RequiredArgsConstructor
public class BookController {
private final BookService bookService;
// 分页查询图书列表
@GetMapping
public ResultVO<Page<BookVO>> getBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).descending());
Page<BookVO> bookPage = bookService.getBooks(pageable);
return ResultVO.success(bookPage);
}
// 根据ID获取图书详情
@GetMapping("/{id}")
public ResultVO<BookVO> getBookById(@PathVariable Long id) {
BookVO book = bookService.getBookById(id);
return ResultVO.success(book);
}
// 添加新图书
@PostMapping
public ResultVO<Book> addBook(@RequestBody @Valid Book book) { // @Valid 触发校验
Book savedBook = bookService.addBook(book);
return ResultVO.success(savedBook);
}
// 借书接口
@PostMapping("/{bookId}/borrow")
public ResultVO borrowBook(@PathVariable Long bookId, @RequestParam Long userId) {
// 这里需要从请求中获取当前登录用户,简化起见用userId参数
bookService.borrowBook(bookId, userId);
return ResultVO.success("借书成功");
}
// 统一异常处理(可以放在一个单独的@ControllerAdvice类中)
@ExceptionHandler(BusinessException.class)
public ResultVO handleBusinessException(BusinessException e) {
return ResultVO.error(400, e.getMessage());
}
}
注意,我们返回给前端的数据通常不是原始的实体对象,而是封装了一层 ResultVO(包含code、message、data),并且实体对象也经常转换为 BookVO(View Object)来过滤掉一些敏感或不必要的字段(如密码),或者组合一些关联数据,这能更好地控制API的输出。分页查询使用了Spring Data JPA的 Page 和 Pageable 对象,前端只需要传页码和每页大小,后端就能返回带分页信息的结果,非常方便。
4. 前端Vue组件开发与界面交互
后端API准备好了,现在我们来构建用户能看到和操作的界面。Vue的核心思想是组件化,我们将系统拆分成一个个可复用的组件。
4.1 项目结构规划与路由配置
首先,规划一下 src 目录下的结构。我习惯这样组织:
src/
├── api/ # 所有与后端交互的请求函数,使用axios封装
├── assets/ # 静态资源(图片、字体等)
├── components/ # 公共组件(如导航栏、分页器、搜索框)
├── router/ # Vue Router路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数(如请求拦截器、日期格式化)
├── views/ # 页面级组件(如首页、图书列表页、个人中心页)
└── App.vue # 根组件
在 router/index.js 中配置路由。我们需要根据用户角色(管理员/普通用户)来动态加载路由,这可以通过路由守卫(beforeEach)来实现。
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import { getToken } from '@/utils/auth' // 假设从本地存储获取token
Vue.use(VueRouter)
// 公共路由(无需登录)
const constantRoutes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
},
{
path: '/books',
name: 'BookList',
component: () => import('@/views/book/BookList.vue')
}
]
// 需要登录才能访问的动态路由
const asyncRoutes = [
{
path: '/user/borrow',
name: 'MyBorrow',
component: () => import('@/views/user/MyBorrow.vue'),
meta: { roles: ['user', 'admin'] } // 用户和管理员都能访问
},
{
path: '/admin/book-manage',
name: 'BookManage',
component: () => import('@/views/admin/BookManage.vue'),
meta: { roles: ['admin'] } // 仅管理员
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: constantRoutes
})
// 路由守卫:检查登录状态和权限
router.beforeEach((to, from, next) => {
const hasToken = getToken()
if (to.meta && to.meta.roles) {
// 需要权限的路由
if (!hasToken) {
next('/login')
} else {
// 这里需要从Vuex或API获取当前用户角色
const userRole = 'user' // 假设当前角色
if (to.meta.roles.includes(userRole)) {
next()
} else {
next('/403') // 跳转到无权限页面
}
}
} else {
next() // 公共路由直接放行
}
})
export default router
4.2 核心页面组件开发
以用户端的“图书列表页” (BookList.vue) 为例,它通常包含搜索框、图书卡片列表和分页组件。
<template>
<div class="book-list-page">
<!-- 搜索区域 -->
<div class="search-bar">
<el-input
v-model="searchParams.keyword"
placeholder="输入书名或作者"
@keyup.enter="handleSearch"
>
<el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
</el-input>
<el-select v-model="searchParams.categoryId" placeholder="全部分类" @change="handleSearch">
<el-option label="全部分类" :value="null"></el-option>
<el-option
v-for="cat in categories"
:key="cat.id"
:label="cat.name"
:value="cat.id"
></el-option>
</el-select>
</div>
<!-- 图书列表 -->
<div class="book-list">
<el-row :gutter="20">
<el-col
v-for="book in bookList"
:key="book.id"
:xs="24"
:sm="12"
:md="8"
:lg="6"
class="book-col"
>
<book-card :book="book" @borrow="handleBorrow(book.id)" />
</el-col>
</el-row>
</div>
<!-- 分页 -->
<div class="pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
>
</el-pagination>
</div>
</div>
</template>
<script>
import { getBookList, getCategories } from '@/api/book'
import BookCard from '@/components/BookCard.vue'
export default {
name: 'BookList',
components: { BookCard },
data() {
return {
searchParams: {
keyword: '',
categoryId: null
},
bookList: [],
categories: [],
pagination: {
current: 1,
size: 10,
total: 0
}
}
},
created() {
this.fetchCategories()
this.fetchBookList()
},
methods: {
async fetchCategories() {
try {
const res = await getCategories()
this.categories = res.data
} catch (error) {
this.$message.error('获取分类失败')
}
},
async fetchBookList() {
try {
const params = {
...this.searchParams,
page: this.pagination.current - 1, // 后端页码通常从0开始
size: this.pagination.size
}
const res = await getBookList(params)
this.bookList = res.data.content
this.pagination.total = res.data.totalElements
} catch (error) {
this.$message.error('获取图书列表失败')
}
},
handleSearch() {
this.pagination.current = 1 // 搜索时回到第一页
this.fetchBookList()
},
handleSizeChange(size) {
this.pagination.size = size
this.fetchBookList()
},
handleCurrentChange(current) {
this.pagination.current = current
this.fetchBookList()
},
async handleBorrow(bookId) {
try {
await this.$confirm('确认借阅这本书吗?', '提示', { type: 'warning' })
await borrowBook(bookId) // 调用借书API
this.$message.success('借阅成功!')
this.fetchBookList() // 刷新列表,更新库存显示
} catch (error) {
if (error !== 'cancel') {
this.$message.error(error.message || '借阅失败')
}
}
}
}
}
</script>
<style lang="scss" scoped>
.book-list-page {
padding: 20px;
.search-bar {
margin-bottom: 20px;
display: flex;
gap: 15px;
}
.pagination {
margin-top: 30px;
text-align: center;
}
}
</style>
这个组件展示了几个Vue的核心特性:v-model 实现表单双向绑定;v-for 渲染列表;@click、@change 事件监听;以及使用 async/await 处理异步的API请求。我们引入了Element UI组件库(需要提前安装:npm i element-ui -S)来快速搭建美观的UI。BookCard 是一个独立的子组件,负责展示单本图书的卡片,这样代码更清晰,也便于复用。
4.3 状态管理与API请求封装
对于跨组件共享的状态,比如用户登录信息,我们使用 Vuex 来管理。在 store 目录下创建模块:
// store/modules/user.js
const state = {
token: localStorage.getItem('token') || '',
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null
}
const mutations = {
SET_TOKEN(state, token) {
state.token = token
localStorage.setItem('token', token)
},
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo
localStorage.setItem('userInfo', JSON.stringify(userInfo))
},
CLEAR_USER(state) {
state.token = ''
state.userInfo = null
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
}
const actions = {
login({ commit }, { username, password }) {
return new Promise((resolve, reject) => {
// 调用封装的API
loginApi({ username, password }).then(res => {
commit('SET_TOKEN', res.data.token)
commit('SET_USER_INFO', res.data.user)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
logout({ commit }) {
commit('CLEAR_USER')
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
所有与后端的HTTP请求,我们都用 axios 进行统一封装,好处是能集中处理请求拦截(如自动添加token)、响应拦截(如统一处理错误)和基础配置。
// utils/request.js
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 在 .env.development 等环境变量文件中配置
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + store.getters.token
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 假设后端统一返回格式为 { code: 200, message: 'success', data: ... }
if (res.code !== 200) {
Message.error(res.message || 'Error')
// 如果是token过期等特定错误,可以触发登出
if (res.code === 401) {
store.dispatch('user/logout').then(() => {
location.reload() // 重新登录
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error)
Message.error(error.message || '网络请求失败')
return Promise.reject(error)
}
)
export default service
然后在 api/book.js 中,引入这个 request 实例,定义具体的接口函数:
import request from '@/utils/request'
export function getBookList(params) {
return request({
url: '/api/books',
method: 'get',
params
})
}
export function borrowBook(bookId) {
return request({
url: `/api/books/${bookId}/borrow`,
method: 'post'
})
}
5. 项目打包与生产环境部署
代码都写好了,在本地跑得也挺顺畅,最后一步就是把它部署到服务器上,让所有人都能访问。这是从开发到上线的临门一脚,同样很重要。
5.1 前后端项目打包
首先,我们需要把源代码转换成生产环境能直接运行的“成品”。
后端打包:在SpringBoot项目的根目录(有 pom.xml 的目录)下,打开命令行,运行 mvn clean package -DskipTests。这个命令会清理旧的构建,跳过测试,然后打包。完成后,你会在 target 目录下找到一个名字类似 library-backend-0.0.1-SNAPSHOT.jar 的文件。这就是一个可执行的 Fat JAR,它里面包含了你的应用代码、依赖库以及内嵌的Tomcat服务器。你可以用 java -jar library-backend-0.0.1-SNAPSHOT.jar 直接运行它,默认端口是8080。
前端打包:进入Vue项目目录,运行 npm run build。这个命令会启动Webpack进行代码压缩、混淆、Tree Shaking等优化,最终在 dist 目录下生成静态资源文件(HTML、CSS、JS)。这里有个关键点:因为我们是前后端分离,前端打包后需要知道后端API的地址。我们之前在 request.js 里用了 process.env.VUE_APP_BASE_API,这个值可以在项目根目录创建 .env.production 文件来设置:VUE_APP_BASE_API = 'http://你的服务器IP或域名/api'。打包命令会自动使用这个生产环境配置。
5.2 服务器环境配置与部署
假设你有一台云服务器(CentOS 7.x 或 Ubuntu 20.04)。部署的典型架构是:Nginx作为反向代理和静态资源服务器,SpringBoot JAR包作为后端应用运行。
-
安装Java和Nginx:
# CentOS sudo yum install -y java-1.8.0-openjdk nginx # Ubuntu sudo apt update sudo apt install -y openjdk-8-jdk nginx -
部署后端:将打包好的
xxx.jar上传到服务器,比如/opt/app/目录。可以创建一个Systemd服务来管理它,这样能开机自启、方便查看日志。创建文件/etc/systemd/system/library.service:[Unit] Description=Library Backend Service After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/app ExecStart=/usr/bin/java -jar library-backend-0.0.1-SNAPSHOT.jar Restart=on-failure [Install] WantedBy=multi-user.target然后运行
sudo systemctl daemon-reload,sudo systemctl start library,sudo systemctl enable library启动并设置开机自启。用sudo systemctl status library查看状态,用journalctl -u library -f跟踪日志。 -
部署前端:将
dist目录下的所有文件上传到服务器,比如/usr/share/nginx/html/library。 -
配置Nginx:编辑Nginx配置文件,通常是
/etc/nginx/conf.d/library.conf:server { listen 80; server_name your-domain.com; # 你的域名或IP # 前端静态文件 location / { root /usr/share/nginx/html/library; index index.html; try_files $uri $uri/ /index.html; # 支持Vue的history模式 } # 反向代理到后端API location /api/ { proxy_pass http://127.0.0.1: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; } }检查配置语法:
sudo nginx -t,无误后重载配置:sudo nginx -s reload。
5.3 域名、HTTPS与持续集成
要让项目更专业、更安全,还有几步可以优化:
- 域名绑定:在域名DNS管理后台,将你的域名A记录解析到服务器公网IP。
- HTTPS加密:现在网站没有小绿锁(HTTPS)几乎不可接受。你可以使用 Let‘s Encrypt 免费申请SSL证书。安装Certbot工具,运行一条命令就能自动为你的Nginx配置HTTPS,它会自动处理证书申请和续期。
- 数据库优化:生产环境的MySQL连接建议使用连接池(如HikariCP,SpringBoot默认集成),并在
application-prod.yml中调整连接参数。数据库最好与应用服务器分离,并定期备份。 - 持续集成/持续部署:如果你后续代码有更新,每次都手动上传打包太麻烦。可以搭建一套简单的CI/CD流程,比如使用 Jenkins 或 GitHub Actions。当你把代码推送到Git仓库的特定分支时,自动触发脚本完成测试、打包、部署到服务器的全过程,实现自动化运维。
走到这一步,你的SpringBoot+Vue图书馆管理系统就已经从一个本地项目,变成了一个在互联网上可访问的、功能完整的Web应用了。这个过程里,你不仅学会了具体的技术,更重要的是掌握了全栈开发的完整工作流。我建议你把源码和部署过程好好整理一下,这不仅是你的学习成果,未来面试或者接一些小项目,这都是非常拿得出手的实战经验。如果在部署过程中遇到端口冲突、权限问题或者跨域报错,别慌,多看看日志,九成的问题都能在日志里找到答案。

879

被折叠的 条评论
为什么被折叠?



