大家好,我是Java1234_小锋老师,分享一套锋哥原创的SpringBoot4+Vue3图书馆(图书借阅)管理系统。

项目介绍
随着信息技术的迅猛发展和高校图书馆藏书规模的不断扩大,传统以人工登记、纸质台账为主的图书借阅管理方式已难以满足读者日益增长的自助化、实时化服务需求,普遍存在借阅效率低、库存统计不准确、超期记录难以追踪等问题。为了解决上述问题,本文设计并实现了一套基于SpringBoot与Vue3的图书馆(图书借阅)管理系统,旨在实现图书资源、读者信息和借阅流程的数字化、规范化管理。
本系统采用目前主流的前后端分离架构进行开发。后端以SpringBoot 4作为基础框架,使用MyBatis-Plus作为持久层框架简化数据库访问,通过Spring Security结合JWT实现无状态的身份认证与权限控制,数据库选用MySQL 8。前端基于Vue3的组合式API进行开发,采用Vite作为构建工具,使用Element Plus组件库搭建用户界面,借助Pinia进行状态管理、Vue Router实现路由控制、Axios完成与后端的数据交互,并利用ECharts实现借阅数据的可视化统计。
系统根据使用角色划分为管理员后台和读者前台两大子系统。管理员可以进行图书分类管理、图书信息管理、读者信息管理、借阅与归还办理、借阅记录查询以及首页数据统计等操作;读者可以进行图书检索浏览、在线自助借阅、续借、归还、查看个人借阅记录以及维护个人资料。系统还实现了借阅上限控制、库存联动、超期罚款自动计算等核心业务逻辑。经过功能测试,系统运行稳定、操作便捷,达到了预期的设计目标,具有一定的实用价值。
源码下载
链接: https://pan.baidu.com/s/1Te2otiuCH5p5pJnVdmD-Yw?pwd=1234 提取码: 1234
系统展示 






核心代码
package com.java1234.controller;
import com.java1234.common.Result;
import com.java1234.dto.BorrowCreateRequest;
import com.java1234.dto.ReaderBorrowRequest;
import com.java1234.security.SecurityUtils;
import com.java1234.service.BorrowService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 借阅管理控制器
*/
@RestController
@RequestMapping("/api/borrow")
public class BorrowController {
private final BorrowService borrowService;
/**
* 构造借阅控制器
* @param borrowService 借阅服务
*/
public BorrowController(BorrowService borrowService) {
this.borrowService = borrowService;
}
/**
* 借阅记录列表(管理员)
* @param page 页码
* @param pageSize 每页条数
* @param keyword 关键字
* @param status 状态
* @param activeOnly 仅在借
* @return 分页结果
*/
@GetMapping("/list")
public Result list(@RequestParam(defaultValue = "1") int page,
@RequestParam(name = "page_size", defaultValue = "10") int pageSize,
@RequestParam(defaultValue = "") String keyword,
@RequestParam(defaultValue = "0") Integer status,
@RequestParam(name = "active_only", defaultValue = "0") Integer activeOnly) {
SecurityUtils.requireAdmin();
return borrowService.list(page, pageSize, keyword, status, activeOnly);
}
/**
* 我的借阅记录(读者)
* @param page 页码
* @param pageSize 每页条数
* @param status 状态
* @return 分页结果
*/
@GetMapping("/my")
public Result my(@RequestParam(defaultValue = "1") int page,
@RequestParam(name = "page_size", defaultValue = "10") int pageSize,
@RequestParam(defaultValue = "0") Integer status) {
return borrowService.myBorrows(page, pageSize, status);
}
/**
* 管理员借书
* @param req 借书请求
* @return 统一响应
*/
@PostMapping("/lend")
public Result lend(@RequestBody BorrowCreateRequest req) {
SecurityUtils.requireAdmin();
return borrowService.lend(req);
}
/**
* 读者自助借书
* @param req 借书请求
* @return 统一响应
*/
@PostMapping("/reader-borrow")
public Result readerBorrow(@RequestBody ReaderBorrowRequest req) {
return borrowService.readerBorrow(req);
}
/**
* 还书
* @param id 借阅记录ID
* @return 统一响应
*/
@PostMapping("/return/{id}")
public Result returnBook(@PathVariable Integer id) {
return borrowService.returnBook(id);
}
/**
* 续借
* @param id 借阅记录ID
* @return 统一响应
*/
@PostMapping("/renew/{id}")
public Result renew(@PathVariable Integer id) {
return borrowService.renew(id);
}
}
<template>
<el-container class="layout-container">
<!– 侧边栏 –>
<el-aside :width="isCollapse ? '64px' : '220px'" class="layout-aside reader-aside">
<div class="logo-area">
<el-icon :size="28"><Reading /></el-icon>
<span v-show="!isCollapse" class="logo-text">读者中心</span>
</div>
<el-menu
:default-active="route.path"
:collapse="isCollapse"
router
background-color="#2c3e50"
text-color="#bdc3c7"
active-text-color="#67c23a"
class="side-menu"
>
<el-menu-item v-for="item in menuList" :key="item.path" :index="item.path">
<el-icon><component :is="item.icon" /></el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header class="layout-header">
<div class="header-left">
<el-icon class="collapse-btn" @click="isCollapse = !isCollapse">
<Fold v-if="!isCollapse" /><Expand v-else />
</el-icon>
<el-breadcrumb separator="/">
<el-breadcrumb-item>读者</el-breadcrumb-item>
<el-breadcrumb-item>{{ route.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="header-right">
<el-dropdown @command="handleCommand">
<span class="user-info">
<el-avatar :size="32" :src="userStore.avatar || undefined" style="background: #67c23a">
{{ userStore.realName?.charAt(0) }}
</el-avatar>
<span class="user-name">{{ userStore.realName }}</span>
<el-icon><ArrowDown /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile">个人中心</el-dropdown-item>
<el-dropdown-item command="logout" divided>安全退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="layout-main">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script setup>
/**
* 读者布局组件
*/
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
import { ElMessageBox } from 'element-plus'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const isCollapse = ref(false)
const menuList = [
{ path: '/reader/books', title: '图书浏览', icon: 'Search' },
{ path: '/reader/my-borrow', title: '我的借阅', icon: 'Collection' },
]
function handleCommand(cmd) {
if (cmd === 'profile') {
router.push('/reader/profile')
} else if (cmd === 'logout') {
ElMessageBox.confirm('确定安全退出吗?', '提示', { type: 'warning' }).then(() => {
userStore.logout()
router.push('/login')
}).catch(() => {})
}
}
</script>
<style scoped>
.layout-container { height: 100vh; }
.layout-aside { transition: width 0.3s; overflow: hidden; }
.reader-aside { background: #2c3e50; }
.logo-area {
height: 60px; display: flex; align-items: center; justify-content: center;
gap: 10px; color: #fff; font-size: 18px; font-weight: 700;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.logo-text { white-space: nowrap; }
.side-menu { border-right: none; }
.layout-header {
background: #fff; display: flex; align-items: center; justify-content: space-between;
box-shadow: 0 1px 4px rgba(0,0,0,0.08); padding: 0 20px; height: 60px;
}
.header-left { display: flex; align-items: center; gap: 16px; }
.collapse-btn { font-size: 20px; cursor: pointer; color: #606266; }
.user-info { display: flex; align-items: center; gap: 8px; cursor: pointer; color: #303133; }
.user-name { font-size: 14px; }
.layout-main { background: #f0f2f5; padding: 0; overflow-y: auto; }
</style>






