Files
MedicineManagerSystem/readme.md
KiriAky 107 3f8af96578 更新后端环境配置说明
- 在启动命令前添加 cd backend 目录切换步骤
- 为环境要求部分增加空行以改善格式
- 修正后端环境变量配置命令,先切换到 backend 目录再执行 cp 命令
2026-07-07 10:11:51 +08:00

425 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 药品管理系统 (MedicineManagerSystem)
基于 **Vue 3 + Spring Boot** 的全栈药品进销存管理系统,支持采购入库、库存管理、销售/领用出库、效期预警、供应商管理及 RBAC 权限控制。
---
## 技术栈
| 类别 | 技术 | 版本 |
|------|------|------|
| 前端框架 | Vue 3 (Composition API) | 3.5+ |
| 构建工具 | Vite | 8.x |
| UI 组件库 | Element Plus | 2.x |
| 路由 / 状态 | Vue Router 4 / Pinia 2 | 4.x / 2.x |
| HTTP / 样式 | Axios 1.x / Sass | 1.x |
| 后端框架 | Spring Boot (Web + Security + Validation) | 4.1.0 |
| JDK | Java 21 | LTS |
| ORM | MyBatis-Plus | 3.5.11 |
| 安全认证 | Spring Security + JWT (jjwt 0.13.0) | — |
| 数据库 | MySQL 8.0 (JDBC + HikariCP) | — |
| 缓存 | Redis 7 + Jedis可选无连接自动跳过 | — |
| 工具库 | Hutool 5.8 / Lombok | — |
| 构建 / 部署 | Maven 3.9+ / Docker + Docker Compose | — |
---
## 系统架构
```mermaid
graph TB
Client["🖥️ 浏览器<br/>Vue 3 + Element Plus SPA"]
subgraph Backend["☕ Spring Boot 4.1.0"]
Security["🔐 Spring Security + JWT Filter<br/>无状态认证"]
Controller["📡 Controller Layer<br/>REST API · 参数校验 · 统一响应格式 Result-T"]
Service["⚙️ Service Layer<br/>业务逻辑 · 事务管理"]
Mapper["🗄️ MyBatis-Plus Mapper<br/>分页 · 逻辑删除 · 自动填充"]
end
DB["🐬 MySQL 8.0<br/>InnoDB · utf8mb4"]
Cache["⚡ Redis 7<br/>可选 · 自动降级"]
Client -->|"HTTPS + JWT Token"| Security
Service --> Cache
Security --> Controller
Controller --> Service
Service --> Mapper
Mapper -->|"JDBC"| DB
```
### 请求响应流程
```mermaid
sequenceDiagram
participant F as 🖥️ Vue 前端
participant A as Axios 拦截器
participant SF as JWT Filter
participant C as Controller
participant SV as Service
participant M as Mapper
participant DB as MySQL
F->>A: 发起 API 请求
A->>A: 自动注入 JWT Token<br/>到 Authorization Header
A->>SF: HTTP Request (Bearer Token)
alt Token 有效
SF->>C: 放行请求
C->>C: @Valid 参数校验
C->>SV: 调用业务方法
SV->>M: 调用数据访问
M->>DB: 执行 SQL
DB-->>M: 返回结果集
M-->>SV: 返回实体对象
SV-->>C: 返回处理结果
C-->>A: "Result<T> JSON"
A->>A: 响应拦截器处理<br/>(统一错误提示 / Token 刷新)
A-->>F: 展示数据
else Token 过期或无效
SF-->>A: 401 Unauthorized
A->>A: 尝试 RefreshToken
alt 刷新失败
A->>F: 跳转登录页
end
else 权限不足
SF-->>A: 403 Forbidden
A-->>F: 提示"权限不足"
end
```
---
### RBAC 权限模型
```mermaid
graph LR
U["👤 User"] -->|N:M| UR["user_role"]
UR -->|N:M| R["🔑 Role"]
R -->|N:M| RM["role_menu"]
RM -->|N:M| M["📋 Menu"]
M --> D["📁 目录 type=1<br/>侧边栏分组"]
M --> P["📄 菜单 type=2<br/>页面路由"]
M --> B["🔘 按钮 type=3<br/>permission_code"]
subgraph 三层权限
L1["路由守卫"] -.-> P
L2["v-permission 指令"] -.-> B
L3["@PreAuthorize"] -.-> B
end
```
---
### 认证流程
```mermaid
sequenceDiagram
participant U as 👤 用户
participant F as 🖥️ Vue 前端
participant B as ☕ Spring Boot
participant DB as 🐬 MySQL
rect rgb(240, 248, 255)
Note over U,DB: === 登录阶段 ===
U->>F: 输入用户名 + 密码
F->>B: POST /api/auth/login<br/>{username, password}
B->>DB: 查询用户信息
DB-->>B: 返回用户实体
B->>B: BCrypt.matches(password, hash)
alt 验证成功
B->>B: 生成 AccessToken (有效期 24h)
B->>B: 生成 RefreshToken (有效期 7天)
B-->>F: { token, refreshToken, userInfo, menus }
F->>F: localStorage.setItem('token', ...)
F-->>U: 跳转首页,渲染菜单
else 验证失败
B-->>F: 401 "用户名或密码错误"
F-->>U: ElMessage.error 提示
end
end
rect rgb(255, 248, 240)
Note over U,DB: === 正常请求阶段 ===
F->>B: GET /api/medicines<br/>Authorization: Bearer <AccessToken>
B->>B: JwtFilter 解析 Token<br/>提取 userId + permissions
B->>B: SecurityContextHolder 设置认证信息
B-->>F: Result<Page<Medicine> >
end
rect rgb(255, 240, 240)
Note over U,DB: === Token 过期刷新 ===
F->>B: POST /api/auth/refresh<br/>{ refreshToken }
B->>B: 校验 RefreshToken 有效性
alt RefreshToken 有效
B->>B: 签发新 AccessToken
B-->>F: { token: "newToken" }
F->>F: 更新 localStorage
else RefreshToken 过期
B-->>F: 401
F->>F: 清空 Token跳转登录页
end
end
```
---
## 数据库设计
```mermaid
erDiagram
medicine_category ||--o{ medicine : "分类"
medicine ||--o{ stock : "库存批次"
medicine ||--o{ stock_in_detail : ""
medicine ||--o{ stock_out_detail : ""
warehouse ||--o{ stock : ""
warehouse ||--o{ stock_in_record : ""
warehouse ||--o{ stock_out_record : ""
supplier ||--o{ stock_in_record : ""
customer ||--o{ stock_out_record : ""
stock_in_record ||--o{ stock_in_detail : ""
stock_out_record ||--o{ stock_out_detail : ""
user ||--o{ stock_in_record : "操作人"
user ||--o{ stock_out_record : "操作人"
user ||--o{ user_role : ""
role ||--o{ user_role : ""
role ||--o{ role_menu : ""
menu ||--o{ role_menu : ""
menu ||--o{ menu : "父子菜单"
medicine_category { bigint id PK "分类ID" varchar name "名称" varchar code UK "编码" }
medicine { bigint id PK "药品ID" varchar name "通用名称" varchar specification "规格" varchar manufacturer "厂家" varchar approval_number UK "批准文号" decimal retail_price "零售价" decimal wholesale_price "批发价" int shelf_life "有效期(月)" int warning_stock "预警阈值" }
supplier { bigint id PK "供应商ID" varchar name "名称" varchar contact_person "联系人" varchar phone "电话" tinyint status "状态" }
customer { bigint id PK "客户ID" varchar name "名称" varchar phone "电话" varchar type "类型" }
warehouse { bigint id PK "仓库ID" varchar name "名称" varchar code UK "编码" }
stock { bigint id PK "库存ID" bigint medicine_id FK "药品" bigint warehouse_id FK "仓库" varchar batch_no "批号" int quantity "数量" decimal cost_price "成本价" date expiry_date "有效期至" }
stock_in_record { bigint id PK "入库单ID" varchar record_no UK "单号" bigint supplier_id FK "供应商" decimal total_amount "总金额" date in_date "入库日期" tinyint status "0待审1已入库2已取消" }
stock_in_detail { bigint id PK bigint record_id FK bigint medicine_id FK varchar batch_no int quantity decimal cost_price "进价" decimal total_price "小计" date expiry_date }
stock_out_record { bigint id PK "出库单ID" varchar record_no UK "单号" bigint customer_id FK "客户" decimal total_amount "总金额" date out_date "出库日期" tinyint out_type "1销售2领用3报损4退货" tinyint status "0待审1已出库2已取消" }
stock_out_detail { bigint id PK bigint record_id FK bigint medicine_id FK varchar batch_no int quantity decimal unit_price "单价" decimal total_price "小计" }
user { bigint id PK "用户ID" varchar username UK "用户名" varchar password "BCrypt" varchar real_name "姓名" varchar phone "手机" tinyint status "状态" }
role { bigint id PK "角色ID" varchar name "名称" varchar code UK "编码" }
user_role { bigint id PK bigint user_id FK bigint role_id FK }
menu { bigint id PK "菜单ID" bigint parent_id "父ID" varchar name "名称" tinyint type "1目录2菜单3按钮" varchar path "路由" varchar icon "图标" varchar permission_code UK "权限编码" }
role_menu { bigint id PK bigint role_id FK bigint menu_id FK }
```
---
## 功能模块
| 模块 | 功能 |
|------|------|
| 🔐 认证 | 登录 / 注册 / JWT 刷新 / 退出 |
| 📊 仪表盘 | 4 项统计卡片 · **效期预警**<30天红 · 30-90天橙 · 最近出入库 |
| 💊 药品管理 | CRUD · 分类树管理 · 按名称/批准文号/分类搜索 · 分页 |
| 📦 库存管理 | 批次跟踪 · 效期颜色标记 · 库存盘点 |
| 📥 入库管理 | 新建入库单供应商+动态明细行 · **审核通过自动增加库存** · 驳回 |
| 📤 出库管理 | 新建出库单客户+库存批次选择 · **审核通过 FIFO 扣减库存** · 驳回 |
| 🏢 供应商 | CRUD · 启用/停用开关 |
| 👥 客户 | CRUD · 类型个人/医院/药店/诊所 |
| 👤 用户管理 | CRUD · 角色分配 |
| 🔑 角色管理 | CRUD · 权限树分配 |
| 📋 菜单管理 | 菜单树 CRUD · 三级目录/菜单/按钮 |
---
## 核心业务流程
### 入库审核
```mermaid
sequenceDiagram
participant F as 前端
participant B as 后端
participant DB as MySQL
Note over F,DB: 1. 创建入库单
F->>B: POST /api/stock-in<br/>{ supplierId, details: [...] }
B->>DB: INSERT stock_in_record (status=0 待审核)
B->>DB: INSERT stock_in_detail (明细)
B-->>F: 入库单已创建
Note over F,DB: 2. 审核通过
F->>B: PUT /api/stock-in/{id}/status<br/>{ status: 1 }
B->>DB: 查同药品+同批号库存
alt 库存已存在
B->>DB: UPDATE stock SET quantity += N
else 新批次
B->>DB: INSERT stock (新批次)
end
B->>DB: UPDATE status=1
B-->>F: 审核通过 · 库存已更新
```
### 出库审核
```mermaid
sequenceDiagram
participant F as 前端
participant B as 后端
participant DB as MySQL
Note over F,DB: 1. 创建出库单
F->>B: POST /api/stock-out<br/>{ customerId, details: [...] }
B->>DB: INSERT stock_out_record (status=0 待审核)
Note over F,DB: 2. 审核通过
F->>B: PUT /api/stock-out/{id}/status<br/>{ status: 1 }
B->>DB: 查可用库存 (FIFO 按效期升序)
alt 库存充足
B->>DB: UPDATE stock SET quantity -= N
B->>DB: UPDATE status=1
B-->>F: 审核通过 · 库存已扣减
else 库存不足
B-->>F: 错误: 库存不足,需要 X可用 Y
end
```
---
## 快速开始
### 方式一Docker Compose推荐
```bash
# 1. 配置环境变量
cd backend
cp .env.example .env
# 编辑 .env 设置 DB_PASSWORD 和 JWT_SECRET
# 2. 一键启动MySQL + Redis + 后端 + 前端)
docker-compose up -d
# 3. 访问
open http://localhost
```
> 首次启动 MySQL 会自动执行 `init.sql` 建表和数据。Redis 不可用时后端自动降级跳过缓存。
### 方式二:手动启动
**环境要求**
- JDK 21+ · Node.js 18+ · MySQL 8.0+ · Maven 3.9+
#### 1. 初始化数据库
```bash
mysql -u root -p < backend/src/main/resources/database/init.sql
```
### 2. 配置后端环境变量
```shell
cd backend
cp .env.example .env
```
`backend/.env` 中填写
```properties
DB_HOST=localhost
DB_PORT=3306
DB_NAME=medicine_manager
DB_USERNAME=root
DB_PASSWORD=你的数据库密码
JWT_SECRET=你的256位Base64密钥
```
### 3. 启动后端
```bash
cd backend
./mvnw spring-boot:run -DskipTests
```
> 后端运行在 `http://localhost:8080`,启动时会自动加载 `.env`
### 4. 启动前端
```bash
cd frontend
npm install
npm run dev
```
> 前端运行在 `http://localhost:5173`API 请求自动代理到后端
### 5. 登录
| 用户名 | 密码 | 角色 | 权限范围 |
|--------|------|------|----------|
| `admin` | `admin123` | 超级管理员 | 全部 |
| `warehouse01` | `admin123` | 仓库管理员 | 出入库 + 库存 + 供应商客户查看 |
| `staff01` | `admin123` | 普通员工 | 仪表盘 + 只读查看 |
---
## 项目结构
```
MedicineManagerSystem/
├── README.md
├── .env.example
├── docker-compose.yml # Docker 编排
├── projectDesigning.md # 详细架构设计文档
├── frontend/ # Vue 3 SPA
│ ├── Dockerfile
│ ├── nginx.conf
│ └── src/
│ ├── api/ (11) # API 模块
│ ├── directives/ (1) # v-permission
│ ├── layout/ (1) # MainLayout
│ ├── router/ (1) # 路由 + 动态菜单
│ ├── stores/ (2) # Pinia
│ ├── styles/ (1) # SCSS
│ ├── utils/ (2) # Axios · Redis 缓存
│ └── views/ (16) # 页面组件
└── backend/ # Spring Boot
├── Dockerfile
└── src/main/
├── java/com/kronecker/backend/
│ ├── common/ (4) # Result · 异常
│ ├── config/ (3) # Security · MP · CORS
│ ├── controller/ (12) # REST API
│ ├── dto/ (4) # 请求对象
│ ├── entity/ (15) # 数据库实体
│ ├── mapper/ (15) # MP Mapper
│ ├── security/ (3) # JWT
│ ├── service/ (23) # 业务逻辑
│ └── utils/ (1) # Redis 缓存
└── resources/
├── application.yml
└── database/init.sql # 建表 + 种子数据
```
---
## API 端点
| 模块 | 方法 | 路径 | 权限 |
|------|------|------|------|
| 认证 | `POST` | `/api/auth/login` | 公开 |
| 认证 | `POST` | `/api/auth/register` | 公开 |
| 认证 | `POST` | `/api/auth/refresh` | 公开 |
| 认证 | `GET` | `/api/auth/userinfo` | 登录 |
| 仪表盘 | `GET` | `/api/dashboard/statistics` | 登录 |
| 仪表盘 | `GET` | `/api/dashboard/expiry-warning` | 登录 |
| 药品 | `GET POST PUT DELETE` | `/api/medicines[/{id}]` | medicine:* |
| 分类 | `GET POST PUT DELETE` | `/api/categories[/{id}]` | category:* |
| 供应商 | `GET POST PUT DELETE` | `/api/suppliers[/{id}]` | supplier:* |
| 客户 | `GET POST PUT DELETE` | `/api/customers[/{id}]` | customer:* |
| 库存 | `GET` | `/api/stocks` | stock:list |
| 入库 | `GET POST` | `/api/stock-in` | stock-in:* |
| 入库 | `PUT` | `/api/stock-in/{id}/status` | stock-in:audit |
| 出库 | `GET POST` | `/api/stock-out` | stock-out:* |
| 出库 | `PUT` | `/api/stock-out/{id}/status` | stock-out:audit |
| 用户 | `GET POST PUT DELETE` | `/api/users[/{id}]` | user:* |
| 角色 | `GET POST PUT DELETE` | `/api/roles[/{id}]` | role:* |
| 菜单 | `GET POST PUT DELETE` | `/api/menus[/{id}]` | menu:* |
| 菜单 | `GET` | `/api/menus/tree` | 登录 |