商城模块
商城模块
ZrAdminNet 内置了一套完整的商城模块(ZR.Mall),涵盖品牌管理、商品管理、订单管理、规格模板和销售统计等功能。
一、模块架构
┌──────────────────────────────────────────────────────────┐
│ ZR.Mall 商城模块 │
├──────────────────────────────────────────────────────────┤
│ Controllers (6) Services (7) │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ BrandController │─────>│ BrandService │ │
│ │ CategoryController│────>│ CategoryService │ │
│ │ ProductController│─────>│ ProductService │ │
│ │ OrderController │─────>│ OMSOrderService │ │
│ │ SkusController │─────>│ SkusService │ │
│ │ SpecTemplateCtrl │─────>│ SpecTemplateService │ │
│ └─────────────────┘ │ ProductSpecService │ │
│ └──────────────────────┘ │
│ │
│ Model (9 Entities + 8 DTOs) │
│ ┌────────────────────────────────────────────────┐ │
│ │ Product ──1:N── Skus (SKU 含乐观锁版本号) │ │
│ │ Product ──1:1── Category (树形自引用) │ │
│ │ Product ──1:1── Brand │ │
│ │ Product ──1:N── ProductSpec (规格值 JSON) │ │
│ │ OMSOrder ──1:N── OMSOrderItem (商品快照) │ │
│ │ MMSUserAddress (收货地址, 独立) │ │
│ │ SpecTemplate (规格模板, 独立可复用) │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ 数据库: MallDb(独立库,SaaS 模式下每租户一份) │
│ 表前缀: mms_(商城管理) / oms_(订单管理) │
└──────────────────────────────────────────────────────────┘
技术栈
| 组件 | 技术 |
|---|---|
| 框架 | .NET 8.0 |
| ORM | SqlSugar (CodeFirst) |
| 依赖注入 | [AppService] 自动注册 |
| 对象映射 | Mapster (Adapt) |
| 数据校验 | FluentValidation |
| 日志 | NLog |
| 多租户 | [Tenant("MallDb")] SaaS 独立数据库 |
二、数据库路由
商城实体全部标注 [Tenant("MallDb")]:
- 非 SaaS 模式:
MallDb配置项指定商城库 ConfigId(默认 "0" 与主库共享) - SaaS 模式:
[Tenant("MallDb")]经配置解析后路由到租户独立的商城数据库
// appsettings.json
{
"MallDb": "0", // 非 SaaS 模式下商城实体使用的 ConfigId
"dbConfigs": [
{ "ConfigId": "0", "Conn": "...", "DbType": 1 } // 主库(含商城)
]
}
三、数据模型
3.1 商品 (Product)
表名:mms_product,主键:ProductId (long)
主要字段
| 字段 | 类型 | 说明 |
|---|---|---|
| ProductId | long | 主键 |
| ProductName | string | 商品名称 |
| ProductCode | string | 商品编码 |
| CategoryId | int | 分类ID,关联 mms_category |
| BrandId | long | 品牌ID,关联 mms_brand |
| Price | decimal | 最低价(由 SKU 自动计算) |
| MaxPrice | decimal | 最高价(由 SKU 自动计算) |
| OriginalPrice | decimal | 原价 |
| MainImage | string | 主图 |
| ImageUrls | string | 图片列表(JSON) |
| VideoUrl | string | 视频链接 |
| Unit | string | 单位 |
| SpecType | int | 规格类型:1=单规格, 2=多规格 |
| SpecSummary | string | 规格摘要(JSON) |
| PurchaseLimit | string | 购买限制(JSON) |
| TotalSalesVolume | int | 累计销量 |
| SaleStatus | int | 1=在售, 0=下架 |
| SortId | int | 排序 |
| Introduce | string | 商品介绍 |
| DetailsHtml | string | 商品详情(富文本) |
| IsDelete | int | 软删除标记 |
3.2 商品 SKU (Skus)
表名:mms_skus,主键:SkuId (long),关联 ProductId
主要字段
| 字段 | 类型 | 说明 |
|---|---|---|
| SkuId | long | 主键 |
| ProductId | long | 商品ID |
| Price | decimal | SKU 价格 |
| Stock | int | 库存 |
| SalesVolume | int | 销量 |
| ImageUrl | string | SKU 图片 |
| SpecCombination | string | 规格组合描述 |
| SpecIds | string | 规格ID组合 |
| Specs | string | 规格详情(JSON) |
| Weight | decimal | 重量 |
| Version | long | 乐观锁版本号,防并发超卖 |
| IsDelete | int | 软删除标记 |
Version字段用于乐观锁控制,防止并发更新库存时数据不一致。
3.3 商品规格 (ProductSpec)
表名:mms_product_spec,主键:Id (long),关联 ProductId
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键 |
| ProductId | long | 商品ID |
| Name | string | 规格名称(如"颜色"、"尺码") |
| SpecValues | string | 规格值列表(JSON List<string>) |
3.4 商品分类 (Category)
表名:mms_category,主键:CategoryId (int),树形结构(自引用 ParentId)
| 字段 | 类型 | 说明 |
|---|---|---|
| CategoryId | int | 主键 |
| Name | string | 分类名称 |
| ParentId | int | 上级分类ID |
| Icon | string | 图标 |
| Introduce | string | 分类介绍 |
| OrderNum | int | 排序 |
| ShowStatus | int | 显示状态 |
| IsDelete | int | 软删除标记 |
3.5 品牌 (Brand)
表名:mms_brand,主键:Id (long)
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键 |
| Name | string | 品牌名 |
| Logo | string | Logo |
| Description | string | 描述 |
3.6 订单 (OMSOrder)
表名:oms_order,主键:Id (long)
主要字段
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键 |
| OrderNo | string | 订单号(唯一索引) |
| UserId | long | 下单用户ID |
| TotalAmount | decimal | 商品总额 |
| PayAmount | decimal | 实付金额 |
| OrderStatus | int | 订单状态(枚举) |
| DeliveryStatus | int | 发货状态(枚举) |
| RefundStatus | int | 退款状态(枚举) |
| AddressSnapshot | string | 收货地址快照(JSON) |
| DeliveryCompany | string | 快递公司 |
| DeliveryNo | string | 快递单号 |
| OrderNote | string | 用户备注 |
| MerchantNote | string | 商家备注 |
| PayTime | datetime | 支付时间 |
| ShipTime | datetime | 发货时间 |
| ConfirmTime | datetime | 确认收货时间 |
| CancelTime | datetime | 取消时间 |
订单状态枚举
| 枚举值 | 状态名 | 说明 |
|---|---|---|
| 1 | 待付款 (Pending) | 已下单未付 |
| 2 | 待发货 (Processing) | 已付款 |
| 3 | 已发货 (Shipped) | 已发货 |
| 4 | 已完成 (Completed) | 已确认收货 |
| 5 | 取消 (Canceled) | 已取消 |
| 6 | 关闭 (Closed) | 交易关闭 |
发货状态枚举
| 枚举值 | 状态名 | 说明 |
|---|---|---|
| 1 | 未发货 (Pending) | 待发货 |
| 2 | 已发货 (Delivered) | 已发货 |
| 3 | 已送达 (Received) | 已签收 |
退款状态枚举
| 枚举值 | 状态名 | 说明 |
|---|---|---|
| 0 | 无退款 (None) | 正常 |
| 1 | 退款中 (Processing) | 处理中 |
| 2 | 已退款 (Refunded) | 已完成 |
| 3 | 退货中 (Returning) | 退货中 |
| 4 | 已退货 (Returned) | 已完成 |
3.7 订单项 (OMSOrderItem)
表名:oms_order_item,主键:ItemId (long),关联 OrderId
主要字段
| 字段 | 类型 | 说明 |
|---|---|---|
| ItemId | long | 主键 |
| OrderId | long | 订单ID |
| ProductId | long | 商品ID |
| ProductName | string | 商品名称快照(下单时记录) |
| ProductPic | string | 商品图片快照 |
| SkuId | long | SKU ID |
| SkuSpec | string | SKU 规格快照 |
| UnitPrice | decimal | 单价快照 |
| TotalPrice | decimal | 小计 |
| Quantity | int | 数量 |
订单项使用快照模式,记录下单时刻的商品名、图片、单价、规格,避免后续商品变更影响历史订单数据。
3.8 收货地址 (MMSUserAddress)
表名:mms_address,主键:Id (long)
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键 |
| UserId | long | 用户ID |
| UserName | string | 收件人 |
| Phone | string | 手机号 |
| Province | string | 省 |
| City | string | 市 |
| District | string | 区/县 |
| DetailAddress | string | 详细地址 |
| IsDefault | bool | 是否默认地址 |
3.9 规格模板 (SpecTemplate)
表名:mms_spec_template,主键:Id (long),可复用的规格模板系统
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键 |
| TemplateName | string | 模板名称 |
| SpecJson | string | 规格配置(JSON List<SpecDto>) |
四、API 接口
接口基础路径:
/shopping,Swagger 分组:shopping
4.1 品牌管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/brand/list | shop:brand:list | 品牌列表 |
| GET | /shopping/brand/{Id} | shop:brand:query | 品牌详情 |
| POST | /shopping/brand | shop:brand:add | 添加品牌 |
| PUT | /shopping/brand | shop:brand:edit | 更新品牌 |
| POST | /shopping/brand/delete/{ids} | shop:brand:delete | 删除品牌 |
| GET | /shopping/brand/export | shop:brand:export | 导出 Excel |
4.2 分类管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/category/list | shop:category:list | 分页查询(5分钟缓存) |
| GET | /shopping/category/treeList | 无 | 树形列表 |
| GET | /shopping/category/{CategoryId} | 匿名 | 查询单个分类 |
| POST | /shopping/category | shop:category:add | 添加分类 |
| PUT | /shopping/category | shop:category:edit | 更新分类 |
| DELETE | /shopping/category/{ids} | shop:category:delete | 删除分类 |
| GET | /shopping/category/ChangeSort | shop:category:edit | 修改排序 |
| GET | /shopping/category/export | shop:category:export | 导出 Excel |
4.3 商品管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/product/list | shop:product:list | 分页查询(含 SKU + 分类 + 品牌联表) |
| GET | /shopping/product/{ProductId} | 匿名 | 商品详情(含 SKU + 规格聚合) |
| POST | /shopping/product | shop:product:add | 添加商品(事务:插入商品+SKU+规格) |
| PUT | /shopping/product | shop:product:edit | 完整更新(事务:差异化更新SKU) |
| PUT | /shopping/product/edit | shop:product:edit | 部分字段更新 |
| POST | /shopping/product/delete/{ids} | shop:product:delete | 软删除 |
| POST | /shopping/product/multi/{type}/{ids} | shop:product:edit | 批量上架/下架 |
| GET | /shopping/product/ChangeSort | shop:product:edit | 修改排序 |
| GET | /shopping/product/export | shop:product:export | 导出 Excel |
4.4 SKU 管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/skus/list | shop:skus:list | SKU 列表 |
| GET | /shopping/skus/{SkuId} | shop:skus:query | SKU 详情 |
| POST | /shopping/skus | shop:skus:add | 添加 SKU |
| PUT | /shopping/skus | shop:skus:edit | 更新 SKU |
| POST | /shopping/skus/delete/{ids} | shop:skus:delete | 删除 SKU |
4.5 订单管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/order/list | oms:order:list | 订单列表(联表 OrderItem) |
| GET | /shopping/order/{Id} | oms:order:query | 订单详情 |
| PUT | /shopping/order | - | 更新订单(多操作类型) |
| POST | /shopping/order/delete/{ids} | oms:order:delete | 软删除 |
| POST | /shopping/order/delivery | - | 发货 |
| GET | /shopping/order/export | oms:order:export | 导出 Excel |
| GET | /shopping/order/exportDelivery | oms:order:ship | 导出待发货订单 |
| POST | /shopping/order/importData | oms:order:ship | 批量导入发货 |
| GET | /shopping/order/getSales | oms:sale:query | 总销售额统计 |
| GET | /shopping/order/getSalesTrade | oms:sale:query | 销售趋势(按天) |
| GET | /shopping/order/getSaleTopProduct | oms:sale:query | TOP10 热销商品 |
订单更新支持多种操作类型(operType):
| operType | 操作方法 | 说明 |
|---|---|---|
| 1 | NotDelivereOrder | 未发货订单查询 |
| 2 | UpdateMerchantNote | 更新商家备注 |
| 3 | (地址更新) | 更新收货地址 |
| 4 | (退款) | 处理退款 |
4.6 规格模板管理
| 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|
| GET | /shopping/specTemplate/list | spectpl:list | 模板列表 |
| GET | /shopping/specTemplate/tplList | - | 简易列表 |
| GET | /shopping/specTemplate/{Id} | - | 模板详情 |
| POST | /shopping/specTemplate | spectpl:add | 添加模板 |
| PUT | /shopping/specTemplate | spectpl:edit | 更新模板 |
| POST | /shopping/specTemplate/delete/{ids} | spectpl:delete | 删除模板 |
五、关键设计要点
5.1 SKU 乐观锁防超卖
Skus 表内置 Version 字段(long 类型),通过 SqlSugar 的乐观锁机制防止并发更新库存时出现超卖:
// SkusService 中的更新逻辑(伪代码)
var sku = GetInfo(skuId);
sku.Stock -= quantity;
sku.Version++; // 版本号递增
Update(sku, it => new { it.Stock, it.Version });
5.2 订单快照机制
OMSOrderItem 在下单时记录商品名、图片、单价、规格的快照值(ProductName、ProductPic、UnitPrice、SkuSpec),后续即使商品信息变更,历史订单数据不受影响。
5.3 价格由 SKU 驱动
Product.Price 和 Product.MaxPrice 并非独立存储,而是根据关联的 Skus 列表自动计算最低价和最高价:
product.Price = Math.Round(skus.Min(s => s.Price), 2);
product.MaxPrice = Math.Round(skus.Max(s => s.Price), 2);
5.4 JSON 字段存储
多字段使用数据库 JSON 列存储结构化数据,减少额外关联表:
| 字段 | 所属表 | 格式 |
|---|---|---|
| PurchaseLimit | Product | JSON(购买限制规则) |
| AddressSnapshot | OMSOrder | JSON(收货地址快照) |
| SpecValues | ProductSpec | JSON List<string> |
| Specs | Skus | JSON(规格详情) |
| SpecJson | SpecTemplate | JSON(模板配置) |
5.5 软删除
Product、OMSOrder、Skus、Category、Brand 均使用 IsDelete 字段实现软删除,数据不会真正从数据库移除。
5.6 规格模板复用
SpecTemplate 独立于 Product,可定义通用的规格模板(如"颜色/尺码"),多个商品可复用同一模板。
六、权限体系
| 权限前缀 | 作用域 | 示例权限 |
|---|---|---|
shop:brand:* | 品牌管理 | shop:brand:list/add/edit/delete |
shop:category:* | 分类管理 | shop:category:list/add/edit/delete |
shop:product:* | 商品管理 | shop:product:list/add/edit/delete |
shop:skus:* | SKU 管理 | shop:skus:list/add/edit/delete |
oms:order:* | 订单管理 | oms:order:list/query/delete |
oms:order:ship | 发货管理 | oms:order:ship |
oms:sale:* | 销售统计 | oms:sale:query |
spectpl:* | 规格模板 | spectpl:list/add/edit/delete |
七、SaaS 多租户集成
商城模块通过 MallTenantInitializer 实现 ITenantModuleInitializer 接口,在 SaaS 环境下自动为新租户初始化商城表:
// ZR.Mall/Service/MallTenantInitializer.cs
public class MallTenantInitializer : ITenantModuleInitializer
{
public string ModuleName => "Mall";
// SaaS 模式:为新租户初始化商城数据库表
public void InitializeTenant(string tenantId)
{
if (!App.IsTenantEnabled()) return;
var db = DbScoped.SugarScope.GetConnectionScope(tenantId);
InitCore(db); // CodeFirst 建表
}
// 非 SaaS 模式:开发环境初始化
public void InitializeNonSaaS()
{
if (!App.IsDevelopment() || !App.OptionsSetting.InitDb) return;
var db = DbScoped.SugarScope.GetConnectionScope(App.MallDbConfigId);
InitCore(db);
}
}
初始化时创建的 9 张表:Product → ProductSpec → Skus → Category → Brand → OMSOrder → OMSOrderItem → MMSUserAddress → SpecTemplate
