1. 项目背景与技术选型
阿博图书馆管理系统是一个典型的现代化Web应用,采用前后端分离架构实现。这种架构模式已经成为当前企业级应用开发的主流选择,它将用户界面与业务逻辑彻底解耦,让前后端开发团队能够并行工作,提高整体开发效率。
1.1 为什么选择前后端分离架构
在传统单体应用中,前端页面通常由后端模板引擎(如JSP、Thymeleaf)渲染,这种模式存在几个明显缺陷:
- 前后端开发耦合度高,任何界面调整都需要重新部署整个应用
- 难以支持多终端适配(Web、移动端、小程序等)
- 前端技术选型受限,无法充分利用现代前端框架的优势
前后端分离架构通过清晰的API契约将前后端解耦:
- 前端:专注于UI呈现和用户体验,使用Vue.js等现代框架
- 后端:专注业务逻辑和数据处理,通过RESTful API提供服务
- 两者通过HTTP接口通信,可以独立开发、测试和部署
1.2 技术栈深度解析
后端技术栈
Spring Boot 2.7.x:
- 自动配置:简化了Spring应用的初始搭建和开发过程
- 内嵌服务器:默认集成Tomcat,无需额外部署
- 生产就绪:提供健康检查、指标监控等生产级特性
- 起步依赖:通过spring-boot-starter-*简化依赖管理
MyBatis 3.5.x:
- SQL与代码分离:XML配置方式保持SQL可维护性
- 动态SQL:支持条件查询、批量操作等复杂场景
- 与Spring无缝集成:通过mybatis-spring-boot-starter简化配置
- 拦截器机制:支持SQL执行监控、分页等插件开发
MySQL 8.0:
- 事务支持:ACID特性保证数据一致性
- 索引优化:B+树索引结构提高查询效率
- JSON支持:原生JSON数据类型和函数
- 窗口函数:支持复杂分析查询
前端技术栈
Vue 3.x:
- 组合式API:更好的逻辑复用和代码组织
- 响应式系统:基于Proxy实现的高效数据绑定
- 单文件组件:将模板、脚本和样式封装在.vue文件中
- Vue Router:实现前端路由和导航守卫
- Vuex/Pinia:状态管理方案
1.3 系统核心模块设计
code复制图书馆管理系统
├── 后端模块 (SpringBoot)
│ ├── 权限管理 (JWT认证)
│ ├── 图书管理 (CRUD+搜索)
│ ├── 借阅管理 (事务控制)
│ ├── 用户管理 (角色权限)
│ └── 系统监控 (Actuator)
└── 前端模块 (Vue3)
├── 登录注册 (JWT处理)
├── 图书展示 (分页+筛选)
├── 借阅操作 (表单验证)
├── 个人中心 (数据统计)
└── 管理员面板 (数据可视化)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建与项目初始化
2.1 后端工程创建
使用Spring Initializr创建项目基础结构:
bash复制# 通过curl快速生成项目骨架
curl https://start.spring.io/starter.zip \
-d type=gradle-project \
-d language=java \
-d bootVersion=2.7.3 \
-d groupId=com.abo \
-d artifactId=library \
-d name=library-backend \
-d dependencies=web,mybatis,mysql,lombok \
-o library-backend.zip
关键依赖说明:
- spring-boot-starter-web:嵌入式Tomcat和Spring MVC
- mybatis-spring-boot-starter:MyBatis集成
- mysql-connector-java:MySQL驱动
- lombok:简化POJO开发
2.2 前端工程搭建
使用Vite创建Vue3项目:
bash复制npm init vite@latest library-frontend --template vue
cd library-frontend
npm install
推荐安装的核心依赖:
- axios:HTTP客户端
- vue-router@4:路由管理
- pinia:状态管理
- element-plus:UI组件库
- sass:CSS预处理器
2.3 数据库设计与初始化
图书管理系统核心表结构:
sql复制CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`salt` varchar(20) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`mobile` varchar(100) DEFAULT NULL,
`status` tinyint DEFAULT '1',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `book_info` (
`book_id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`author` varchar(100) NOT NULL,
`publish` varchar(100) NOT NULL,
`ISBN` varchar(100) NOT NULL,
`introduction` text,
`language` varchar(100) NOT NULL,
`price` decimal(10,2) NOT NULL,
`pub_date` date NOT NULL,
`class_id` int DEFAULT NULL,
`number` int DEFAULT NULL,
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `lend_list` (
`ser_num` bigint NOT NULL AUTO_INCREMENT,
`book_id` bigint NOT NULL,
`reader_id` bigint NOT NULL,
`lend_date` datetime DEFAULT NULL,
`back_date` datetime DEFAULT NULL,
`fine` decimal(10,2) DEFAULT NULL,
`state` tinyint DEFAULT NULL,
PRIMARY KEY (`ser_num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现细节
3.1 后端API开发
JWT认证实现
- 添加JWT依赖:
gradle复制implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
- 创建JWT工具类:
java复制public class JwtUtil {
private static final String SECRET_KEY = "your-256-bit-secret";
private static final long EXPIRATION_TIME = 864_000_000; // 10天
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
- 实现Spring Security配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
MyBatis动态SQL应用
图书多条件查询示例:
xml复制<select id="queryBooks" resultType="Book">
SELECT * FROM book_info
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="author != null and author != ''">
AND author LIKE CONCAT('%', #{author}, '%')
</if>
<if test="publish != null and publish != ''">
AND publish LIKE CONCAT('%', #{publish}, '%')
</if>
<choose>
<when test="minPrice != null and maxPrice != null">
AND price BETWEEN #{minPrice} AND #{maxPrice}
</when>
<when test="minPrice != null">
AND price >= #{minPrice}
</when>
<when test="maxPrice != null">
AND price <= #{maxPrice}
</when>
</choose>
</where>
ORDER BY book_id DESC
</select>
3.2 前端页面开发
图书列表页实现
使用Vue3组合式API:
vue复制<script setup>
import { ref, onMounted } from 'vue'
import { getBooks } from '@/api/book'
const books = ref([])
const loading = ref(false)
const pagination = ref({
current: 1,
pageSize: 10,
total: 0
})
const fetchBooks = async () => {
loading.value = true
try {
const res = await getBooks({
page: pagination.value.current,
size: pagination.value.pageSize
})
books.value = res.data.list
pagination.value.total = res.data.total
} finally {
loading.value = false
}
}
const handlePageChange = (page) => {
pagination.value.current = page
fetchBooks()
}
onMounted(() => {
fetchBooks()
})
</script>
<template>
<el-table :data="books" v-loading="loading">
<el-table-column prop="name" label="书名" />
<el-table-column prop="author" label="作者" />
<el-table-column prop="publish" label="出版社" />
<el-table-column prop="price" label="价格" />
</el-table>
<el-pagination
v-model:currentPage="pagination.current"
:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handlePageChange"
/>
</template>
全局状态管理
使用Pinia管理用户状态:
javascript复制// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || '',
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null
}),
actions: {
setToken(token) {
this.token = token
localStorage.setItem('token', token)
},
setUserInfo(userInfo) {
this.userInfo = userInfo
localStorage.setItem('userInfo', JSON.stringify(userInfo))
},
logout() {
this.token = ''
this.userInfo = null
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
}
})
4. 系统部署方案
4.1 后端部署
打包Spring Boot应用
bash复制./gradlew bootJar
生成的jar包位于:build/libs/library-backend-0.0.1-SNAPSHOT.jar
生产环境启动
使用systemd管理服务:
ini复制# /etc/systemd/system/library.service
[Unit]
Description=Library Backend Service
After=syslog.target
[Service]
User=library
ExecStart=/usr/bin/java -jar /opt/library/library-backend.jar
SuccessExitStatus=143
Restart=always
[Install]
WantedBy=multi-user.target
启动命令:
bash复制sudo systemctl daemon-reload
sudo systemctl enable library
sudo systemctl start library
4.2 前端部署
生产环境构建
bash复制npm run build
生成的静态文件位于dist目录
Nginx配置示例
nginx复制server {
listen 80;
server_name library.example.com;
location / {
root /var/www/library/dist;
try_files $uri $uri/ /index.html;
}
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;
}
}
4.3 数据库部署优化
MySQL配置调优
ini复制[mysqld]
innodb_buffer_pool_size = 1G # 缓冲池大小,建议为物理内存的50-70%
innodb_log_file_size = 256M # 日志文件大小
innodb_flush_log_at_trx_commit = 2 # 平衡性能与安全性
max_connections = 200 # 最大连接数
query_cache_size = 0 # 禁用查询缓存(MySQL 8.0已移除)
定期备份策略
bash复制# 每日全量备份
mysqldump -u root -p library > /backup/library_$(date +%F).sql
5. 项目优化与扩展
5.1 性能优化实践
后端缓存策略
- 集成Redis作为二级缓存:
gradle复制implementation 'org.springframework.boot:spring-boot-starter-data-redis'
- 配置MyBatis二级缓存:
yaml复制mybatis:
configuration:
cache-enabled: true
- 自定义Redis缓存实现:
java复制@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues())
.build();
}
}
前端性能优化
- 路由懒加载:
javascript复制const routes = [
{
path: '/books',
component: () => import('@/views/BookList.vue')
}
]
- 代码分割配置(vite.config.js):
javascript复制export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0]
}
}
}
}
}
})
5.2 安全加固措施
后端安全
- 防止SQL注入:
java复制@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public List<Book> queryBooks(
@RequestParam String name,
@RequestParam String author) {
// 使用MyBatis参数化查询,不要拼接SQL字符串
return bookService.queryBooks(name, author);
}
}
- XSS防护:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new XssInterceptor());
}
}
public class XssInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String param = request.getParameter("param");
if (param != null && containsXss(param)) {
throw new IllegalArgumentException("XSS攻击检测");
}
return true;
}
private boolean containsXss(String value) {
// 实现XSS检测逻辑
}
}
前端安全
- 输入验证:
vue复制<script setup>
const form = ref({
username: '',
password: ''
})
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 4, max: 20, message: '长度在4到20个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/,
message: '至少8个字符,包含字母和数字' }
]
}
</script>
- CSP策略配置(nginx):
nginx复制add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:;";
5.3 监控与运维
Spring Boot Actuator
- 添加依赖:
gradle复制implementation 'org.springframework.boot:spring-boot-starter-actuator'
- 配置端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
- 自定义健康检查:
java复制@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
return Health.up().withDetail("database", "available").build();
} catch (Exception e) {
return Health.down().withDetail("error", e.getMessage()).build();
}
}
}
前端错误监控
- 集成Sentry:
javascript复制import * as Sentry from "@sentry/vue"
app.use(Sentry, {
dsn: "your-dsn",
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router)
})
],
tracesSampleRate: 0.2
})
- 全局错误处理:
javascript复制app.config.errorHandler = (err, vm, info) => {
console.error(err)
Sentry.captureException(err, { extra: { component: info } })
}
6. 常见问题与解决方案
6.1 跨域问题处理
后端解决方案
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "https://your-domain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
前端代理配置(vite.config.js)
javascript复制export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
})
6.2 MyBatis常见问题
一级缓存导致数据不一致
java复制@Transactional
public void updateAndQuery() {
// 更新操作
bookMapper.updateById(book);
// 清空一级缓存
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession();
session.clearCache();
// 查询最新数据
Book freshBook = bookMapper.selectById(book.getId());
}
动态表名处理
xml复制<select id="selectByTable" resultType="map">
SELECT * FROM ${tableName}
WHERE id = #{id}
</select>
注意:使用${}存在SQL注入风险,应确保tableName参数经过严格校验
6.3 Vue3常见问题
组件刷新问题
使用key强制刷新:
vue复制<template>
<BookList :key="componentKey" />
<button @click="refresh">刷新</button>
</template>
<script setup>
const componentKey = ref(0)
const refresh = () => componentKey.value++
</script>
路由参数变化监听
javascript复制import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
(newId) => {
fetchBook(newId)
}
)
7. 项目扩展方向
7.1 微服务化改造
模块拆分建议
- 用户服务:处理认证、授权、用户信息
- 图书服务:管理图书目录、库存
- 借阅服务:处理借阅、归还、罚款
- 通知服务:发送邮件、短信通知
Spring Cloud集成
关键组件:
- Eureka/Nacos:服务注册与发现
- OpenFeign:服务间HTTP调用
- Spring Cloud Gateway:API网关
- Sleuth/Zipkin:分布式追踪
7.2 多租户支持
数据库层面实现
- 独立数据库:每个租户使用单独的数据库实例
- 共享数据库,独立Schema:同一实例不同Schema
- 共享Schema:通过tenant_id字段区分
MyBatis多租户拦截器
java复制@Intercepts({
@Signature(type= Executor.class, method="update", args={MappedStatement.class,Object.class}),
@Signature(type= Executor.class, method="query", args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前租户ID
String tenantId = TenantContext.getCurrentTenant();
// 修改SQL添加租户条件
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
SqlCommandType sqlCommandType = ms.getSqlCommandType();
if (sqlCommandType == SqlCommandType.INSERT) {
// 处理INSERT语句
} else if (sqlCommandType == SqlCommandType.SELECT) {
// 处理SELECT语句
}
return invocation.proceed();
}
}
7.3 移动端适配
响应式设计
使用CSS媒体查询:
css复制@media (max-width: 768px) {
.book-card {
width: 100%;
margin-bottom: 1rem;
}
.search-form {
flex-direction: column;
}
}
PWA支持
- 添加manifest.json:
json复制{
"name": "图书馆管理系统",
"short_name": "图书馆",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1890ff",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
- 注册Service Worker:
javascript复制if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
})
}
8. 开发经验与最佳实践
8.1 前后端协作规范
API设计原则
-
使用RESTful风格:
- GET /books - 获取图书列表
- POST /books - 创建新图书
- GET /books/{id} - 获取特定图书
- PUT /books/{id} - 更新图书
- DELETE /books/{id} - 删除图书
-
统一响应格式:
json复制{
"code": 200,
"message": "success",
"data": {
"books": [...]
},
"timestamp": 1630000000000
}
- 错误码规范:
- 200:成功
- 400:客户端错误
- 401:未授权
- 403:禁止访问
- 404:资源不存在
- 500:服务器错误
接口文档管理
使用Swagger UI:
- 添加依赖:
gradle复制implementation 'io.springfox:springfox-boot-starter:3.0.0'
- 配置类:
java复制@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.abo.library.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("图书馆管理系统API文档")
.description("前后端分离架构")
.version("1.0")
.build();
}
}
8.2 代码质量保障
单元测试实践
- 后端测试(JUnit5 + Mockito):
java复制@ExtendWith(MockitoExtension.class)
class BookServiceTest {
@Mock
private BookMapper bookMapper;
@InjectMocks
private BookService bookService;
@Test
void shouldReturnBookWhenExists() {
Book mockBook = new Book();
mockBook.setId(1L);
when(bookMapper.selectById(1L)).thenReturn(mockBook);
Book result = bookService.getBookById(1L);
assertNotNull(result);
assertEquals(1L, result.getId());
}
}
- 前端测试(Vitest):
javascript复制import { describe, it, expect } from 'vitest'
import { formatDate } from '@/utils/date'
describe('date utils', () => {
it('formats date correctly', () => {
const date = new Date('2023-01-01')
expect(formatDate(date)).toBe('2023-01-01')
})
})
代码规范检查
- 后端Checkstyle配置:
xml复制<module name="Checker">
<module name="TreeWalker">
<module name="JavadocMethod"/>
<module name="MethodLength">
<property name="max" value="50"/>
</module>
</module>
</module>
- 前端ESLint配置:
javascript复制module.exports = {
rules: {
'vue/multi-word-component-names': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
8.3 持续集成与交付
GitHub Actions配置
后端CI流程:
yaml复制name: Java CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Run tests
run: ./gradlew test
前端CI流程:
yaml复制name: Vue CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
- run: npm run build
- run: npm run test:unit
Docker化部署
后端Dockerfile:
dockerfile复制FROM eclipse-temurin:11-jre
WORKDIR /app
COPY build/libs/library-backend-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
前端Dockerfile:
dockerfile复制FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
9. 项目源码解析
9.1 核心类设计
后端关键类
- 实体类(Book.java):
java复制@Data
@TableName("book_info")
public class Book {
@TableId(type = IdType.AUTO)
private Long bookId;
private String name;
private String author;
private String publish;
private String isbn;
private String introduction;
private String language;
private BigDecimal price;
private Date pubDate;
private Integer classId;
private Integer number;
}
- 服务层(BookService.java):
java复制@Service
@RequiredArgsConstructor
public class BookService {
private final BookMapper bookMapper;
@Transactional(readOnly = true)
public Page<Book> queryBooks(BookQuery query, Pageable pageable) {
return bookMapper.queryBooks(query, pageable);
}
@Transactional
public void addBook(Book book) {
if (bookMapper.existsByIsbn(book.getIsbn())) {
throw new BusinessException("ISBN已存在");
}
bookMapper.insert(book);
}
}
- 控制器(BookController.java):
java复制@RestController
@RequestMapping("/api/books")
@RequiredArgsConstructor
public class BookController {
private final BookService bookService;
@GetMapping
public ResponseEntity<Page<Book>> listBooks(
@ModelAttribute BookQuery query,
@PageableDefault Pageable pageable) {
return ResponseEntity.ok(bookService.queryBooks(query, pageable));
}
}
前端关键组件
- 图书列表(BookList.vue):
vue复制<script setup>
const columns = [
{
title: '书名',
dataIndex: 'name',
key: 'name'
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' }
}
]
const { data, loading, run } = useRequest(getBooks)
</script>
<template>
<a-table
:columns="columns"
:dataSource="data"
:loading="loading"
>
<template #action="{ record }">
<a-button @click="showDetail(record)">详情</a-button>
</template>
</a-table>
</template>
- API封装(book.js):
javascript复制import request from '@/utils/request'
export function getBooks(params) {
return request({
url: '/api/books',
method: 'get',
params
})
}
9.2 设计模式应用
策略模式在借阅规则中的应用
java复制public interface LendRule {
boolean canLend(User user, Book book);
int getMaxLendDays(User user);
}
@Service
public class StudentLendRule implements LendRule {
@Override
public boolean canLend(User user, Book book) {
return user.getBorrowedCount() < 5;
}
@Override
public int getMaxLendDays(User user) {
return 30;
}
}
@Service
public class TeacherLendRule implements LendRule {
@Override
public boolean canLend(User user, Book book) {
return true;
}
@Override
public int getMaxLendDays(User user) {
return 60;
}
}
@Service
public class LendService {
private final Map<UserType, LendRule> ruleMap;
public LendService(List<LendRule> rules) {
this.ruleMap = rules.stream()
.collect(Collectors.toMap(
rule -> rule.getClass().getAnnotation(RuleFor.class).value(),
Function.identity()
));
}
public void lendBook(User user, Book book) {
LendRule rule = ruleMap.get(user.getType());
if (!rule.canLend(user, book)) {
throw new BusinessException("借阅限制");
}
// 处理借阅逻辑
}
}
观察者模式在借阅通知中的应用
java复制public interface LendObserver {
void onLend(LendEvent event);
}
@Service
public class EmailNotificationObserver implements LendObserver {
@Override
public void onLend(LendEvent event) {
// 发送邮件通知
}
}
@Service
public class LendEventPublisher {
private final List<LendObserver> observers;
public void publish(LendEvent event) {
observers.forEach(observer -> observer.onLend(event));
}
}
@Service
public class LendService {
private final LendEventPublisher publisher;
@Transactional
public void lendBook(User user, Book book) {
// 借阅逻辑...
publisher.publish(new LendEvent(user, book));
}
}
10. 项目总结与展望
在开发阿博图书馆管理系统的过程中,前后端分离架构展现了其显著优势。通过清晰的API契约,前后端团队能够并行开发,大大提高了整体效率。Spring Boot的约定优于配置理念简化了后端开发,而Vue3的组合式API则让前端代码更加模块化和可维护。
10.1 技术选型反思
-
MyBatis vs JPA:
- 对于复杂查询场景,MyBatis的动态SQL提供了更大灵活性
- 但简单的CRUD操作,JPA的Repository接口可能更简洁
- 未来可以考虑混合使用,简单操作用JPA,复杂查询用MyBatis
-
状态管理选择:
- Pinia相比Vuex更加轻量和直观
- 但对于大型应用,可能需要更严格的状态变更控制
- 可以考虑引入Redux模式的状态管理方案
10.2 性能优化收获
-
N+1查询问题:
- 通过MyBatis的关联查询或批量查询优化
- 使用二级缓存减少数据库访问
-
前端性能:
- 路由懒加载显著减少首屏加载时间
- 虚拟滚动优化长列表渲染性能
10.3 安全实践心得
-
输入验证:
- 后端绝不能信任前端传入的数据
- 所有参数必须进行有效性校验
-
JWT安全:
- 使用合理的过期时间
- 敏感操作应要求重新认证
- 考虑加入token刷新机制
10.4 项目扩展思考
-
国际化支持:
- 后端返回错误消息的多语言处理
- 前端界面语言切换功能
-
数据分析:
- 集成ELK栈实现借阅日志分析
- 使用ECharts展示图书借阅趋势
-
无服务器架构:
- 将部分功能转为Serverless实现
- 使用云函数处理定时任务
这个项目从技术选型到最终部署,涵盖了现代Web开发的完整流程。随着业务发展,系统还可以在微服务化、智能化推荐等方面继续演进。最重要的是保持架构的灵活性,为未来的扩展预留空间。
