1. 项目概述与核心技术栈选型
这套图书进销存管理系统采用了当前企业级开发中最主流的"前后端分离"架构模式,前端基于Vue3组合式API开发,后端采用SpringBoot+MyBatis技术栈,数据库选用MySQL 8.0。这种技术组合在2023年的电商、ERP类系统中已经成为事实上的标准方案,我们团队在实际落地过程中验证了其稳定性和开发效率。
技术选型背后的思考:Vue3的Composition API相比Options API更适合复杂业务逻辑组织,SpringBoot的自动配置特性大幅减少了XML配置,而MyBatis在复杂SQL场景下比JPA更灵活可控。
系统主要包含以下核心模块:
- 图书基础信息管理(CRUD+批量导入)
- 供应商管理与采购订单流程
- 库存预警与盘点功能
- 销售统计与财务报表
- 多角色权限控制系统
2. 开发环境搭建与工程结构
2.1 后端工程初始化
使用Spring Initializr创建项目时,关键依赖选择:
xml复制<dependencies>
<!-- Web核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 开发常用工具包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
工程目录结构采用DDD领域驱动设计思想组织:
code复制src/main/java
├── com.book.management
│ ├── config # 配置类
│ ├── controller # 控制层
│ ├── service # 业务层
│ │ ├── impl # 实现类
│ ├── repository # 持久层
│ ├── model # 实体类
│ ├── dto # 数据传输对象
│ ├── vo # 视图对象
│ └── exception # 异常处理
2.2 前端工程配置
Vue3项目创建时推荐使用Vite作为构建工具:
bash复制npm create vite@latest book-management-ui --template vue-ts
关键依赖配置:
json复制{
"dependencies": {
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"pinia": "^2.0.34",
"axios": "^1.3.4",
"element-plus": "^2.3.3",
"echarts": "^5.4.2"
}
}
3. 数据库设计与MyBatis优化
3.1 核心表结构设计
图书主表设计示例(MySQL 8.0):
sql复制CREATE TABLE `t_book` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`isbn` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '国际标准书号',
`name` varchar(100) COLLATE utf8mb4_bin NOT NULL COMMENT '图书名称',
`author` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '作者',
`publisher` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '出版社',
`publish_date` date NOT NULL COMMENT '出版日期',
`price` decimal(10,2) NOT NULL COMMENT '定价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存数量',
`category_id` int NOT NULL COMMENT '分类ID',
`cover_url` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '封面URL',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(1:在售 0:下架)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_isbn` (`isbn`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='图书信息表';
3.2 MyBatis高级特性应用
- 动态SQL构建:
xml复制<select id="selectBooksByCondition" resultType="BookVO">
SELECT * FROM t_book
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="author != null and author != ''">
AND author = #{author}
</if>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
- 批量插入优化:
java复制@Insert("<script>" +
"INSERT INTO t_book_stock(book_id, warehouse_id, quantity) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.bookId}, #{item.warehouseId}, #{item.quantity})" +
"</foreach>" +
"</script>")
int batchInsertStock(@Param("list") List<BookStock> stockList);
4. 前后端交互关键实现
4.1 RESTful API设计规范
图书相关接口示例:
code复制GET /api/books - 分页查询图书列表
POST /api/books - 新增图书
GET /api/books/{id} - 获取图书详情
PUT /api/books/{id} - 修改图书信息
DELETE /api/books/{id} - 删除图书
POST /api/books/import - 批量导入图书
统一响应体结构:
java复制public class Result<T> {
private Integer code; // 状态码
private String message; // 提示信息
private T data; // 业务数据
private Long timestamp; // 时间戳
// 成功静态方法
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
result.setTimestamp(System.currentTimeMillis());
return result;
}
}
4.2 前端请求封装
基于axios的请求拦截器配置:
typescript复制// src/utils/request.ts
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
})
// 请求拦截
service.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截
service.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
(error) => {
ElMessage.error(error.message || 'Request Error')
return Promise.reject(error)
}
)
export default service
5. 典型业务场景实现
5.1 图书采购入库流程
mermaid复制sequenceDiagram
participant F as 前端
participant B as 后端
participant D as 数据库
F->>B: 提交采购单(POST /api/purchases)
B->>D: 开启事务
B->>D: 插入采购主表记录
B->>D: 批量插入采购明细
B->>D: 更新图书库存(库存+=采购量)
alt 所有操作成功
B->>D: 提交事务
B->>F: 返回成功
else 任何失败
B->>D: 回滚事务
B->>F: 返回失败原因
end
5.2 库存预警实现方案
- 定时任务检查:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
public void checkStockWarning() {
List<Book> lowStockBooks = bookMapper.selectLowStockBooks(10); // 阈值10本
if (!lowStockBooks.isEmpty()) {
String message = "以下图书库存不足:\n" +
lowStockBooks.stream()
.map(b -> b.getName() + "(" + b.getIsbn() + "): 剩余" + b.getStock() + "本")
.collect(Collectors.joining("\n"));
emailService.sendWarningEmail("admin@example.com", "图书库存预警", message);
}
}
- 数据库层面触发器(备选方案):
sql复制DELIMITER //
CREATE TRIGGER after_stock_update
AFTER UPDATE ON t_book
FOR EACH ROW
BEGIN
IF NEW.stock < 10 AND NEW.stock != OLD.stock THEN
INSERT INTO t_stock_warning(book_id, current_stock, create_time)
VALUES(NEW.id, NEW.stock, NOW());
END IF;
END//
DELIMITER ;
6. 权限控制方案
6.1 基于RBAC的权限模型
数据库关系设计:
code复制t_user t_role t_permission
------- ------ ------------
id id id
username name name
password description url
... ... method
|
t_user_role t_role_permission
----------- ----------------
user_id role_id
role_id permission_id
6.2 Spring Security配置
核心安全配置类:
java复制@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/books").permitAll()
.requestMatchers("/api/books/**").hasAnyRole("ADMIN", "OPERATOR")
.requestMatchers("/api/purchases/**").hasRole("PURCHASER")
.anyRequest().authenticated()
)
.sessionManagement(sess ->
sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
7. 项目部署与运维
7.1 生产环境部署方案
推荐使用Docker Compose编排:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: book-mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: book_management
MYSQL_USER: book_user
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- book-net
backend:
build: ./backend
container_name: book-backend
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/book_management
SPRING_DATASOURCE_USERNAME: book_user
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
ports:
- "8080:8080"
networks:
- book-net
frontend:
build: ./frontend
container_name: book-frontend
ports:
- "80:80"
networks:
- book-net
volumes:
mysql_data:
networks:
book-net:
driver: bridge
7.2 常见问题排查
- MyBatis映射问题:
当遇到"Invalid bound statement (not found)"错误时,检查:
- mapper.xml文件是否在resources目录正确路径下
- mybatis.mapper-locations配置是否正确
- 方法名是否与xml中的id一致
- 是否添加了@Mapper注解或配置了@MapperScan
- Vue3响应式丢失:
typescript复制// 错误做法
const form = reactive({ ... })
form = newFormData // 这将破坏响应式
// 正确做法
Object.assign(form, newFormData)
// 或使用解构
const form = reactive({ ... })
const newForm = reactive({ ...newFormData })
