SpringBoot+Vue3 商城系统设计:商品·SKU·购物车·订单全流程拆解

🌐 演示地址http://ruoyioffice.com | 📦 源码1·GitHubruoyi-office | 📦 源码2·GitCoderuoyi-office | 📦 源码3·Giteeruoyi-office | 💬 微信:17156169080(备注「RuoYi Office」)

商城系统是所有电商业务的底座。它看起来只是"上架商品、加购物车、下单付款",但真正落地后会发现:一件衣服有红色 XL、蓝色 M 等十几种规格,价格库存怎么存?购物车要不要实时算优惠?同一个 SKU 只剩 1 件,两个人同时下单会不会超卖?优惠券、满减、积分要怎么叠加才不会算错?RuoYi Office 的商城模块(yudao-module-mall)用 SPU/SKU 双层模型承载商品,用一张轻量购物车表记录选购,用一条可插拔的价格计算责任链(TradePriceCalculator)统一算价,下单即原子扣减 SKU 库存防超卖,并让订单与支付单、售后单彻底解耦——构成一套可二次开发的完整商城内核。
mall-product-order-architecture.png

▲ 全景图:商品域(SPU/SKU/分类)→ 购物车(轻量选购)→ 价格责任链(秒杀·折扣·满减·券·积分·运费逐级叠加)→ 订单(下单扣库存、解耦支付单)→ 售后,全链路一张图看懂

引言:商城系统到底难在哪?

“不就是商品列表加个购物车加个下单吗?”——这是很多人对商城的第一印象。但真正做成系统,复杂度会从四个地方集中爆发:

商品的"一物多规格"怎么建模? 一件 T 恤有颜色、尺码两个维度,组合出"红色 S / 红色 M / 蓝色 XL"等多个可售单元,每个单元价格、库存、条码都不同。如果只用一张商品表,规格一多就会字段爆炸。正确做法是 SPU(标准产品单元)+ SKU(库存量单位) 双层建模。

购物车要不要实时算优惠价? 如果加购时就算秒杀、满减、券,用户每改一次数量都要重算一遍,性能和一致性都难保证。RuoYi Office 的选择是:购物车只存"买什么、买几个、选不选",营销价留到结算页统一算

优惠怎么叠加才不会乱? 秒杀价、限时折扣、会员价、满减送、优惠券、积分抵扣、运费——这些优惠有先后顺序、有互斥关系。把它们硬编码在一个大方法里,迟早改不动。需要一条可插拔、可排序的价格计算责任链

库存什么时候扣、怎么防超卖? 下单扣还是支付扣?并发下单会不会扣成负数?这两个问题直接决定商城会不会"卖出去发不出货"。

本文以 RuoYi Office 的 yudao-module-mall 模块为例,基于真实源码,完整拆解商品 SPU/SKU、购物车、价格计算责任链、订单创建与库存防超卖的设计与实现。


一、业务设计:SPU/SKU 双层商品模型

1.1 核心抽象:SPU 是"商品",SKU 是"能下单的那一件"

结论先行:SPU(Standard Product Unit,标准产品单元)描述"一款商品",SKU(Stock Keeping Unit,库存量单位)描述"这款商品的某个具体规格组合"。 用户在列表看到的是 SPU(如"纯棉圆领 T 恤"),真正加入购物车、扣减库存、计算成交价的是 SKU(如"红色 / XL")。

  • ProductSpuDO(表 product_spu):一款商品,1 个 SPU 对应 N 个 SKU;冗余下属 SKU 的最低价总库存,方便列表展示与排序。
  • ProductSkuDO(表 product_sku):通过 spu_id 关联 SPU,存放真实的 price / stock / properties(规格属性)。

spec_type 字段区分单规格(false)与多规格(true):单规格商品也有一条 SKU,只是没有规格属性,这样下单逻辑可以统一。

1.2 规格属性用 JSON 存,SKU 自带"它是什么规格"

SKU 的规格组合存在 properties 字段里,用 JacksonTypeHandler 序列化为 JSON。每个 SKU 自己就携带"我是颜色=红色、尺码=XL"这组属性:

