依据: 《购物车功能需求.md》v1.0
关联: 平台《商品管理功能需求》v1.3.3、《商品审核功能需求》v1.0、《商品分类技术方案》v1.2、《店铺管理技术方案》v1.3.6、《会员管理技术方案》v1.3、《订单管理技术方案》v1.0.1、《关联需求分析.md》v1.6 §11;C 端《商品详情内页技术方案》v1.0、《我的服务技术方案》v1.1
范围: C 端/api/cart/**购物车行 CRUD、加购写入、勾选态、失效判定、同店结算预校验;不含 确认订单、支付、履约。
原则: 新建biz_member_cart_item;商品/店铺/分类 只读 实时档案;不预扣库存;可购 四条件 复用IGoodsPurchaseFacade;v1.0 统一规格(spec_key固定默认键)。
| 项 | 选型 |
|---|---|
| 基础框架 | RuoYi v3.9.2(springboot2 分支) |
| 数据库 | MySQL 5.7.39 |
| ORM / 响应 | MyBatis;AjaxResult(code、msg、data) |
| 鉴权 | 全部接口须 会员 Token(MemberWebConfig → /api/cart/**) |
| 分页 | 购物车 不分页(单会员行数可控);列表一次返回 |
| 缓存 | 本期不做 Redis |
baqing-shop/src/main/java/com/ruoyi/web/modules/cart/
├── controller/CartAppController.java # /api/cart/**
├── service/ICartAppService.java
├── service/impl/CartAppServiceImpl.java
├── mapper/CartAppMapper.java
├── domain/BizMemberCartItem.java
├── dto/
│ ├── CartItemAddDTO.java # 加购
│ ├── CartItemQuantityDTO.java
│ ├── CartItemCheckedBatchDTO.java
│ ├── CartItemBatchDeleteDTO.java
│ └── CartCheckoutPrepareDTO.java # 去结算
├── vo/
│ ├── CartListVO.java # 按店分组列表
│ ├── CartShopGroupVO.java
│ ├── CartItemVO.java
│ └── CartCheckoutPrepareVO.java
└── constant/CartConstants.java
resources/mapper/cart/CartAppMapper.xml
sql/biz_member_cart_item.sql # 新建
account/(已有 · 协作)
├── config/MemberWebConfig.java # 追加 /api/cart/**
├── support/MemberContext.java
└── facade/IMemberFacade.isMemberEnabled
goods/(已有 · 协作)
├── facade/IGoodsPurchaseFacade.canPurchase
├── mapper/BizGoodsMapper
└── constant/GoodsConstants
store/(已有 · 协作)
└── facade/IAgriShopFacade.isShopOpenForOrder
category/(已有 · 协作)
└── facade/ICategoryFacade.isCategoryVisible
【上游写入】
商品详情 POST /api/cart/items
→ 四条件校验 + 同规格合并累加
【本模块】
GET /api/cart → 条目列表(cartItemId 倒序,含店铺)+ 勾选合计
PUT /api/cart/items/{id}/quantity → 改数量(≤库存)
PUT /api/cart/items/checked → 批量更新勾选
DELETE /api/cart/items/{id} → 移出
DELETE /api/cart/items → 批量移出
DELETE /api/cart/invalid → 清理失效行
POST /api/cart/checkout/prepare → 同店结算预校验 → 确认订单(待建)
【下游】
确认订单(待建)← checkout/prepare 返回 shopId + 行快照
下单成功(待建)→ ICartFacade.removeByOrderItems(占位)
biz_member_cart_item
├── JOIN biz_goods → 名称/主图/价/库/状态
├── JOIN biz_shop → 店名/店态
└── Facade → 四条件、分类可见
| 关联模块 | 协作 |
|---|---|
| 平台 · 商品管理/审核 | 维护 biz_goods;下架 不删 购物车行;列表 实时 读态 |
| 平台 · 店铺管理 | 维护 biz_shop;停业 可展示、禁结算 |
| 平台 · 商品分类 | 分类隐藏 → 行 可展示、禁结算 |
| 平台 · 会员管理 | 禁用会员 不可 调本模块 |
| 平台 · 订单管理 | 支付成功扣库存;下单后 移除/扣减 购物车行(确认订单专册) |
| C 端 · 商品详情 | 唯一 加购入口 POST /api/cart/items |
| C 端 · 确认订单 | 待建设;本模块 仅 prepare |
| C 端 · 我的服务 | 地址 不在 购物车;结算页引用地址簿 |
| 接口 | 本模块用法 |
|---|---|
IGoodsPurchaseFacade.canPurchase(goodsId) |
加购、去结算 四条件(出售中/店开业/分类可见/库存>0) |
BizGoodsMapper.selectById |
读实时 sale_price、stock;改量/累加上限 |
IMemberFacade.isMemberEnabled |
写操作前校验会员 启用 |
IAgriShopFacade / ICategoryFacade |
已含于 canPurchase;列表失效 细分文案 时 补充 读档 |
ICartFacade(规划) |
供 订单模块 下单成功后 removeItems(memberId, cartItemIds) |
库存与数量: canPurchase 仅校验 stock>0;本 Service 额外 校验 quantity <= stock(加购累加、改量、prepare)。
biz_member_cart_item| 字段 | 类型 | 说明 |
|---|---|---|
| cart_item_id | bigint PK | 自增 |
| member_id | bigint | 会员 ID(biz_member.member_id) |
| shop_id | bigint | 店铺 ID(加购时自 biz_goods.shop_id 冗余,便于分组) |
| goods_id | bigint | 商品 ID |
| spec_key | varchar(64) | 规格键;v1.0 统一规格固定 ''(空串) |
| spec_text | varchar(256) | 加购时 规格展示快照(如「默认」或规格项拼接) |
| quantity | int | 数量,≥1 |
| checked | char(1) | 0 未勾选 / 1 已勾选;新行默认 1 |
| create_time | datetime | 首次加购 |
| update_time | datetime | 最近改量/勾选 |
约束与索引:
| 项 | 说明 |
|---|---|
| 唯一 | uk_member_goods_spec (member_id, goods_id, spec_key) — 同规格合并 |
| 索引 | idx_member_update (member_id, update_time DESC) — 列表排序 |
| 索引 | idx_member_shop (member_id, shop_id) — 按店分组 |
DDL: sql/biz_member_cart_item.sql
CREATE TABLE IF NOT EXISTS `biz_member_cart_item` (
`cart_item_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '购物车行ID',
`member_id` bigint(20) NOT NULL COMMENT '会员ID',
`shop_id` bigint(20) NOT NULL COMMENT '店铺ID',
`goods_id` bigint(20) NOT NULL COMMENT '商品ID',
`spec_key` varchar(64) NOT NULL DEFAULT '' COMMENT '规格键;v1统一规格为空串',
`spec_text` varchar(256) NOT NULL DEFAULT '默认' COMMENT '规格展示快照',
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
`checked` char(1) NOT NULL DEFAULT '1' COMMENT '0未勾选1已勾选',
`create_time` datetime DEFAULT NULL COMMENT '加购时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`cart_item_id`),
UNIQUE KEY `uk_member_goods_spec` (`member_id`, `goods_id`, `spec_key`),
KEY `idx_member_update` (`member_id`, `update_time`),
KEY `idx_member_shop` (`member_id`, `shop_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员购物车';
| 表 | 本模块用途 | 权威 DDL |
|---|---|---|
biz_goods |
主图、名称、售价、库存、状态 | sql/biz_goods.sql |
biz_shop |
店名、店态、删标 | sql/biz_shop.sql |
biz_goods_category |
经 ICategoryFacade 判可见 |
sql/biz_goods_category.sql |
biz_member |
会员启用态 | sql/biz_member.sql |
| 项 | 定稿 |
|---|---|
| SKU 矩阵 | 无;与详情 v1 统一规格 一致 |
spec_key |
固定 '';预留多规格扩展 |
spec_text |
加购请求传入;空则 Service 读 biz_goods_attr(attr_type=2)拼接,仍无则 「默认」 |
| 合并维度 | member_id + goods_id + spec_key |
| invalidType | 条件 | 展示文案方向 | 可勾选 |
|---|---|---|---|
NONE |
四条件满足且 quantity <= stock |
— | ✓ |
OUT_OF_STOCK |
出售中但 stock=0 或 quantity>stock |
缺货 / 库存不足 | ✗ |
OFF_SHELF |
goods_status != '2' |
已下架 / 不可购买 | ✗ |
SHOP_CLOSED |
shop_status='1' |
店铺休息中 | ✗ |
CATEGORY_HIDDEN |
分类不可见 | 商品不可购买 | ✗ |
GOODS_DELETED |
商品 del_flag='2' |
商品失效 | ✗ |
SHOP_DELETED |
店铺 del_flag='2' |
店铺失效 | ✗ |
浏览与下单分离: 历史行 保留;
invalidType != NONE时purchasable=false,不可 参与 prepare。
基路径: /api/cart
鉴权: 全部须 Authorization: Bearer {token};未登录 → 401「请先登录」
MemberWebConfig 追加: .addPathPatterns(..., "/api/cart/**")
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/cart |
购物车条目列表 |
| POST | /api/cart/items |
加购(详情页调用) |
| PUT | /api/cart/items/{cartItemId}/quantity |
修改数量 |
| PUT | /api/cart/items/checked |
批量更新勾选 |
| DELETE | /api/cart/items/{cartItemId} |
移出单行 |
| DELETE | /api/cart/items |
批量移出(body 传 id 列表) |
| DELETE | /api/cart/invalid |
清理当前会员全部失效行 |
| POST | /api/cart/checkout/prepare |
去结算 预校验 |
本期不提供:
| 项 | 说明 |
|---|---|
| 跨店合单 prepare | CT-S1 不支持 |
| 平台/商家查会员购物车 | 无 B 端 API |
| 购物车搜索/推荐 | 需求 CT 非本期 |
GET /api/cart| 项 | 说明 |
|---|---|
| 数据范围 | 仅当前会员 |
| 响应 | AjaxResult → data: CartListVO |
CartListVO:
| 字段 | 类型 | 说明 |
|---|---|---|
| items | array | 购物车条目列表(主结构),见下 |
| checkedSummary | object | 当前 已勾选且可购 行合计(前端底栏参考) |
items[] → CartItemVO:
| 字段 | 类型 | 说明 |
|---|---|---|
| cartItemId | long | |
| goodsId | long | 进详情 |
| goodsName | string | 实时 |
| mainPic | string | 实时 |
| specText | string | 加购快照 |
| salePrice | decimal | 当前售价 |
| quantity | int | |
| subtotal | decimal | salePrice × quantity |
| checked | boolean | |
| purchasable | boolean | 是否可勾选结算 |
| invalidType | string | §2.4 枚举;可购时为 NONE |
| invalidMsg | string | 前端 Toast/标签;可购时 null |
| shopId | long | 所属店铺 |
| shopName | string | 店铺名称(实时) |
| shopAvatar | string | 店铺头像 |
| shopStatus | string | 0 开业 / 1 停业 |
排序: cart_item_id DESC(最新加购/合并行在前)。
空态: items=[] → 前端「购物车是空的」。
checkedSummary:
| 字段 | 说明 |
|---|---|
| checkedCount | 已勾选 且 purchasable 的行数 |
| checkedQuantity | 上述行数量之和 |
| checkedAmount | 上述行 subtotal 之和 |
同店结算: 前端底栏以 当前操作店 内勾选行为准;若勾选跨店,不得 合单 prepare(CT-S4)。
POST /api/cart/items| 项 | 说明 |
|---|---|
| 调用方 | 商品详情「加入购物车」 |
| 校验 | 会员启用;quantity >= 1;四条件 + quantity <= stock |
Body · CartItemAddDTO:
| 字段 | 必填 | 说明 |
|---|---|---|
| goodsId | ✓ | |
| quantity | ✓ | 默认 1 |
| specKey | 否 | v1 不传或传 '' |
| specText | 否 | 不传则 Service 生成 |
逻辑:
canPurchase(goodsId) → 不通过 → 400 + reason
quantity > stock → 400「库存不足」
已存在同 member+goods+spec_key 行
→ newQty = min(quantity + 旧quantity, stock)
→ UPDATE quantity, update_time;checked 保持
不存在
→ INSERT;checked='1';shop_id 取自 goods
响应: AjaxResult.success(data) → { cartItemId, quantity }
PUT /api/cart/items/{cartItemId}/quantityBody: { "quantity": 3 }
| 规则 | 说明 |
|---|---|
| 归属 | 行须属于 当前会员 |
| 下限 | quantity >= 1 |
| 上限 | quantity <= biz_goods.stock(实时) |
| 失效 | 改量后 不重算 checked;列表读时刷新 purchasable |
失败 → 400 + 明确 msg;成功 → { cartItemId, quantity }。
PUT /api/cart/items/checkedBody · CartItemCheckedBatchDTO:
{
"items": [
{ "cartItemId": 1, "checked": true },
{ "cartItemId": 2, "checked": false }
]
}
| 规则 | 说明 |
|---|---|
| 归属 | 全部 id 须属当前会员 |
| 失效行 | 允许 写入 checked;前端 置灰不可勾选;prepare 时 过滤 |
| 接口 | Body | 说明 |
|---|---|---|
DELETE .../items/{cartItemId} |
— | 单行;非本人 → 404 |
DELETE .../items |
{ "cartItemIds": [1,2] } |
批量;忽略 不存在 id |
均 物理删除 购物车行;幂等。
DELETE /api/cart/invalid| 项 | 说明 |
|---|---|
| 范围 | 当前会员全部 purchasable=false 行 |
| 响应 | { "removedCount": n } |
POST /api/cart/checkout/prepare| 项 | 说明 |
|---|---|
| 用途 | 跳转 确认订单 前 服务端 再校验(CT-S2、§10) |
Body · CartCheckoutPrepareDTO:
| 字段 | 必填 | 说明 |
|---|---|---|
| cartItemIds | ✓ | 用户勾选行 ID,至少 1 条 |
校验顺序:
1. 全部 cartItemId 属于当前会员
2. 解析 shop_id → 必须 **同一店铺**(否则 400「请选择同一店铺的商品结算」)
3. 逐行:canPurchase + quantity <= stock
4. 通过 → 返回 CheckoutPrepareVO
data · CartCheckoutPrepareVO:
| 字段 | 类型 | 说明 |
|---|---|---|
| shopId | long | |
| shopName | string | |
| items | array | { cartItemId, goodsId, goodsName, mainPic, specText, salePrice, quantity, subtotal } |
| goodsAmount | decimal | 商品合计(不含运费) |
确认订单(待建): 接收
shopId + items[]做地址/运费/提交订单;下单成功后调用ICartFacade.removeItems删除对应cart_item_id。
| 情形 | 行为 |
|---|---|
| Token 失效 | 401 |
| 会员禁用 | 403「账号已禁用」 |
| 商品/行不存在 | 404 |
| 库存/四条件不满足 | 400 + reason / invalidMsg |
| prepare 跨店 | 400「请选择同一店铺的商品结算」 |
CartAppController
→ ICartAppService
├── list(memberId) → 分组 + 失效计算 + checkedSummary
├── addItem(memberId, dto) → canPurchase + merge/insert
├── updateQuantity(...) → stock cap
├── updateChecked(...) → batch
├── remove / removeBatch
├── cleanInvalid(memberId)
└── prepareCheckout(memberId, ids) → 同店 + 四条件
CartAppMapper
├── selectByMemberId(memberId) → JOIN goods + shop
├── selectByIdAndMember(...)
├── insert / updateQuantity / updateChecked
└── deleteByIds / deleteInvalidByMember
| 要点 | 说明 |
|---|---|
| memberId | 仅 从 MemberContext 取,禁止 客户端传 |
| 价格 | 列表/prepare 读实时 sale_price;不 在 cart 表存价 |
| 事务 | 加购 merge、批量删 单事务 |
| 日志 | 写操作 @Log(可选) |
| 平台/商家操作 | 购物车行为 |
|---|---|
| 商品 下架/审核失败/待审 | 行 保留;列表 OFF_SHELF;不可 prepare |
| 商品 改价 | 列表读 新价;prepare 用 新价 |
| 商品 减库存 | 列表可能 OUT_OF_STOCK;改量/prepare 拦截 |
| 店铺 停业 | 行 保留;SHOP_CLOSED;prepare 拦截 |
| 店铺 删店 | SHOP_DELETED;可 清理失效 |
| 分类 隐藏 | CATEGORY_HIDDEN |
| 会员 禁用 | 403,全接口不可用 |
| 订单 支付成功 | 确认订单模块 删行(不占本模块) |
对齐《关联需求分析》§8:不自动删 购物车行;仅 标失效 + 用户 批量清理。
| 能力 | 商品详情 /api/goods |
本模块 /api/cart |
确认订单(待建) |
|---|---|---|---|
| 详情展示 | ✓ | — | — |
GET .../can-purchase |
✓ | 内部复用 Facade | — |
| POST 加购 | 前端调 cart | ✓ | — |
| 列表/改量/勾选/清理 | — | ✓ | — |
| prepare | — | ✓ | 接收参数 |
| 地址/运费/提交订单 | — | — | ✓ |
MemberWebConfig 变更:
.addPathPatterns("/api/member/**", "/api/merchant/entry/**",
"/api/shop/*/follow", "/api/cart/**", "/api/checkout/**", "/api/order/**")
.excludePathPatterns("/api/member/register", "/api/member/login", ...,
"/api/merchant/entry/agreement", "/api/merchant/entry/status");
完整 exclude 列表见《会员注册登录技术方案》§3.1。
| 需求规则 | 实现 |
|---|---|
| CT0 须登录 | /api/cart/** 拦截器 |
| CT1 仅当前会员 | SQL member_id = #{memberId} |
| CT2 按店分组 | shop_id 冗余 + 列表 groups[] |
| CT3 行字段 | CartItemVO |
| CT4 勾选 | checked 字段 + PUT .../checked |
| CT5 改量 ≥1 ≤库存 | updateQuantity |
| CT6 移出/清理失效 | DELETE 系列 |
| CT7 不支持跨店 | prepareCheckout 校验同 shop_id |
| CT8 库存/可购校验 | 列表读时 + prepare |
| CT9 失效进详情 | 前端 goodsId 路由 |
| CT10 详情加购、同规格合并 | POST /items + uk_member_goods_spec |
| 新行默认勾选 | INSERT checked='1' |
| 不预扣库存 | 无库存锁定表 |
ICartFacade(供订单模块)public interface ICartFacade {
/** 下单成功后移除已结算购物车行 */
void removeItems(Long memberId, List<Long> cartItemIds);
}
实现落位 cart.facade.CartFacadeImpl;订单模块 待实现 前可不建。
| 版本 | 说明 |
|---|---|
| v1.0 | 首版:biz_member_cart_item、8 个 C 端 API、按店分组、四条件、同店 prepare;关联平台商品/店铺/分类/会员/订单与详情 |
文档版本:v1.0 · 关联《购物车功能需求.md》v1.0、《商品详情内页技术方案》v1.0、《订单管理技术方案》v1.0.1、《店铺管理技术方案》v1.3.6、《关联需求分析.md》v1.6 · 技术栈 RuoYi v3.9.2-springboot2 + MySQL 5.7.39