|
|
@@ -0,0 +1,525 @@
|
|
|
+# 上货员(补货员)模块说明
|
|
|
+
|
|
|
+> 本文档描述系统上货员模块的完整架构,涵盖独立补货员体系、微信绑定流程、补货操作流程及相关实现。
|
|
|
+> 帮助开发人员理解"为什么这样设计"以及各环节的特殊处理方式。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、模块概述
|
|
|
+
|
|
|
+### 1.1 什么是上货员/补货员?
|
|
|
+
|
|
|
+上货员(系统内称"补货员",Replenisher)指负责为智能售卖柜补货的工作人员。他们通过手机小程序查看库存、执行补货操作。
|
|
|
+
|
|
|
+### 1.2 独立补货员体系(V2)
|
|
|
+
|
|
|
+系统使用独立补货员体系,核心表:
|
|
|
+
|
|
|
+| 体系 | 数据表 | 说明 |
|
|
|
+|------|--------|------|
|
|
|
+| **独立补货员** | `t_replenisher` + `t_replenisher_device` | 独立账号体系,设备级绑定粒度,微信扫码绑定 |
|
|
|
+
|
|
|
+> **历史背景**:早期存在基于 `t_shop_replenisher` + `t_admin` 的 V1 体系(补货员需是系统管理员),已于 2026年4月 完成迁移清理,V1 表 `t_shop_replenisher` 及所有相关代码已彻底移除。
|
|
|
+
|
|
|
+### 1.3 为什么使用独立补货员体系?
|
|
|
+
|
|
|
+独立补货员体系相比旧的 V1(基于 `t_admin` 管理员表)解决了以下问题:
|
|
|
+- 补货员必须是系统管理员,具备过多后台权限
|
|
|
+- 粒度仅到门店级别,无法精确控制设备权限
|
|
|
+- 全凭管理员账号密码登录,补货员使用体验差
|
|
|
+
|
|
|
+V2 独立补货员体系的核心优势:
|
|
|
+- **独立账号**:`t_replenisher` 表独立管理,不与管理员混合
|
|
|
+- **设备级绑定**:通过 `t_replenisher_device` 表实现设备级粒度绑定
|
|
|
+- **微信扫码绑定**:后台创建账号 → 生成绑定码 → 微信扫码绑定,纯手机端操作
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、数据库设计
|
|
|
+
|
|
|
+### 2.1 核心表
|
|
|
+
|
|
|
+#### `t_replenisher` — 补货员表
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键,雪花算法 |
|
|
|
+| `name` | VARCHAR(50) | 姓名 |
|
|
|
+| `phone` | VARCHAR(20) | 手机号(可选) |
|
|
|
+| `employee_id` | VARCHAR(50) | 工号(可选) |
|
|
|
+| `wechat_openid` | VARCHAR(100) | 微信 OpenID(扫码绑定时回填) |
|
|
|
+| `avatar` | VARCHAR(500) | 头像 URL |
|
|
|
+| `status` | INT | 状态:1-启用,0-禁用 |
|
|
|
+| `total_tasks` | INT | 累计任务数 |
|
|
|
+| `last_task_time` | DATETIME | 最后任务时间 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+| `update_time` | DATETIME | 更新时间 |
|
|
|
+
|
|
|
+#### `t_replenisher_device` — 补货员-设备关联表
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键,雪花算法 |
|
|
|
+| `replenisher_id` | BIGINT | 补货员ID |
|
|
|
+| `device_id` | VARCHAR(50) | 设备SN号 |
|
|
|
+| `source` | VARCHAR(20) | 绑定来源:`MANUAL`-手动,`QR`-扫码绑定,`SHOP_INHERIT`-门店继承 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+### 2.2 补货记录表
|
|
|
+
|
|
|
+#### `t_stock_record` — 上货/补货记录表
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键,雪花算法 |
|
|
|
+| `device_id` | VARCHAR(50) | 设备ID |
|
|
|
+| `activity_id` | VARCHAR(100) | 哈哈平台活动号 |
|
|
|
+| `stock_type` | INT | 类型:1-上货,2-补货 |
|
|
|
+| `stocker_id` | BIGINT | 上货员ID |
|
|
|
+| `stocker_name` | VARCHAR(50) | 上货员名称 |
|
|
|
+| `stocker_phone` | VARCHAR(20) | 上货员电话 |
|
|
|
+| `total_items` | INT | 上货商品种类数 |
|
|
|
+| `total_quantity` | INT | 上货总数量 |
|
|
|
+| `status` | INT | 状态:1-进行中,2-已完成,3-已取消 |
|
|
|
+| `start_time` | DATETIME | 开始时间 |
|
|
|
+| `end_time` | DATETIME | 结束时间 |
|
|
|
+| `remark` | VARCHAR(500) | 备注 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+| `update_time` | DATETIME | 更新时间 |
|
|
|
+
|
|
|
+#### `t_stock_record_item` — 补货明细表
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT | 主键,雪花算法 |
|
|
|
+| `record_id` | BIGINT | 上货记录ID |
|
|
|
+| `device_id` | VARCHAR(50) | 设备ID |
|
|
|
+| `product_id` | BIGINT | 商品ID |
|
|
|
+| `product_code` | VARCHAR(50) | 商品编码 |
|
|
|
+| `product_name` | VARCHAR(100) | 商品名称 |
|
|
|
+| `shelf_num` | INT | 货架层号 |
|
|
|
+| `quantity` | INT | 上货数量 |
|
|
|
+| `before_stock` | INT | 上货前库存 |
|
|
|
+| `after_stock` | INT | 上货后库存 |
|
|
|
+| `create_time` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+### 2.3 数据迁移
|
|
|
+
|
|
|
+`docs/database/migrate_replenisher.sql` 提供了从旧表 `t_stocker` 到 `t_replenisher` 的数据迁移脚本。
|
|
|
+
|
|
|
+> V1 表 `t_shop_replenisher` 及所有相关后端/前端代码已于 2026年4月 彻底清理移除。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、后端实现
|
|
|
+
|
|
|
+### 3.1 模块目录结构
|
|
|
+
|
|
|
+```
|
|
|
+haha-entity
|
|
|
+├── Replenisher.java # 补货员实体
|
|
|
+├── ReplenisherDevice.java # 补货员-设备关联实体
|
|
|
+├── StockRecord.java # 补货记录实体
|
|
|
+├── StockRecordItem.java # 补货明细实体
|
|
|
+├── dto/
|
|
|
+│ ├── ReplenisherCreateDTO.java # 创建补货员
|
|
|
+│ ├── ReplenisherUpdateDTO.java # 更新补货员
|
|
|
+│ ├── ReplenisherQueryDTO.java # 分页查询参数
|
|
|
+│ ├── ReplenisherBindDTO.java # 微信扫码绑定
|
|
|
+│ ├── BindReplenisherDTO.java # 绑定补货员到设备/门店
|
|
|
+│ ├── ReplenishDTO.java # 执行补货操作
|
|
|
+│ ├── BindDeviceDTO.java # 绑定设备
|
|
|
+│ ├── BatchBindDevicesDTO.java # 批量绑定设备
|
|
|
+│ ├── StatusDTO.java # 通用状态更新
|
|
|
+│ └── WechatLoginDTO.java # 微信登录
|
|
|
+
|
|
|
+haha-mapper
|
|
|
+├── ReplenisherMapper.java # 补货员 Mapper
|
|
|
+├── ReplenisherDeviceMapper.java # 关联 Mapper
|
|
|
+├── resources/mapper/
|
|
|
+│ ├── ReplenisherMapper.xml # 分页带设备绑定数查询
|
|
|
+│ └── ReplenisherDeviceMapper.xml # 批量插入忽略已存在
|
|
|
+
|
|
|
+haha-service
|
|
|
+├── ReplenisherService.java # 补货员服务接口
|
|
|
+├── impl/
|
|
|
+│ └── ReplenisherServiceImpl.java # 补货员服务实现
|
|
|
+
|
|
|
+haha-admin (运营平台)
|
|
|
+├── controller/
|
|
|
+│ ├── ReplenisherController.java # 补货员 CRUD 管理
|
|
|
+│ ├── ReplenisherLoginController.java # 微信登录/绑定
|
|
|
+│ └── ReplenisherOperationController.java # 补货操作(设备查询/库存/补货)
|
|
|
+
|
|
|
+haha-admin-web (前端)
|
|
|
+├── src/api/replenisher.ts # API 接口层
|
|
|
+├── src/views/replenisher/
|
|
|
+│ ├── index.vue # 补货员管理页面
|
|
|
+│ └── utils/
|
|
|
+│ ├── hook.tsx # 逻辑钩子
|
|
|
+│ └── types.ts # 类型定义
|
|
|
+
|
|
|
+haha-admin-mp (管理端小程序)
|
|
|
+├── src/api/replenish.ts # 补货员 API
|
|
|
+├── src/pages/
|
|
|
+│ ├── login/login.vue # 微信登录 + 绑定码入口
|
|
|
+│ └── replenish/
|
|
|
+│ ├── bind.vue # 微信绑定页
|
|
|
+│ └── index.vue / ... # 补货工作台
|
|
|
+
|
|
|
+haha-mp (用户端小程序)
|
|
|
+├── src/api/replenish.ts # 补货员 API
|
|
|
+├── src/pages/replenish/bind.vue # 微信绑定页
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 核心实体关系
|
|
|
+
|
|
|
+```
|
|
|
+┌─────────────┐ ┌──────────────────────┐ ┌──────────┐
|
|
|
+│ t_replenisher│────<│ t_replenisher_device │────>│ t_device │
|
|
|
+│ (补货员) │ │ (补货员-设备关联) │ │ (设备) │
|
|
|
+└─────────────┘ └──────────────────────┘ └──────────┘
|
|
|
+ │
|
|
|
+ │ (wechat_openid 绑定)
|
|
|
+ │
|
|
|
+ ┌────┴─────┐
|
|
|
+ │ 微信 OpenID│
|
|
|
+ └──────────┘
|
|
|
+```
|
|
|
+
|
|
|
+### 3.3 API 接口总览
|
|
|
+
|
|
|
+#### 3.3.1 补货员管理(运营平台,需要管理员权限)
|
|
|
+
|
|
|
+| 接口 | 方法 | 路径 | 权限 | 说明 |
|
|
|
+|------|------|------|------|------|
|
|
|
+| 分页列表 | GET | `/replenishers/list` | `replenisher:read` | 关键词+状态筛选,含设备绑定数 |
|
|
|
+| 详情 | GET | `/replenishers/{id}` | `replenisher:read` | 含设备绑定数 |
|
|
|
+| 搜索 | GET | `/replenishers/search` | `replenisher:read` | 仅搜索启用状态,用于下拉选择 |
|
|
|
+| 创建 | POST | `/replenishers` | `replenisher:create` | 姓名必填,手机号可选 |
|
|
|
+| 更新 | PUT | `/replenishers/{id}` | `replenisher:update` | 仅更新非空字段 |
|
|
|
+| 状态 | PUT | `/replenishers/{id}/status` | `replenisher:update` | 启用/禁用 |
|
|
|
+| 删除 | DELETE | `/replenishers/{id}` | `replenisher:delete` | 级联解绑设备 |
|
|
|
+| 绑定设备 | POST | `/replenishers/{id}/devices` | `replenisher:update` | 绑定设备到补货员 |
|
|
|
+| 解绑设备 | DELETE | `/replenishers/{id}/devices/{deviceId}` | `replenisher:update` | 解绑单个设备 |
|
|
|
+| 批量绑定 | POST | `/replenishers/batch-bind` | `replenisher:update` | 多补货员→多设备 |
|
|
|
+| **生成绑定码** | POST | `/replenishers/{id}/binding-code` | `replenisher:update` | **生成微信绑定码** |
|
|
|
+
|
|
|
+#### 3.3.2 门店补货员关联(运营平台)
|
|
|
+
|
|
|
+| 接口 | 方法 | 路径 | 权限 | 说明 |
|
|
|
+|------|------|------|------|------|
|
|
|
+| 获取门店补货员 | GET | `/shops/{id}/replenishers` | `shop:read` | 通过设备绑定反向推导门店关联的补货员 |
|
|
|
+| 绑定到门店 | POST | `/shops/{id}/replenishers` | `shop:update` | 将补货员绑定到门店下的所有设备 |
|
|
|
+
|
|
|
+#### 3.3.3 补货员登录/绑定(免登录)
|
|
|
+
|
|
|
+| 接口 | 方法 | 路径 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| 微信静默登录 | POST | `/replenisher/login/wechat` | 已绑定微信的补货员直接登录 |
|
|
|
+| 扫码绑定微信 | POST | `/replenisher/login/bind` | 绑定码 + 微信凭证进行绑定 |
|
|
|
+
|
|
|
+#### 3.3.4 补货员操作(需补货员身份认证)
|
|
|
+
|
|
|
+| 接口 | 方法 | 路径 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| 获取个人信息 | GET | `/replenisher/my-info` | 含设备绑定数 |
|
|
|
+| 设备列表 | GET | `/replenisher/device/list` | 含库存概览 |
|
|
|
+| 设备库存 | GET | `/replenisher/device/inventory/{deviceId}` | 详细库存 |
|
|
|
+| 执行补货 | POST | `/replenisher/stock/replenish` | 增加库存 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、微信绑定流程
|
|
|
+
|
|
|
+### 4.1 流程概述
|
|
|
+
|
|
|
+```
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ 补货员微信绑定流程 │
|
|
|
+│ │
|
|
|
+│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
|
|
+│ │ 管理员后台 │───>│ 生成一次性绑定码│───>│ 展示二维码/绑定码 │ │
|
|
|
+│ │ 创建补货员 │ │ (Redis 24h) │ │ (前端弹窗展示) │ │
|
|
|
+│ └──────────┘ └──────────────┘ └────────┬─────────┘ │
|
|
|
+│ │ │
|
|
|
+│ ▼ │
|
|
|
+│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
|
|
+│ │ 绑定成功 │<───│ 存入openid + │<───│ 补货员扫码/输入 │ │
|
|
|
+│ │ 自动登录 │ │ 删除绑定码 │ │ + 微信授权 │ │
|
|
|
+│ └──────────┘ └──────────────┘ └──────────────────┘ │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 详细步骤
|
|
|
+
|
|
|
+**1. 管理员创建补货员**
|
|
|
+- 运营平台后台 → 补货员管理 → 创建补货员
|
|
|
+- 输入姓名、手机号(可选)、工号(可选)
|
|
|
+- 创建后状态为启用,`wechat_openid` 为空
|
|
|
+
|
|
|
+**2. 管理员生成绑定码**
|
|
|
+- 在补货员列表操作列点击"绑定码"
|
|
|
+- 后端生成 24 位 UUID 大写绑定码,存入 Redis(24 小时 TTL)
|
|
|
+- 前端弹窗展示绑定码和二维码(小程序路径 `pages/replenish/bind?code=xxx`)
|
|
|
+- 同一补货员如已绑定微信则拒绝生成
|
|
|
+
|
|
|
+**3. 补货员扫码绑定**
|
|
|
+- 打开管理端小程序(`haha-admin-mp`)或用户端小程序(`haha-mp`)
|
|
|
+- 扫描二维码或手动输入绑定码
|
|
|
+- 点击"绑定微信" → `uni.login()` 获取微信 code
|
|
|
+- 调用 `POST /replenisher/login/bind` 接口,传入 `bindingCode` + `code`
|
|
|
+- 后端验证 Redis 绑定码 → `jscode2session` 获取 openid → 检查 openid 唯一性 → 存入补货员记录 → 删除 Redis 绑定码 → 自动登录
|
|
|
+
|
|
|
+**4. 后续登录**
|
|
|
+- 已绑定微信的补货员可直接使用微信静默登录 `POST /replenisher/login/wechat`
|
|
|
+- 后端通过 `wechat_openid` 定位补货员 → 检查状态 → Sa-Token 登录
|
|
|
+
|
|
|
+### 4.3 安全机制
|
|
|
+
|
|
|
+| 机制 | 说明 |
|
|
|
+|------|------|
|
|
|
+| **绑定码一次性有效** | 使用后立即从 Redis 删除 |
|
|
|
+| **绑定码过期** | Redis TTL 24 小时 |
|
|
|
+| **openid 唯一性检查** | 绑定和登录时都检查 openid 是否已被其他账号占用 |
|
|
|
+| **禁止重复绑定** | 已绑定微信的补货员不能再生成绑定码 |
|
|
|
+| **账号状态检查** | 禁用状态的补货员无法登录 |
|
|
|
+
|
|
|
+### 4.4 微信配置
|
|
|
+
|
|
|
+补货员使用**管理端微信小程序**的 AppId/Secret:
|
|
|
+
|
|
|
+```yaml
|
|
|
+wechat:
|
|
|
+ admin-miniapp:
|
|
|
+ app-id: xxxxxxxxxxxxxx
|
|
|
+ secret: xxxxxxxxxxxxxx
|
|
|
+```
|
|
|
+
|
|
|
+通过 `jscode2session` 接口获取 openid:
|
|
|
+
|
|
|
+```
|
|
|
+GET https://api.weixin.qq.com/sns/jscode2session
|
|
|
+ ?appid={appid}
|
|
|
+ &secret={secret}
|
|
|
+ &js_code={code}
|
|
|
+ &grant_type=authorization_code
|
|
|
+```
|
|
|
+
|
|
|
+### 4.5 认证方式
|
|
|
+
|
|
|
+补货员登录使用 **Sa-Token 同一域名**(与运营平台共享 `adminAccessToken`),通过 Token Session 中的 `userType` 字段区分身份:
|
|
|
+
|
|
|
+```java
|
|
|
+StpUtil.login(replenisher.getId());
|
|
|
+StpUtil.getTokenSession().set("userType", "REPLENISHER");
|
|
|
+```
|
|
|
+
|
|
|
+登录成功后返回 `LoginVO`:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "token": "sa-token-value",
|
|
|
+ "userInfo": {
|
|
|
+ "id": "182734...",
|
|
|
+ "nickname": "张三",
|
|
|
+ "avatar": "https://...",
|
|
|
+ "phone": "138..."
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+补货员操作接口通过 `getCurrentReplenisher()` 方法验证身份:
|
|
|
+```java
|
|
|
+String userType = StpUtil.getTokenSession().getString("userType");
|
|
|
+if (!"REPLENISHER".equals(userType)) {
|
|
|
+ throw new BusinessException(403, "无访问权限");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、补货操作流程
|
|
|
+
|
|
|
+### 5.1 补货流程
|
|
|
+
|
|
|
+```
|
|
|
+┌──────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────┐
|
|
|
+│ 登录小程序 │───>│ 查看绑定设备 │───>│ 查看设备库存详情 │───>│ 执行补货 │
|
|
|
+│ 微信静默 │ │ 列表含库存概览 │ │ 含商品库存明细 │ │ 增加库存 │
|
|
|
+└──────────┘ └──────────────┘ └──────────────────┘ └──────────┘
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 补货 API
|
|
|
+
|
|
|
+**请求**:
|
|
|
+```json
|
|
|
+POST /replenisher/stock/replenish
|
|
|
+{
|
|
|
+ "deviceId": "B142977",
|
|
|
+ "items": [
|
|
|
+ {
|
|
|
+ "productId": 123456,
|
|
|
+ "productCode": "6921234567890",
|
|
|
+ "productName": "矿泉水550ml",
|
|
|
+ "quantity": 24,
|
|
|
+ "shelfNum": 1,
|
|
|
+ "position": "A1"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**响应**:
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "message": "补货完成,成功1项,失败0项",
|
|
|
+ "data": {
|
|
|
+ "total": 1,
|
|
|
+ "success": 1,
|
|
|
+ "details": [
|
|
|
+ {
|
|
|
+ "productId": 123456,
|
|
|
+ "productName": "矿泉水550ml",
|
|
|
+ "quantity": 24,
|
|
|
+ "afterStock": 120,
|
|
|
+ "success": true
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5.3 安全校验
|
|
|
+
|
|
|
+补货操作前进行权限验证:
|
|
|
+1. 验证当前用户是否为补货员(通过 `userType` 身份标识)
|
|
|
+2. 验证设备是否绑定到此补货员(从 `t_replenisher_device` 查询)
|
|
|
+3. 逐项执行补货,每项独立事务,失败不影响其他项
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、前端实现
|
|
|
+
|
|
|
+### 6.1 管理后台(haha-admin-web)
|
|
|
+
|
|
|
+- **页面**:`src/views/replenisher/index.vue` — 补货员管理页面
|
|
|
+- **API**:`src/api/replenisher.ts` — 全部 CRUD + 绑定设备 + 生成绑定码
|
|
|
+- **功能**:
|
|
|
+ - 补货员分页列表(含设备绑定数)
|
|
|
+ - 创建/编辑/删除补货员
|
|
|
+ - 启用/禁用
|
|
|
+ - 绑定/解绑设备
|
|
|
+ - **生成绑定码弹窗**(展示 24 位绑定码 + 二维码)
|
|
|
+- **依赖**:`qrcode` npm 包用于生成二维码图片
|
|
|
+
|
|
|
+### 6.2 管理端小程序(haha-admin-mp)
|
|
|
+
|
|
|
+- **登录页**:`src/pages/login/login.vue`
|
|
|
+ - 微信一键登录(`POST /replenisher/login/wechat`)
|
|
|
+ - 未绑定提示 → 展开绑定码输入区域
|
|
|
+ - 绑定码 + 微信授权 → 绑定登录
|
|
|
+- **绑定页**:`src/pages/replenish/bind.vue`
|
|
|
+ - 从 `options.code` / `scene` 获取绑定码
|
|
|
+ - 绿色清新 UI,绑定流程指引
|
|
|
+ - 微信授权 → 绑定 API → 自动跳转
|
|
|
+- **补货工作台**:`src/pages/replenish/` 目录下
|
|
|
+ - 设备列表
|
|
|
+ - 库存查看
|
|
|
+ - 执行补货
|
|
|
+
|
|
|
+### 6.3 用户端小程序(haha-mp)
|
|
|
+
|
|
|
+- **绑定页**:`src/pages/replenish/bind.vue`
|
|
|
+ - 金色品牌风格 UI,三步骤流程指引
|
|
|
+ - 支持 `options.code` / `options.bindingCode` / `options.scene` 获取绑定码
|
|
|
+ - 绑定成功后保存 token 并跳转首页
|
|
|
+ - 已加入免登录白名单
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、Redis 键设计
|
|
|
+
|
|
|
+| Key | 格式 | 说明 | TTL |
|
|
|
+|-----|------|------|-----|
|
|
|
+| 补货员绑定码 | `replenisher:bind:code:{bindingCode}` | Value 为补货员 ID | 24 小时 |
|
|
|
+
|
|
|
+定义在 `RedisConstants.java`:
|
|
|
+```java
|
|
|
+public static final String REPLENISHER_BIND_CODE_KEY = "replenisher:bind:code:%s";
|
|
|
+public static final String REPLENISHER_BIND_CODE_KEY_PREFIX = "replenisher:bind:code:";
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、关键设计决策
|
|
|
+
|
|
|
+### 8.1 为什么不允许主动注册?
|
|
|
+
|
|
|
+**决策**:补货员不允许自主注册,必须由管理员在后台创建后,通过绑定码完成微信绑定。
|
|
|
+
|
|
|
+**原因**:
|
|
|
+- 补货员属于内部人员,需要经过公司审核和培训
|
|
|
+- 设备绑定权限不可随意分配(关系到库存数据和设备安全)
|
|
|
+- 避免未授权的第三方通过扫描二维码自动注册
|
|
|
+
|
|
|
+### 8.2 为什么使用 Redis 存储绑定码而非数据库?
|
|
|
+
|
|
|
+- 绑定码是一次性凭证,无需持久化存储
|
|
|
+- Redis 自动过期(TTL)机制天然适合此类场景
|
|
|
+- 避免数据库表膨胀(绑定码数据量少但频繁生成/删除)
|
|
|
+- 高性能读写,不增加数据库压力
|
|
|
+
|
|
|
+### 8.3 为什么补货员使用管理端小程序的 AppId?
|
|
|
+
|
|
|
+补货员和管理端共用同一套微信小程序(`haha-admin-mp`),使用管理端小程序的 AppId/Secret 进行微信登录。这意味着:
|
|
|
+- 补货员和管理员扫码的是同一个微信小程序
|
|
|
+- 通过 `userType` 区分身份和权限
|
|
|
+- 补货员无法访问管理后台页面
|
|
|
+
|
|
|
+### 8.4 补货操作的事务处理
|
|
|
+
|
|
|
+补货操作的 `increaseStock` 方法内部通过 `synchronized (deviceId.intern())` 实现设备级锁,防止同一设备并发补货导致库存数据不一致。每条商品补货独立执行,失败不影响其他商品。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 九、SQL 参考
|
|
|
+
|
|
|
+### 9.1 补货员分页查询(含设备绑定数)
|
|
|
+
|
|
|
+```xml
|
|
|
+<select id="selectWithDeviceCount" resultMap="BaseResultMap">
|
|
|
+ SELECT r.*,
|
|
|
+ (SELECT COUNT(*) FROM t_replenisher_device rd WHERE rd.replenisher_id = r.id) AS bound_device_count
|
|
|
+ FROM t_replenisher r
|
|
|
+ <where>
|
|
|
+ <if test="keyword != null and keyword != ''">
|
|
|
+ AND (r.name LIKE CONCAT('%', #{keyword}, '%')
|
|
|
+ OR r.phone LIKE CONCAT('%', #{keyword}, '%')
|
|
|
+ OR r.employee_id LIKE CONCAT('%', #{keyword}, '%'))
|
|
|
+ </if>
|
|
|
+ <if test="status != null">
|
|
|
+ AND r.status = #{status}
|
|
|
+ </if>
|
|
|
+ </where>
|
|
|
+ ORDER BY r.create_time DESC
|
|
|
+</select>
|
|
|
+```
|
|
|
+
|
|
|
+### 9.2 通过设备查询补货员
|
|
|
+
|
|
|
+```sql
|
|
|
+SELECT DISTINCT r.* FROM t_replenisher r
|
|
|
+INNER JOIN t_replenisher_device rd ON r.id = rd.replenisher_id
|
|
|
+INNER JOIN t_device d ON rd.device_id = d.device_id
|
|
|
+WHERE d.shop_id = #{shopId}
|
|
|
+ORDER BY r.create_time DESC
|
|
|
+```
|
|
|
+
|
|
|
+### 9.3 批量插入关联(忽略已存在)
|
|
|
+
|
|
|
+```sql
|
|
|
+INSERT IGNORE INTO t_replenisher_device (id, replenisher_id, device_id, source, create_time)
|
|
|
+VALUES (?, ?, ?, ?, ?)
|
|
|
+```
|