[
  {"propertyId": 1, "propertyName": "颜色", "valueId": 10, "valueName": "红色"},
  {"propertyId": 2, "propertyName": "尺码", "valueId": 20, "valueName": "XL"}
]

下单时,订单项 TradeOrderItemDO.properties 会冗余这份 JSON——这样即使商品后续改了规格,历史订单看到的仍是"下单那一刻的规格",保证订单可追溯。

1.3 商品分类:两级树形,商品必须挂在第二级

商品分类 ProductCategoryDO(表 product_category)是 parent_id 树形结构,但有一条硬约束:层级限制为 2(CATEGORY_LEVEL = 2),商品必须挂在第二级分类下。这是电商的通用实践——一级分类做大类导航,二级分类才挂具体商品,避免分类层级无限延伸导致筛选混乱。

商品状态 ProductSpuStatusEnum 只有三态,简单清晰:

枚举 status 含义
RECYCLE -1 回收站(软删除)
DISABLE 0 下架
ENABLE 1 上架

二、系统设计:模块组成与核心决策

2.1 模块组成

商城内核由"商品 + 购物车 + 交易订单 + 售后"组成,分属 producttrade 两个子模块:

子模块 数据表 功能
商品 SPU product_spu 商品主体,冗余最低价/总库存
商品 SKU product_sku 规格单元,真实价格/库存/规格 JSON
商品分类 product_category 两级树形分类
购物车 trade_cart 用户×SKU×数量×选中
交易订单 trade_order(+item) 订单主表 + 订单项
售后单 trade_after_sale 退款/退货退款
分销佣金 trade_brokerage_* 推广分佣(可选)

2.2 核心设计决策

决策点 方案 理由
商品建模 SPU/SKU 双层 + 规格 JSON 一物多规格,价格库存独立
价格冗余 SPU 冗余最低价/总库存 列表展示快,无需聚合 SKU
购物车 只存选购,不算营销价 简单、一致,性能好
价格计算 TradePriceCalculator 责任链 + @Order 优惠可插拔、可排序、可扩展
库存扣减 下单即扣 SKU 库存 防止"下单后被别人买走"
防超卖 条件 UPDATE:stock = stock - x WHERE stock >= x 单行原子,扣不动则失败
订单与支付 解耦,trade_order.pay_order_id 关联 支付渠道可换,互不影响
订单扩展 TradeOrderHandler 生命周期钩子 库存/积分/券/分佣各司其职

三、PC 端功能实现

3.1 商品(SPU)列表

商品管理是商城后台的入口,按 SPU 展示,支持按分类、状态、名称筛选。列表里的"价格""库存"直接读 SPU 的冗余字段,无需实时聚合 SKU。
mall-product-spu-list.png

▲ 商品列表:每行是一个 SPU,价格/库存为下属 SKU 的最低价与求和;状态分上架/下架/回收站,可批量上下架

列表设计要点

  • 展示维度是 SPU:用户视角的"一款商品",点进去才看到多个 SKU 规格。
  • 价格库存是冗余值:SPU 的 price 是下属 SKU 最低价,stock 是求和,列表查询不需要 join SKU。
  • 三态管理:上架(ENABLE)/下架(DISABLE)/回收站(RECYCLE),回收站实现软删除可恢复。

3.2 商品编辑(SPU + 多 SKU 规格)

商品编辑页是商城后台最复杂的表单:先配规格项(颜色、尺码),再由规格值笛卡尔积生成 SKU 表格,逐个 SKU 填价格、库存、条码。
mall-product-spu-edit.png

▲ 商品编辑:在"价格库存"页选择单/多规格,多规格时配置规格项→生成 SKU 矩阵→逐行填价格/库存/图片;保存时校验分类为二级

设计要点

  • 单/多规格切换spec_type=false 时只有一条无属性 SKU;true 时由规格值组合生成多条 SKU。
  • 价格单位是"分"price / market_price / cost_price 均以分为单位存储,避免浮点误差。
  • SPU 冗余自动算:保存时取所有 SKU 的最低价写入 SPU price,库存求和写入 SPU stock

3.3 订单列表与详情

订单列表展示交易全貌,按订单状态(待支付/待发货/已发货/已完成/已取消)筛选,可下钻到订单详情查看订单项、收货地址、价格明细、物流信息。
mall-trade-order-list.png

▲ 订单列表:状态彩色标签区分,价格明细含商品价、优惠、运费、实付;点详情可发货、查看售后

设计要点

  • 价格明细透明:实付 = 商品总价 − 优惠券 − 积分抵扣 − 折扣 + 运费 + 调价 − 会员价,每一项都落库可追溯。
  • 订单项冗余商品快照spu_name / properties / pic_url 在下单时冗余,商品改名/改规格不影响历史订单。
  • 状态驱动操作:待发货才能发货,已发货才能确认收货,非法操作在 Service 层被拦截(详见本周《订单状态机设计》)。

四、价格计算责任链:商城最优雅的设计

4.1 一条链,把所有优惠按顺序算清楚

这是整个商城最值得借鉴的设计。 算价不是写在一个大方法里,而是定义一个 TradePriceCalculator 接口,每种优惠是一个独立实现类,用 @Order 控制执行顺序,Spring 注入成 List 后依次执行:

执行顺序(@Order 计算器 职责
8 秒杀 / 砍价 / 拼团 / 积分商城 活动专享价(互斥,取其一)
10 TradeDiscountActivityPriceCalculator 限时折扣 + 会员 VIP 价(取优惠大的)
20 TradeRewardActivityPriceCalculator 满减送
30 TradeCouponPriceCalculator 优惠券
40 TradePointUsePriceCalculator 积分抵现
50 TradeDeliveryPriceCalculator 运费
999 TradePointGiveCalculator 计算赠送积分(放最后,含运费后)

新增一种优惠玩法,只需写一个新的 Calculator 实现类、标个 @Order完全不用改原有代码——这就是责任链 + 开闭原则的威力。

4.2 价格计算入口:先校验库存,再跑责任链

TradePriceServiceImpl#calculateOrderPrice 是算价入口:先校验 SKU/SPU 存在且库存充足,再用 Helper 组装初始价格,最后让责任链逐个计算:

@Override
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
    // 1. 校验 SKU、SPU 存在且库存充足
    List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
    List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
    // 2. 组装初始价格(从 SKU 取 price/stock/properties),判定订单类型
    TradePriceCalculateRespBO resp = TradePriceCalculatorHelper
            .buildCalculateResp(calculateReqBO, spuList, skuList);
    // 3. 责任链逐个计算(秒杀→折扣→满减→券→积分→运费→赠送积分)
    priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, resp));
    // 4. 兜底:非积分订单实付必须 > 0
    if (calculateReqBO.getPointActivityId() == null
            && resp.getPrice().getPayPrice() <= 0) {
        throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
    }
    return resp;
}

4.3 优惠互斥:限时折扣 vs 会员 VIP 价,取优惠更大的

同一件商品既参加限时折扣、用户又是 VIP 会员时,不能两个优惠叠加,而是取更划算的那个。这段代码体现了"互斥优惠"的处理思路:

// TradeDiscountActivityPriceCalculator#calculate(@Order=10,仅普通订单)
Integer discountPrice = calculateActivityPrice(discountProduct, orderItem); // 限时折扣优惠额
Integer vipPrice = calculateVipPrice(level, orderItem);                     // 会员价优惠额
if (discountPrice > vipPrice) {
    orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice); // 折扣更优
} else {
    orderItem.setVipPrice(vipPrice);                                          // 会员价更优
}
// 重算该 item 应付,再汇总订单总价
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);

TradePriceCalculatorHelper 还负责把订单级的优惠券、积分按 payPrice 比例分摊到每个 SKU(dividePrice),这样发生部分退款时,能精确算出每个 SKU 该退多少钱。


五、后端核心实现:下单与库存防超卖

5.1 下单全流程:算价 → 建单 → 扣库存 → 删购物车 → 建支付单

订单创建是商城的核心事务,TradeOrderUpdateServiceImpl#createOrder 把多个步骤压在一个事务里完成:

@Transactional(rollbackFor = Exception.class)
public TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO) {
    // 1. 价格计算(跑责任链,含库存校验、选中校验)
    TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
    // 2. 构建订单主表(status=UNPAID,生成订单号 no)与订单项
    TradeOrderDO order = buildTradeOrder(userId, createReqVO, calculateRespBO);
    List<TradeOrderItemDO> orderItems = buildTradeOrderItems(order, calculateRespBO);
    // 3. 前置 Handler:扣减 SKU 库存(关键)
    tradeOrderHandlers.forEach(h -> h.beforeOrderCreate(order, orderItems));
    // 4. 落库
    tradeOrderMapper.insert(order);
    orderItems.forEach(item -> item.setOrderId(order.getId()));
    tradeOrderItemMapper.insertBatch(orderItems);
    // 5. 后置:用券/扣积分/删购物车/创建支付单
    afterCreateTradeOrder(order, orderItems, createReqVO);
    return order;
}

注意第 3 步和第 5 步——库存、优惠券、积分、购物车、支付单这些"副作用"都不是硬编码,而是通过 TradeOrderHandler 钩子分散到各个处理器,下面会讲。

5.2 库存扣减时机:下单即扣,不等支付

这是一个重要的产品决策。 RuoYi Office 选择"创建订单就扣减 SKU 库存",而不是"支付成功才扣"。原因是:如果等支付才扣,用户下单后库存还在,别人也能下单同一批库存,支付时才发现没货,体验极差。下单即扣(库存预占)能保证"下了单就一定有货",未支付超时取消时再把库存加回去。

扣减逻辑在 TradeProductSkuOrderHandler#beforeOrderCreate,把订单项数量取负数传给商品模块扣减:

// 下单:incrCount = -count(负数表示扣减)
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
    productSkuApi.updateSkuStock(
            TradeOrderConvert.INSTANCE.convertNegative(orderItems)).checkError();
}
// 取消订单时反向调用 convert(orderItems)(正数)把库存加回去

5.3 防超卖的关键:SKU 条件 UPDATE(CAS)

库存扣减如果用"先查再改",并发下必然超卖。RuoYi Office 把"判断 + 扣减"压成一条原子 SQL——UPDATE ... SET stock = stock - x WHERE id = ? AND stock >= x,扣得动返回 1,库存不足返回 0:

// ProductSkuServiceImpl#updateSkuStock(节选)
items.forEach(item -> {
    if (item.getIncrCount() > 0) {            // 入库/回滚:直接加
        productSkuMapper.updateStockIncr(item.getId(), item.getIncrCount());
    } else if (item.getIncrCount() < 0) {     // 下单扣减:条件 UPDATE
        int rows = productSkuMapper.updateStockDecr(item.getId(), item.getIncrCount());
        if (rows == 0) {                       // 影响 0 行 = 库存不足,抛异常回滚
            throw exception(SKU_STOCK_NOT_ENOUGH);
        }
    }
});
// 汇总后同步更新 SPU 冗余库存
productSpuService.updateSpuStock(spuStockIncrCounts);

updateStockDecr 的 MyBatis-Plus 写法核心是 .ge(ProductSkuDO::getStock, incrCount)——即 WHERE stock >= 扣减量,数据库行锁保证并发安全。关于库存并发的更多方案(悲观锁/乐观锁/Redis 预扣减)对比,可参考本系列《库存扣减的并发难题》

5.4 购物车:故意做得很"轻"

购物车 CartDO(表 trade_cart)只有五个核心字段:user_id / spu_id / sku_id / count / selected。加购逻辑也很简单——同一 SKU 已存在就累加数量并自动选中,否则新增:

// CartServiceImpl#addCart
CartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId());
ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count); // 校验库存
if (cart != null) {                       // 已有该 SKU → 数量累加 + 选中
    cartMapper.updateById(new CartDO().setId(cart.getId())
            .setSelected(true).setCount(cart.getCount() + count));
} else {                                  // 新增购物车项
    cartMapper.insert(new CartDO().setUserId(userId).setSelected(true)
            .setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count));
}

购物车不算营销价(源码 TODO 注释明确说明):选中、改数量都只更新这张表,真正的优惠在用户点"去结算"时才由价格责任链统一计算。这样购物车操作永远是 O(1) 的简单更新,不受营销规则变化影响。


六、RuoYi Office 的创新设计

6.1 SPU/SKU 双层 + 规格 JSON,一物多规格优雅落地

商品主体(SPU)与可售单元(SKU)分离,规格组合用 JSON 存在 SKU 上,SPU 冗余最低价和总库存。列表查 SPU 不用聚合 SKU,下单认 SKU 精确到规格,订单项再冗余规格快照——展示快、下单准、历史可追溯。

6.2 价格计算责任链,优惠玩法即插即用

把"算价"拆成一条 TradePriceCalculator 责任链,每种优惠是一个 @Order 排序的独立实现类。新增秒杀、拼团、新人券等玩法只加类不改老代码,是开闭原则在业务系统里的教科书式应用。

6.3 订单 Handler 钩子,副作用各司其职

下单/支付/取消/发货/收货等节点的副作用(扣库存、用券、扣积分、分佣、发通知)通过 TradeOrderHandler 生命周期钩子分散到 9 个处理器。核心状态流转代码保持干净,扩展业务只需实现钩子方法。

6.4 下单即扣库存 + 条件 UPDATE 防超卖

库存预占策略(下单即扣)保证"下了单就有货",配合 stock = stock - x WHERE stock >= x 的原子 SQL 防超卖,未支付超时取消自动回滚库存,兼顾体验与安全。

6.5 订单与支付单解耦,渠道随便换

交易订单 trade_order 只通过 pay_order_id 关联支付单,支付渠道(微信/支付宝/钱包)的接入、切换全在 yudao-module-pay 完成,商城侧不感知。详见本周《聚合支付中心设计》


七、数据结构

7.1 表结构:product_spu(商品主体,节选)

字段 类型 说明
id bigint 主键
name / keyword / introduction varchar 名称 / 关键字 / 卖点
category_id / brand_id bigint 分类(二级)/ 品牌
spec_type bit 规格类型:false 单规格 / true 多规格
price / market_price / cost_price int 最低价 / 市场价 / 成本价(单位:分,冗余)
stock int 总库存(下属 SKU 求和,冗余)
status int 状态(-1 回收/0 下架/1 上架)
sales_count / browse_count int 销量 / 浏览量

7.2 表结构:product_sku(库存量单位,节选)

字段 类型 说明
id bigint 主键
spu_id bigint 所属 SPU
properties json 规格属性(propertyId/Name + valueId/Name)
price / market_price / cost_price int 售价 / 市场价 / 成本价(单位:分)
stock int 真实库存(扣减发生在此)
bar_code / pic_url / weight varchar/varchar/double 条码 / 图 / 重量

7.3 表结构:trade_order(订单主表,节选)

字段 类型 说明
id / no bigint/varchar 主键 / 订单号
type / status int 订单类型 / 订单状态
user_id / pay_order_id bigint 买家 / 关联支付单
total_price / pay_price int 商品总价 / 实付(单位:分)
discount_price / coupon_price / point_price int 折扣 / 券 / 积分抵扣
delivery_price / vip_price int 运费 / 会员优惠
receiver_name / receiver_mobile / receiver_detail_address varchar 收货信息

7.4 设计要点

  • 金额单位统一为"分":所有价格字段用 Integer 存分,避免浮点误差,展示时除以 100。
  • 订单项冗余商品快照spu_name / properties / pic_url 下单时冗余,商品变更不影响历史订单。
  • 多租户:所有表基于 Yudao 多租户体系,自动按租户隔离。
  • 应付公式pay_price = total_price − coupon_price − point_price − discount_price + delivery_price + adjust_price − vip_price

八、技术亮点总结

设计要点 实现方式 价值
SPU/SKU 双层 SPU 冗余最低价/库存,SKU 存真实价/规格 JSON 一物多规格,展示快下单准
规格属性 JSON JacksonTypeHandlerproperties 规格灵活,订单可追溯
两级分类约束 CATEGORY_LEVEL=2 商品挂二级 分类清晰不滥用层级
轻量购物车 只存 user×sku×count×selected 操作 O(1),不受营销影响
价格责任链 TradePriceCalculator + @Order 优惠即插即用,开闭原则
优惠分摊 Helper dividePrice 按比例分摊 部分退款金额可精确算
下单即扣库存 beforeOrderCreate 负数扣减 下单即有货,超时回滚
条件 UPDATE 防超卖 stock = stock - x WHERE stock >= x 原子扣减,扣不动则失败
订单 Handler 钩子 9 个 TradeOrderHandler 副作用解耦,扩展不改核心
订单/支付解耦 pay_order_id 关联 渠道可换,互不影响

