资讯动态

从用例图到库存流水:超市进销存系统设计与实现

发布时间:2026/10/27 4:37:32 来源:尧图企业网站定制
简介这份超市进销存管理系统源码与设计资料面向计算机相关专业学生、课程设计者及中小超市信息化开发者围绕商品进货、销售、库存统计、订单管理等核心业务提供完整实现参考并附用例图帮助梳理员工、顾客、供应商与系统之间的交互关系。压缩包共103个文件、约2.03MB包含24个cs源码、18个aspx页面、24张jpg与22个gif界面/流程图素材以及css、bmp、png、mdl、mdf、ldf和config等配置与数据库文件兼顾前端页面、后台逻辑和数据库结构。已有4380人学习适合用作毕业设计、课程实训或小型进销存项目的原型参考。读者可从中了解采购入库、销售出库、库存预警、订单追踪等模块的组织方式借助用例图快速把握需求边界与业务流程并参考现成页面和数据库文件搭建自己的管理原型。1. 从一张用例图倒推代码超市进销存系统该怎么拆拿到一份超市进销存管理系统源码包绝大多数人的第一动作是打开 IDE 找启动类跑通登录页就以为摸清了。真正决定这套东西能不能用的是随包附带的用例图——它把收银员、库管员、采购员、店长这几类人和系统的每一次交互摊在纸面上也决定了后面表怎么建、事务边界划在哪。这份资源适合三类人课程设计要同时交文档和源码的、想拿一个完整业务闭环练手的、以及手上有个小门店系统要维护的。下面的顺序是「读图 → 建表 → 跑链路 → 排错 → 自检」每一步都能对着资源包里的文件复现不跳步。2. 读懂用例图四类参与者、十二条用例与模块清单的对齐用例图不是交作业时的装饰品它是这套系统里最便宜的一次设计评审。图上的每个椭圆最后都要落成一个 Controller 类或者一组接口图上的每条连线最后都要变成一条权限校验或者一次事务。读图的方式决定了你后面写代码是「有据可依」还是「边写边猜」。2.1 先定参与者再定权限表用例图左上角那几个小人不是随便画的。这类系统里稳定的四类参与者是收银员、库管员、采购员、店长有些资源包会再加一个「系统管理员」负责账号和参数。问题出在偷懒的画法只画一个「管理员」通吃所有用例图是清爽了代码里就会出现满屏if (user.getType() 1)的硬编码等店长提出「我要能看报表但不能改价格」的时候你得回头改五个 Controller。血泪经验是参与者数量直接对齐角色表行数一个参与者一个 role权限点挂在 role 上而不是挂在人身上。表就四张别省sys_user、sys_role、sys_user_role、sys_role_permission权限点用stock:in、sale:checkout、report:view:cost这种「模块:动作」的字符串接口层做一次注解校验就够了。-- 角色与权限点一个参与者对应一行 role权限点用字符串常量 INSERT INTO sys_role (id, role_code, role_name) VALUES (1, CASHIER, 收银员), (2, STOCKER, 库管员), (3, BUYER, 采购员), (4, MANAGER, 店长); -- 只读店长靠权限组合实现不新增角色行 INSERT INTO sys_role_permission (role_id, perm_code) VALUES (1, sale:checkout), (1, sale:return), (2, stock:in), (2, stock:check), (2, stock:view), (3, purchase:create), (3, purchase:view), (3, supplier:view), (4, report:view), (4, report:view:cost), (4, stock:view);这段 SQL 的逻辑是先把「谁能做什么」固定成数据代码里只判断权限点是否存在不判断用户类型。参数上要注意perm_code的命名必须和用例图上的用例名一一对应比如用例叫「库存盘点」权限点就是stock:check这样图、表、注解三处能对得上号。改权限只改数据不改代码这是后面加「财务只读」「临时工只能收银」这类需求时唯一的后悔药。2.2 include 与 extend 别画反用例粒度别画碎include是必须走的公共子流程extend是可选的扩展分支这两个箭头画反了代码照样能跑但会让你判断错「哪些逻辑该抽成公共服务」。判断方法很简单多条主链路都要经过的画include比如「收银结算」include「库存校验」、include「会员积分抵扣」因为退货、换货、挂单结账都要走这一段只有一个场景下才可能发生的画extend比如「限时折扣」extend「收银结算」、「整单退货」extend「销售单详情」因为这些是可插拔的策略代码里就该做成策略类或者开关而不是塞进主流程的 if 里。另一个常见毛病是用例粒度画碎。商品新增、商品改价、商品下架画成三个椭圆代码里却是同一个商品 Controller 的三个方法图上密密麻麻几十个圈看着专业实际信息量为零。判断标准就一句一个用例对应「用户想达成的一件事」映射到一个 Controller 类或者一组 REST 接口。超过这个粒度是画细了合成「商品维护」一个用例即可少于这个粒度是画粗了比如出现「管理所有数据」这种椭圆等于什么都没说。还有一类用例图里通常不画、但代码里绝不能漏的登录日志、操作日志、每日对账定时任务、报表导出。它们属于系统级用例画不画看你交不交文档但写代码时如果忘了对账任务线上库存对不上就只能靠人工翻流水。2.3 用例到模块清单的对照表拿到资源包先别急着跑按下面这张表逐个对文件目录缺哪块心里先有数比跑到一半发现没有退货逻辑强得多。用例主参与者对应模块/包名关键接口事务边界商品维护库管员modules/product/product/save、/product/off单表无事务采购入库采购员modules/purchase/purchase/inbound单据头明细库存批次流水同一事务销售收银收银员modules/sale/sale/checkout扣库存写单写流水同一事务销售退货收银员modules/sale/sale/return回滚批次写负向流水同一事务库存盘点库管员modules/stock/stock/check盘点单调整流水同一事务供应商管理采购员modules/supplier/supplier/save单表报表统计店长modules/report/report/daily只读无事务会员与积分收银员modules/member/member/points与结算同事务对完这张表你会发现一个规律凡是带「单据」两个字的用例事务边界一定横跨三张以上的表凡是「维护」类的基本都是单表操作。这个规律直接决定了第 4 章代码里Transactional该加在哪一层而不是每个 Service 方法上无脑加一遍。3. 把用例落成表结构商品、批次、库存、流水四张主表的字段取舍用例图回答「谁做什么」表结构回答「做完之后数据长什么样」。这两者之间的翻译如果偷懒后面所有链路都会跟着歪。见过太多版本把库存简化成商品表里的一个stock字段前期跑得飞起等要做临期预警、先进先出、退货回原批次的时候只能推倒重来。3.1 库存为什么不能只留一个数字只留一个剩余数量会同时丢掉三样东西。第一是批次同一款牛奶这个月进了两批一批下月到期一批明年到期收银时该先出哪批没有批次表就只能拍脑袋先进先出变成玄学。第二是成本两批进价不同退货退款按哪个价退、毛利怎么算全靠一个平均价糊弄月底盘账必翻车。第三是可追溯库存少了 3 件是被偷了、录错了、还是上次退货没回滚没有流水表就只能当黑匣子。所以这套资源包里合格的表设计一定是三件套stock存当前可用量一个商品一行查询快stock_batch存每个批次的剩余量对应先进先出和临期stock_flow存每一次变动的流水只增不改对账的唯一事实来源。三张表之间用product_id关联流水表再用batch_id落到具体批次。这套结构看着比单字段麻烦但它换来的是任何一个数字对不上都能在三分钟内定位到是哪张单、哪一次操作改的。3.2 建表脚本与字段说明-- 商品主表只放不随库存变化的静态属性 CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, sku_code VARCHAR(32) NOT NULL COMMENT 商品编码业务唯一键, name VARCHAR(64) NOT NULL, category_id BIGINT NOT NULL DEFAULT 0, unit VARCHAR(8) NOT NULL DEFAULT 件, spec VARCHAR(64) DEFAULT NULL, purchase_price DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 参考进价, sale_price DECIMAL(12,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, UNIQUE KEY uk_sku (sku_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 库存汇总表一个商品一行只放当前可用量 CREATE TABLE stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL, qty INT NOT NULL DEFAULT 0 COMMENT 可用库存, locked_qty INT NOT NULL DEFAULT 0 COMMENT 已下单未出库, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本, UNIQUE KEY uk_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 批次表先进先出与临期管理的依据 CREATE TABLE stock_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL, batch_no VARCHAR(40) NOT NULL COMMENT 入库单号行号, produce_date DATE DEFAULT NULL, expire_date DATE DEFAULT NULL, qty INT NOT NULL DEFAULT 0 COMMENT 该批次剩余量, cost_price DECIMAL(12,2) NOT NULL DEFAULT 0.00, KEY idx_product_expire (product_id, expire_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 流水表只增不改唯一键同时承担幂等职责 CREATE TABLE stock_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL, biz_type VARCHAR(16) NOT NULL COMMENT PURCHASE_IN/SALE_OUT/SALE_RETURN/CHECK_ADJUST, biz_no VARCHAR(40) NOT NULL COMMENT 业务单号, batch_id BIGINT DEFAULT NULL, change_qty INT NOT NULL COMMENT 正数入库负数出库, before_qty INT NOT NULL, after_qty INT NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_biz (biz_type, biz_no, product_id, batch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑上四张表各管一段商品表管「卖什么」库存表管「还有多少」批次表管「这批货从哪来、什么时候过期」流水表管「每一分变化是谁造成的」。参数上有几处不能改错。金额字段一律DECIMAL(12,2)不要用DOUBLE浮点累加在结算页面上差一分钱用户就会截图来问。qty用INT够用于标品如果店里卖散称糖果要改成DECIMAL(12,3)并在整个链路统一单位别一边按克一边按千克。version字段是给乐观锁备用的虽然 3.3 节的方案最终没用到它但留着不亏。stock_flow的UNIQUE KEY uk_biz是整套设计里最值钱的一行它让「同一张单重复提交」在数据库层面直接失败而不是靠代码里一层层判断。before_qty和after_qty两个冗余字段看起来浪费空间但排查问题时不用连表推算直接看流水就能还原现场。3.3 扣减库存的三种写法与并发取舍库存扣减是这类系统里唯一真正有并发的地方。两个收银台同时卖最后一件商品写法不对就会卖成 -1。常见四种写法对比写法并发安全适用场景主要问题先SELECT再UPDATE否单机演示检查与更新之间被插队超卖条件更新WHERE qty n是绝大多数场景需判断影响行数乐观锁version是冲突少、读多写少冲突高时重试开销大悲观锁FOR UPDATE是单据行数少的强一致场景锁持有时间长易死锁我一般用条件更新简单且在 MySQL 的 InnoDB 行锁下足够可靠。// Mapper一条 SQL 完成「校验 扣减」影响行数为 0 即失败 Update(UPDATE stock SET qty qty - #{num} WHERE product_id #{productId} AND qty #{num}) int deduct(Param(productId) Long productId, Param(num) int num); // Service判断影响行数不要用查询结果做二次判断 Transactional(rollbackFor Exception.class) public void deductStock(Long productId, int num) { int rows stockMapper.deduct(productId, num); if (rows 0) { // 行数为 0 有两种可能商品不存在或库存不足统一按业务异常抛出 throw new BizException(库存不足: productId productId , need num); } }这段代码的关键在WHERE qty #{num}这个条件。它把「够不够」的判断交给数据库在行锁内完成避免了应用层的检查空窗。rows 0时不要再去查一次库存表给用户提示「还剩 3 件」那次查询已经在事务外读到的可能是旧值直接抛异常让前端提示「库存不足请刷新」更准确。num参数必须在调用前确保为正数负数会变成加库存这条在退货链路里尤其要小心退货应该走独立方法而不是给deductStock传负数。4. 跑通三条核心链路采购入库、销售扣减、退货冲销的代码实现用例图上的椭圆落到代码里真正需要小心对待的只有三条链路货怎么进来、怎么出去、怎么退回来。剩下的商品维护、供应商管理都是单表 CRUD照着模板改字段就行。这一章按「先写什么、后写什么、失败了回滚到哪」的顺序拆。4.1 采购入库事务边界与幂等键入库是所有库存的正向源头。它的正确顺序是写单据头 → 写明细 → 逐行加库存 → 写批次 → 写流水。五步必须在同一个事务里任何一步失败全部回滚绝不能出现「单据写了库存没加」或者「库存加了流水没写」的半成品状态。Transactional(rollbackFor Exception.class) public Long inbound(PurchaseInDTO dto) { // 1. 单据号由前端或规则生成同时作为幂等键 String bizNo dto.getBizNo(); // 2. 写单据头uk_biz_no 唯一索引兜底重复提交 PurchaseOrder order new PurchaseOrder(); order.setBizNo(bizNo); order.setSupplierId(dto.getSupplierId()); order.setTotalAmount(calcTotal(dto.getItems())); order.setStatus(IN); purchaseOrderMapper.insert(order); // 重复提交在此抛 DuplicateKeyException // 3. 逐行处理行号拼进批次号保证批次唯一 int lineNo 1; for (PurchaseItemDTO item : dto.getItems()) { String batchNo bizNo - lineNo; // 4. 加库存用 INSERT ... ON DUPLICATE KEY 兼容首次入库 stockMapper.addQty(item.getProductId(), item.getQty()); // 5. 写批次expire_date 为空时按商品保质期推算 stockBatchMapper.insert(buildBatch(item, batchNo)); // 6. 写流水before/after 由 addQty 返回后重新查询得到 stockFlowMapper.insert(buildFlow(item, batchNo, PURCHASE_IN, bizNo)); } return order.getId(); }逻辑说明单据头必须先写因为它是幂等的锚点。biz_no在表上有唯一索引前端因为网络超时重试提交同一张单时第二次插入直接抛DuplicateKeyException事务回滚库存不会被加两遍。这比在 Service 里SELECT ... WHERE biz_no ?再判断可靠得多因为「查了没有」和「插入」之间仍然有时间窗。参数说明bizNo建议用「前缀 日期 序列」比如PI20240512001方便人工核对批次号用单号加行号拼接保证退货时能精确回滚到批次。calcTotal里逐行用BigDecimal的multiply后add最后setScale(2, RoundingMode.HALF_UP)不要在循环里先转double。stockMapper.addQty内部用INSERT INTO stock(product_id, qty) VALUES(?, ?) ON DUPLICATE KEY UPDATE qty qty VALUES(qty)这样首次入库和后续入库走同一条 SQL。4.2 销售收银先扣库存还是先落单这两个顺序在新手眼里差不多实际差别在失败成本和锁持有时间。先落单再扣库存一旦扣减失败要回滚整张单日志里会留下一堆回滚记录先扣库存再落单扣减失败直接快速失败事务里做的事最少。我一般按「先扣库存 → 按批次出库 → 写销售单 → 写流水」来。Transactional(rollbackFor Exception.class) public String checkout(CheckoutDTO dto) { String saleNo dto.getSaleNo(); for (SaleItemDTO item : dto.getItems()) { // 1. 汇总库存条件扣减失败立即抛出事务不继续 int rows stockMapper.deduct(item.getProductId(), item.getQty()); if (rows 0) { throw new BizException(库存不足: item.getProductId()); } // 2. 按到期日升序扣批次实现先进先出 int remain item.getQty(); ListStockBatch batches stockBatchMapper .listAvailable(item.getProductId()); // ORDER BY expire_date ASC for (StockBatch b : batches) { if (remain 0) break; int take Math.min(remain, b.getQty()); stockBatchMapper.deduct(b.getId(), take); stockFlowMapper.insert(buildFlow(item, b.getBatchNo(), SALE_OUT, saleNo, -take)); remain - take; } if (remain 0) { // 汇总够但批次不够说明数据已不一致必须回滚 throw new BizException(批次库存与汇总库存不一致: item.getProductId()); } } // 3. 库存全部扣成功后再写销售单金额在此统一计算 saleOrderMapper.insert(buildOrder(dto)); return saleNo; }逻辑说明remain 0那个判断是这套代码里最容易被删掉、也最不能删的一段。它保证汇总表和批次表不会走岔——只要出现「汇总说还有 10 件批次加起来只有 8 件」事务立刻回滚并报错而不是硬着头皮卖出去把脏数据留到月底。参数说明批次查询必须按expire_date ASC排序NULL值用ISNULL(expire_date), expire_date排到最后否则无保质期的商品会永远排第一先进先出就废了。saleNo同样要有唯一索引做幂等。take Math.min(remain, b.getQty())中的b.getQty()是查询时的快照真正的扣减还要靠stockBatchMapper.deduct里的WHERE qty #{take}兜底两层防护不冲突。4.3 退货冲销与每日对账脚本退货不是简单地把库存加回去它要解决「退回哪个批次、退多少钱、原单还能不能再次退」。做法是按原销售单明细逐行找流水优先回到原批次。Transactional(rollbackFor Exception.class) public void saleReturn(String saleNo, ListReturnItemDTO items) { for (ReturnItemDTO item : items) { // 1. 查原出库流水拿到批次与数量防止超退 ListStockFlow outFlows stockFlowMapper .listByBiz(SALE_OUT, saleNo, item.getProductId()); int canReturn outFlows.stream() .mapToInt(f - -f.getChangeQty()).sum() - stockFlowMapper.returnedQty(saleNo, item.getProductId()); if (item.getQty() canReturn) { throw new BizException(退货数量超出可退数量); } // 2. 原批次回滚写正数流水 stockMapper.addQty(item.getProductId(), item.getQty()); stockBatchMapper.addQty(item.getBatchId(), item.getQty()); stockFlowMapper.insert(buildFlow(item, item.getBatchNo(), SALE_RETURN, saleNo -R, item.getQty())); } }逻辑说明canReturn的计算方式是「原出库总量 − 已退总量」两个数都从流水表聚合不依赖销售单上的状态字段因为状态字段会被人工改动流水不会。退货单号用原单号加-R后缀既关联原单又不会和原流水撞唯一键。参数说明item.getBatchId()必须由前端从原单明细带过来不能让后端自己猜批次猜错会导致批次表和汇总表双双失真。stockBatchMapper.addQty同样用ON DUPLICATE KEY UPDATE保证批次行存在。跑完一天的账用下面这条 SQL 抽查一致性三方差值必须为零SELECT s.product_id, s.qty AS stock_qty, IFNULL(b.batch_qty, 0) AS batch_qty, IFNULL(f.flow_qty, 0) AS flow_qty FROM stock s LEFT JOIN (SELECT product_id, SUM(qty) batch_qty FROM stock_batch GROUP BY product_id) b ON b.product_id s.product_id LEFT JOIN (SELECT product_id, SUM(change_qty) flow_qty FROM stock_flow GROUP BY product_id) f ON f.product_id s.product_id WHERE s.qty IFNULL(b.batch_qty, 0) OR s.qty IFNULL(f.flow_qty, 0);这条 SQL 返回任何一行都说明三张表已经走岔先别急着修数据去流水表按product_id倒序看最近十条基本能定位到是哪张单漏写了批次或者漏写了流水。5. 避坑与排查用例图和代码对不上的五个现场用例图画得再漂亮代码落地时照样会冒出一堆对不上的地方。下面五条都是这类系统里反复出现的现场按「现象 → 原因 → 解决」写遇到了直接对号入座。5.1 库存类负数库存与批次不联动现象一盘点之后库存出现负数而且只有高峰期才出现。原因扣减用了「先SELECT查余量、Java 里判断够不够、再UPDATE扣减」的写法两个收银台在同一毫秒都查到余量 1都判断够都执行扣减。解决换成 3.3 节的条件更新把判断交给WHERE qty #{num}用影响行数判断成败别再做二次查询。现象二退货之后stock表的数量加回去了stock_batch的批次数量却没变先进先出开始乱序。原因退货代码只调了汇总表的加法忘了批次表。解决所有库存变动收敛到一个StockService.change(productId, batchId, delta, bizType, bizNo)入口方法内部同时改汇总、改批次、写流水三件事任何业务代码都不允许直接调 Mapper 改stock.qty。这条约束写进代码规范比写进文档有用得多。5.2 单据与金额重复提交与一分钱误差现象三同一张采购单提交两次库存加了两遍单据列表里却只有一条。原因前端防重复点击做了后端没做幂等第二次请求把第一次的INSERT变成UPDATE覆盖库存却累加了两次。解决单据号唯一索引 捕获DuplicateKeyException直接返回「已处理」不要试图在代码里用SELECT判重那个时间窗永远堵不住。现象四结算页合计和明细逐行相加差一分钱。原因金额字段用了DOUBLE或者用单价乘数量后先转double再累加。解决全链路DECIMAL(12,2)加BigDecimal乘法之后立刻setScale(2, RoundingMode.HALF_UP)落到行小计合计只加行小计不要拿总数量乘总单价。这条在一次促销活动里被验证过折扣按行摊分时四舍五入的位置差一步一万单里就有几十单对不上。5.3 权限与配置菜单挡住了接口没挡住现象五收银员登录后看不到「采购管理」菜单但用抓包工具直接调/purchase/inbound居然成功了进价成本价一览无余。原因只做了菜单可见性控制接口层没做权限校验。解决菜单控制只是体验接口层必须用注解或者拦截器再校验一次权限点同时成本价这类敏感字段在返回给收银员的 DTO 里直接不查、不填别指望前端隐藏。还有一条不算 bug 但很耗时间expire_date为NULL的商品在先进先出排序里排到了最前面导致临期商品永远出不去。解决方式在 4.2 节的参数说明里提过排序时先按ISNULL(expire_date)再按日期空值统一沉底。6. 交付前自检用用例图反查系统完整性的三个动作代码跑通不等于系统完整。交付或者上线前我习惯拿用例图当 checklist 反过来查一遍代码三个动作半小时能做完能挡掉大部分「演示时才发现」的尴尬。6.1 逐条用例走查主流程和异常流用例图上的每个椭圆至少要走两遍一遍正常流程一遍最可能出问题的异常流。正常流程验证功能在不在异常流验证事务边界对不对。用例验证动作通过标准采购入库同一biz_no提交两次第二次报「已处理」库存只加一次销售收银库存 1 件两个窗口同时结算一个成功一个提示库存不足无负数销售退货对同一单连续退两次第二次超量第二次被拦截流水出现负向或异常提示库存盘点盘亏 3 件后查流水出现CHECK_ADJUST记录before-after差值为 3报表统计用收银员账号直接调报表接口返回 403成本价字段不出现在响应里走查时有个细节容易被忽略异常流要故意制造失败比如入库时把某个product_id改成不存在的值看事务是不是整体回滚单据头有没有残留。残留单据头是最常见的半成品因为很多人把单据头写在了事务外面。6.2 跑一遍三方差值和对账上线前把 4.3 节那条三方差值 SQL 跑一次确认返回空结果。然后手工造一笔业务入库 10 件、卖出 3 件、退 1 件再跑一次stock_qty应该是 8batch_qty也应该是 8flow_qty也应该是 8。三个数任何一个对不上说明某条链路漏写了表这时候修比上线后修便宜十倍。再补一条更细的检查按批次核对-- 检查是否存在孤儿批次批次有量但汇总表没有对应商品行 SELECT b.product_id, SUM(b.qty) AS batch_qty FROM stock_batch b LEFT JOIN stock s ON s.product_id b.product_id WHERE s.id IS NULL AND b.qty 0 GROUP BY b.product_id;返回结果就是脏数据清单通常是历史导入时只灌了批次没建汇总行导致的补一条INSERT ... ON DUPLICATE KEY就能修。6.3 一个能省下无数电话的小习惯最后说个不是技术但很管用的做法交付前把「单据号规则、批次号规则、退货单号后缀」三件事写成一页纸放进资源包的说明文件里。这套系统的单号是人工核对时唯一的线索收银员报「这单有问题」的时候你第一句问的就是单号有了规则表你甚至能从单号直接看出是哪天、哪台机、第几笔不用再让对方截图。从那以后我每次拿到这类带用例图的源码包第一件事不是跑 main 方法而是把用例图上的参与者、用例、权限点抄成一张对照表再对着表把三方差值 SQL 跑一遍。图、表、代码三处对得上这套系统才算真的能交出去。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