九、快速体验

在线演示http://ruoyioffice.com/web/(账号 admin / 密码 admin123

操作路径:商城系统 → 商品管理 / 订单管理 / 售后管理 / 营销中心

推荐体验流程

  1. 建分类:创建一级、二级商品分类(商品须挂二级)。
  2. 建商品:新建多规格商品,配置颜色/尺码规格项,生成 SKU 矩阵,逐行填价格、库存。
  3. 上架商品:把商品状态切到"上架"。
  4. 下单体验:在 App/H5 端将商品加入购物车,进入结算页观察价格明细(商品价、运费、实付)。
  5. 看库存扣减:下单后回后台看对应 SKU 库存减少。
  6. 超卖验证:把某 SKU 库存改为 1,并发下两单,观察第二单被"库存不足"拦截。
  7. 订单流转:后台对订单发货、用户确认收货,观察订单状态流转。

源码仓库

平台 地址
GitHub https://github.com/yuqing2026/ruoyi-office
GitCode https://gitcode.com/zhouzhongyan/ruoyi-office
Gitee https://gitee.com/yqzy1688/ruoyi-office

结语

商城系统的本质,是让"商品、选购、价格、订单、库存"五件事各自简单、彼此咬合。RuoYi Office 的答案是:用 SPU/SKU 双层模型承载一物多规格;用一张轻量购物车表记录选购;用一条可插拔的价格计算责任链把所有优惠按序算清;下单即原子扣减库存防超卖;最后让订单与支付、售后彻底解耦。

这套"双层商品 + 责任链算价 + 钩子解耦 + 原子扣减"的思路,不止适用于零售商城,也能迁移到票务、酒店、课程、SaaS 订阅等任何"有规格、要算价、扣库存"的交易场景。

如果你正在设计电商或交易系统,欢迎参考源码实现,也欢迎在评论区聊聊:你们的商城,优惠叠加是怎么算的?有没有被"算错价"坑过?


常见问题(FAQ)

RuoYi Office 的商城系统是开源免费的吗?

是。商城模块(yudao-module-mall)基于 RuoYi-Vue-Pro / Yudao 架构,后端 Spring Boot 3.5 + 前端 Vue3,开源可商用、无 license 限制,本地约 10 分钟即可启动体验。

SPU 和 SKU 有什么区别?

SPU(标准产品单元)是"一款商品",如"纯棉 T 恤";SKU(库存量单位)是这款商品的"某个具体规格组合",如"红色/XL"。用户看到的列表是 SPU,真正加购、扣库存、算成交价的是 SKU。一个 SPU 对应多个 SKU。

商城的优惠是怎么叠加计算的?

通过 TradePriceCalculator 价格计算责任链:秒杀/折扣/满减/优惠券/积分抵扣/运费各是一个独立计算器,用 @Order 控制执行顺序依次叠加。新增优惠玩法只需加一个计算器类,不改原有代码。

并发下单会超卖吗?

不会。库存扣减用原子 SQL —— UPDATE product_sku SET stock = stock - x WHERE id = ? AND stock >= x,扣得动返回 1、库存不足返回 0,Service 层按影响行数判断并回滚事务。且采用"下单即扣库存"策略,保证下单即有货。

库存是下单扣还是支付扣?

RuoYi Office 采用下单即扣(库存预占)。下单创建时就扣减 SKU 库存,保证"下了单就一定有货";若用户超时未支付,定时任务取消订单时再把库存加回去。


💡 想要体验 RuoYi Office 的强大功能?

🌐 在线演示http://ruoyioffice.com/web/(账号 admin / admin123)

📦 源码仓库GitHub | GitCode | Gitee

💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