SpringBoot电商平台实战:订单状态机与库存扣减设计
简介基于SpringBoot的电商平台项目面向计算机相关专业毕业设计、课程设计与Vue期末大作业场景适合需要快速搭建前后端分离电商系统的开发者也可作为入门级企业电商项目范本。项目整合Spring Data JPA、Spring Security与Vue.js覆盖商品、用户、订单等核心业务模块并包含Maven多环境配置、日志与性能优化处理。压缩包共81个文件以jsp页面、Java源码、SQL脚本、properties配置、XML配置为主另有Eclipse项目文件和前端js/css资源其中SQL脚本用于初始化数据表配置文件支撑后端环境整体仅1.27MB便于下载与本地部署。已有52人学习适合据此理解SpringBoot自动配置、RESTful接口设计及电商业务实现路径。内容包含初始化数据库脚本、构建配置文件、部署说明以及前后端分离的目录结构读者可对照源码熟悉订单与商品模块的实现细节从中获得一套可运行、可扩展的电商平台基础工程与排错参考。1. 拿到这个标题先想清楚它到底要你做什么如果你正对着“基于SpringBoot的电商平台”这个项目标题准备开题或者开始搭代码我猜你手上拿到的大概率是一个毕设题目、课程设计大纲或者公司内网某份被翻烂了的参考文档。我的建议是先别急着找现成代码包因为这一类项目表面看是商品展示加购物车下单实际上真正决定你能不能过评审、能不能上线跑稳的是订单状态流转、库存扣减和支付回调这三个深水区。这套东西我前后搭过三版最早的版本连购物车都没做商品表一张表打天下结果订单和库存对不上对账时直接翻车。后来按一套固定的结构重新拆才把复杂度压住。这篇文章我就按SpringBoot电商平台这个标题把一个单体项目从表结构到订单状态机完整拆给你包含可以直接抄的代码、每一条SQL的设计理由以及我踩过的那些坑。适合三类人正在做毕设的学生、刚接手电商后端的新人、以及想快速搭一个演示系统的开发者。2. 数据库设计与工程骨架先把地基夯死2.1 商品和订单的三张核心表DDL 直接抄电商平台不管前端页面怎么花哨后端落到数据库里有几个实体是绕不开的用户、商品、订单。我见过很多项目把商品属性和库存全塞在一张表里SKU库存量单位和SPU标准化产品单元完全不分当时能用等加上规格和促销就难受了。我一般会拆成商品表、商品规格表、订单表三张核心表先不碰用户表和营销表把主链路先跑通。下面这份DDL是我常用的最小可用版本去掉了营销、优惠券、物流等周边字段集中解决“卖什么、以什么价格卖、卖出后怎么记”这一条主链路。-- 商品表一个商品对应一条记录是商品的公共属性 CREATE TABLE goods ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 商品ID, goods_sn varchar(32) NOT NULL COMMENT 商品编号对外展示用, title varchar(120) NOT NULL COMMENT 商品标题, category_id bigint(20) DEFAULT NULL COMMENT 分类ID, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1上架 0下架, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_goods_sn (goods_sn), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 商品规格表同一个商品可以有多条规格价格和库存挂在规格上 CREATE TABLE goods_sku ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT SKU ID, goods_id bigint(20) NOT NULL COMMENT 商品ID, sku_code varchar(64) NOT NULL COMMENT 规格编码比如颜色-版本, price decimal(10,2) NOT NULL COMMENT 售价, original_price decimal(10,2) DEFAULT NULL COMMENT 原价仅展示用, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存数量, sales int(11) NOT NULL DEFAULT 0 COMMENT 销量冗余字段展示用, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1启用 0停用, PRIMARY KEY (id), KEY idx_goods_id (goods_id), UNIQUE KEY uk_sku_code (sku_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品规格表; -- 订单表一个订单包含一个收货人和一组商品快照 CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 订单ID, order_sn varchar(32) NOT NULL COMMENT 订单号业务维度唯一, user_id bigint(20) NOT NULL COMMENT 下单用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额预留优惠抵扣位, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0待支付 1已支付 2已发货 3已完成 4已取消 5已退款, receiver_name varchar(30) NOT NULL COMMENT 收货人姓名, receiver_phone varchar(20) NOT NULL COMMENT 收货人电话, receiver_address varchar(200) NOT NULL COMMENT 收货地址, remark varchar(200) DEFAULT NULL COMMENT 订单备注, pay_time datetime DEFAULT NULL COMMENT 支付时间, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_sn (order_sn), KEY idx_user_id (user_id), KEY idx_status_create (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;三个设计点提醒你注意。第一价格和小数相关的字段一律用decimal(10,2)不要用double或者float否则金额对账会出现玄学误差。第二订单表里存的是商品快照字段比如收货人、商品标题、价格而不是下单后实时去查商品表这样做是为了防止商品下架或改名之后订单数据跟着变这是电商订单表的一个基本原则。第三订单号独立于ID单独建唯一索引对外查询、对账、客服沟通都用order_sn自增ID只做内部关联用。2.2 分层结构controller、service、mapper 之间别跳层工程结构方面SpringBoot电商平台最常见的骨架是四层controller 接参数、service 写业务、mapper 操作数据库、domain 放实体。很多人为了省事直接在 controller 里写查询逻辑当时觉得代码少后面每加一个接口都要翻一遍维护成本直线上升。我习惯用下面这套包结构com.example.mall ├── controller // HTTP 接口层只做参数接收和结果包装 ├── service // 业务层事务边界都在这层 ├── mapper // 数据访问层对应 MyBatis 的 Mapper 接口 ├── domain // 实体类、枚举、DTO ├── config // 配置类比如拦截器、跨域、线程池 ├── common // 统一响应、异常处理、工具类 └── MallApplication.java我在服务层单独划了一个service接口加实现类的结构Controller 只依赖接口。这不算过度设计因为电商的下单流程涉及库存、订单、支付回调等多个数据操作接口隔离能让你在不改调用方的前提下替换实现测试时也方便Mock。2.3 统一响应体与全局异常越早做越省事接口返回值如果不统一前端联调时每个接口都要单独适配错误提示也没法做全局拦截。这个项目里我定义了一个ResultT类所有接口都返回这个结构code 表示业务状态码data 放业务数据message 放提示信息。配合RestControllerAdvice做全局异常处理业务代码里只需要抛业务异常HTTP层会自动包装成统一的JSON结构返回。public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(0); r.setMessage(ok); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(-1); r.setMessage(message); return r; } // getter/setter 省略 }这段代码没什么玄机核心是用静态工厂方法统一创建成功和失败的结果避免每个Controller里都手动堆对象。code字段我建议保留0作为成功非0全部视为失败前端判断逻辑只要写if(res.code 0)就够了。等到后面接支付回调、对接管理后台时这套统一结构能省下大量联调时间。3. 登录与鉴权用 JWT 把会话状态从服务端请走3.1 为什么电商单体项目更推荐 JWT 而不是 Session很多第一次做电商项目的人会直接用 Session 保存登录态但在没有专门会话服务的情况下Session 有两点不方便一是服务端要维护会话存储项目多实例部署时得额外引入共享存储二是前后端分离时 Session 的 Cookie 机制和跨域配置经常打架。JWTJSON Web Token把用户标识和过期时间放在令牌里服务端不存状态校验时只验签名和有效期这在单体电商项目里是更轻量的做法。要注意的是JWT 本身不能主动失效——你没法在服务端直接把某个用户的令牌作废所以它的过期时间不能设置太长。我的习惯是登录令牌有效期设为2小时同时要求前端在令牌过期前刷新配合一个活跃度过滤。管理后台的令牌可以单独放宽到12小时但要加上操作日志这一点后面在避坑章节会细说。3.2 登录接口与 JWT 签发逻辑40 行代码跑通先看登录接口。为了保持示例简洁这里省去了验证码和密码加密的部分生产环境密码一定要用BCrypt加密不要用MD5加盐这种已经不被推荐的做法。Service public class AuthService { private final UserMapper userMapper; private final TokenManager tokenManager; public AuthService(UserMapper userMapper, TokenManager tokenManager) { this.userMapper userMapper; this.tokenManager tokenManager; } public String login(String username, String password) { User user userMapper.selectByUsername(username); if (user null) { throw new BizException(用户不存在); } if (!passwordEncoder.matches(password, user.getPassword())) { throw new BizException(密码错误); } if (user.getStatus() ! 1) { throw new BizException(账号已被禁用); } // 签发令牌传入用户ID和角色 return tokenManager.createToken(user.getId(), user.getRole()); } } Component public class TokenManager { private final SecretKey key; public TokenManager() { this.key Keys.hmacShaKeyFor(your-secret-key.getBytes(StandardCharsets.UTF_8)); } public String createToken(Long userId, String role) { Date now new Date(); Date expiry new Date(now.getTime() 2 * 60 * 60 * 1000); return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(now) .setExpiration(expiry) .signWith(key, SignatureAlgorithm.HS256) .compact(); } public Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } }这段代码里有两个关键参数要说明。第一个是密钥上面代码里的your-secret-key只是示例实际项目必须放在配置文件或环境变量里长度至少32字节否则HS256算法会报错。第二个是过期时间2小时时间单位是毫秒2 * 60 * 60 * 1000就是7200000毫秒。如果业务需要用户长时间免登录可以把过期时间放到7天但一定要配合操作敏感操作时二次校验密码。3.3 拦截器配置哪些接口放行哪些必须带令牌登录校验我一般用Spring的HandlerInterceptor实现而不是在每个Controller方法里手写判断。下面这段配置直接决定了接口的访问策略。Configuration public class WebConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; public WebConfig(AuthInterceptor authInterceptor) { this.authInterceptor authInterceptor; } Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) .excludePathPatterns( /api/auth/login, /api/goods/list, /api/goods/detail/** ); } }拦截器的核心要点如果用addPathPatterns(/api/**)拦截全部业务接口那么注册、登录、商品分页这种不需要登录就能访问的接口必须显式放进excludePathPatterns里否则前端一进来就被拦。另外一个典型坑是静态资源的放行如果/api/**不包含静态资源路径通常问题不大但如果把拦截范围写成了/**就必须把/static/**、/templates/**、/error都排除掉。拦截器里拿到用户信息后用request.setAttribute(userId, userId)的方式往下传Controller才能从请求对象里安全的拿到当前登录人。不要图省事把用户信息放到ThreadLocal里然后忘了清理在线程池场景下这是经典的内存泄漏来源。4. 商品查询与购物车把“加购扣库存“这个核心链路先跑通4.1 商品分页查询where 条件要防注入排序要防慢查询商品列表是电商平台的流量入口这个接口几乎所有页面都会调。最常见的实现方式是分页加条件筛选前端传page、size、categoryId、keyword后端返回商品列表和总数。这里值得注意的不只是MyBatis的if动态SQL更关键的是排序和索引的匹配。select idselectGoodsPage resultTypecom.example.mall.domain.Goods SELECT g.id, g.goods_sn, g.title, g.category_id, g.status, MIN(s.price) AS min_price FROM goods g INNER JOIN goods_sku s ON g.id s.goods_id where g.status 1 if testcategoryId ! null AND g.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND g.title LIKE CONCAT(%, #{keyword}, %) /if /where GROUP BY g.id, g.goods_sn, g.title, g.category_id, g.status ORDER BY g.id DESC LIMIT #{offset}, #{size} /selectLIKE查询走不上索引这个SQL里g.title LIKE CONCAT(%, #{keyword}, %)是最容易造成慢查询的地方。数据量在几万条以内问题不大超过十万条之后这段条件查询的响应时间会明显爬升。我建议如果项目只是演示用保持这个写法没问题如果要去生产环境就接上Elasticsearch做商品搜索MySQL这条路就只扛基础的分类筛选。另外要注意GROUP BY g.id, ...这行因为子查询里没有其它聚合列所以分组后取MIN(s.price)是安全的写法。MySQL默认开启了ONLY_FULL_GROUP_BY模式如果只GROUP BY g.id而select里还带着g.title高版本MySQL会直接报错这一点在SQL编写时就要留意。4.2 加入购物车库存校验在写库前做一次提交订单时再做一次购物车本身没有太深的技术含量核心就是一个cart_item表记录用户、SKU和数量。我把它放到和订单同一个数据库里不单独建库因为在单体项目里业务量还远没到需要拆库的程度。public void addToCart(Long userId, Long skuId, Integer num) { if (num null || num 0 || num 99) { throw new BizException(购买数量必须在1到99之间); } SkuStockDTO sku skuMapper.selectBySkuId(skuId); if (sku null || sku.getStatus() ! 1) { throw new BizException(商品规格不存在或已下架); } if (sku.getStock() num) { throw new BizException(库存不足); } CartItem existing cartMapper.selectByUserAndSku(userId, skuId); if (existing ! null) { cartMapper.increaseNum(userId, skuId, num); } else { CartItem item new CartItem(); item.setUserId(userId); item.setSkuId(skuId); item.setNum(num); item.setChecked(true); cartMapper.insert(item); } }这段逻辑是标准的“先查后写”注意这只是把商品放进购物车不涉及真正的库存扣减所以这里的库存判断只需要给出友好提示不用为了并发去加锁。真正的并发控制发生在提交订单那一刻下面第三节讲的扣减库存SQL才是重头戏。4.3 扣减库存的正确姿势把“先查后改”换成“条件更新”这是整个电商后端里最容易出问题的代码。如果按直觉写“先select查库存再update减库存”在高并发下两个请求同时读到库存为1然后都做了减1操作最终数据库里库存变成-1这就是超卖。SpringBoot单体项目里解决超卖最常见也最可靠的做法是让数据库自己来判断库存是否够用UPDATE goods_sku SET stock stock - #{num} WHERE sku_id #{skuId} AND stock #{num}这条SQL的精髓在于把“查询库存是否足够”和“扣减库存”两个动作合并成一个原子操作。stock #{num}是数据库层面的条件判断影响行数为0就说明库存不足这一步执行成功后库存已经减掉了不需要再select一次。配合方法上的Transactional就能保证同一事务范围内订单创建和库存扣减要么都成功要么都回滚。我见过有人在这条SQL前面加SELECT ... FOR UPDATE把SKU行锁住再扣减。在单体项目里这个做法也能解决超卖但代价是并发能力下降因为同一SKU的扣减请求被串行化了。用上述条件更新则不同InnoDB的行锁只用在真正更新的那一瞬间吞吐量更高。至于Redis预扣库存那一套是千万级流量的玩法这个项目用不上别提前上复杂度。5. 订单与支付回调状态机设计和这 5 个必踩坑5.1 订单状态机用枚举限定流转方向拒绝 if else 乱跳订单状态是整个电商项目里业务规则最密集的地方。我建议先把状态流转画清楚再用代码实现。最简单可靠的版本是待支付0可以变成已支付1、已取消4已支付1可以变成已发货2、已退款5、已完成3已发货2只能变成已完成3。所有非法流转比如从待支付直接跳已完成直接抛异常。public enum OrderStatusEnum { UNPAID(0, 待支付), PAID(1, 已支付), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELLED(4, 已取消), REFUNDED(5, 已退款); private final int code; private final String desc; OrderStatusEnum(int code, String desc) { this.code code; this.desc desc; } public boolean canChangeTo(OrderStatusEnum target) { switch (this) { case UNPAID: return target PAID || target CANCELLED; case PAID: return target SHIPPED || target COMPLETED || target REFUNDED; case SHIPPED: return target COMPLETED; default: return false; } } public int getCode() { return code; } }有了这个枚举订单状态更新就变成一句安全的带乐观锁条件的SQLUPDATE orders SET status #{target} WHERE order_sn #{orderSn} AND status #{expect}。这里status #{expect}这个条件的价值在于即使两个请求同时对同一订单做了状态变更数据库也会只让其中一个成功另一个影响行数为0事务回滚。用代码去管理状态流转范围再用SQL去兜底并发双保险。5.2 支付回调接口幂等设计比信任前端通知更可靠在毕设或演示项目里无法对接真实的支付渠道我一般会自己写一个模拟的支付回调HTTP接口方便前后端联调和演示。这个模拟回调会接收订单号、支付金额、支付流水号三个参数处理逻辑和真实回调保持一致。PostMapping(/mock/pay/notify) public ResultString payNotify(RequestBody PayNotifyDTO dto) { // 1. 校验订单是否存在 Order order orderMapper.selectByOrderSn(dto.getOrderSn()); if (order null) { throw new BizException(订单不存在); } // 2. 幂等判断订单已经是已支付状态直接返回成功 if (order.getStatus() OrderStatusEnum.PAID.getCode()) { return Result.ok(重复通知已处理); } // 3. 校验支付金额和订单应付金额一致 if (order.getPayAmount().compareTo(dto.getPayAmount()) ! 0) { throw new BizException(支付金额与订单金额不匹配); } // 4. 更新订单为已支付 int rows orderMapper.updateStatusByOrderSn(dto.getOrderSn(), OrderStatusEnum.PAID.getCode(), OrderStatusEnum.UNPAID.getCode()); if (rows 0) { throw new BizException(订单状态更新失败); } return Result.ok(支付成功); }这段代码最重要的不是更新订单而是第二步的幂等判断。真实场景中支付平台的通知可能重复发送本地代码也可能在超时后重试如果没有这个判断同一个订单会被处理两次库存被扣两次对账就全乱了。幂等判断的可靠做法是先查状态再按UNPAID - PAID的条件更新这样即使两个通知同时到达数据库层面也只会成功一个。5.3 避坑单测靠运气发现的 5 个常见问题以下是这个项目从开发到联调过程中最有代表性的 5 个坑每个都是先出现现象再查根因最后才定位到问题点。坑一支付金额对不上浮点类型惹的祸。现象是商品单价9.9订单总金额计算出来却是9.899999。原因是订单金额在计算时用了double类型的局部变量浮点运算的精度问题在金额这种连续累加的场景下必然露馅。解决方式很直接金额一律用BigDecimal运算或者干脆用整数分作为金额单位前端展示时再转元。这个坑属于典型的“看着跑通了一算钱就出事”。坑二商品详情接口可以查到别人的订单。现象是A用户用浏览器手动访问/api/order/detail/10001居然能查到B用户的订单信息。原因是为了省事订单查询接口只按订单ID查询完全没有校验订单归属。解决方式是所有订单详情查询都要带上userId条件WHERE order_sn ? AND user_id ?不满足就直接返回“订单不存在”。这一步不是可选项是安全底线。坑三取消订单后库存没有归还。现象是用户下单锁了库存然后主动取消订单再去查商品库存发现数量没变。原因是当初只在创建订单时做了库存扣减没有做取消订单的库存回补逻辑。解决方式是在订单状态更新到已取消时同一个事务内执行库存返还UPDATE goods_sku SET stock stock #{num} WHERE sku_id #{skuId}。注意返还库存和更新订单状态必须在同一个Transactional方法里否则中途报错会出现状态改了库存没回的脏数据。坑四支付回调里更新订单用了“先查再改”导致状态覆盖。现象是支付回调处理期间用户主动取消了订单结果支付回调还是把订单更新成了已支付。原因是回调逻辑先查了订单状态然后不管当前状态直接把状态字段更新为目标值。解决方式就是前面代码里展示的乐观锁写法更新SQL强制带上当前期望状态影响行数为0就说明状态已经变化要重新处理。坑五启动项目时提示“无效的绑定字段”或者接口直接400。现象是请求参数总是解析失败或者运行时报Failed to convert property value of type java.lang.String to required type java.lang.Integer。原因多数是DTO里出现了JSON格式错误、前端传了空字符串而后端用了Integer接收或者对象名的setter写法不规范。解决方式是统一用RequestBody接收JSON所有接口参数都定义为DTO而不是散装参数前端联调时开浏览器DevTools看Network的Payload是否符合约定。这个坑不涉及高深技术但遇到的频率最高。6. 打包部署与验收让项目能跑起来还不够要能稳定演示6.1 跳过测试打包并用 nohup 启动演示前最重要的命令项目开发完离真正能演示还差一步打包、启动、冒烟。我最推荐的方式是用Maven打成可执行Jar包然后用nohup脱离终端运行这样即使SSH断开服务也不会跟着断。# 跳过测试把项目打成可执行Jar包 mvn clean package -DskipTests # 后台启动日志输出到指定文件 nohup java -jar target/demo-mall-0.0.1-SNAPSHOT.jar --server.port8080 logs/app.log 21 # 确认端口在监听 ss -tlnp | grep 8080-DskipTests的意思是跳过单元测试直接打包在赶演示版本时可以加速编译如果团队有强制测试要求应该用-Dmaven.test.skiptrue它连测试代码都不会编译。21把标准错误重定向到标准输出保证异常栈也会写进日志文件。排错时最常用的命令是tail -f logs/app.log看到滚动日志基本说明启动正常。如果你是在某个集成开发环境里直接点绿色三角运行的那只适合开发和断点调试演示时弹窗或接口地址不稳定会很尴尬。提前跑通这个打包命令能给自己留出充足时间处理环境问题。6.2 用一份冒烟测试清单代替“觉得没问题”演示翻车大多不是功能缺失而是某个接口在特定参数下没验证过。我习惯在答辩或演示前用命令行工具按下面这份清单把接口全过一遍。如果项目里没有现成的API调试工具环境用curl就够。# 1. 登录获取令牌 curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:demo,password:123456} # 2. 带令牌查询商品列表 curl http://localhost:8080/api/goods/list?page1\size10 \ -H Authorization: Bearer 上面返回的token # 3. 创建订单先确认skuId和库存数量 curl -X POST http://localhost:8080/api/order/create \ -H Content-Type: application/json \ -H Authorization: Bearer 上面的token \ -d {skuId:1,num:2,receiverName:张三,receiverPhone:13800000000,receiverAddress:某市某区某路1号}第一轮按照正常流程跑通第二轮马上测试异常场景库存不足时下单看是否返回友好错误重复请求同一个支付回调看幂等是否生效把token改成一段乱码看是否被拦截器拒绝。一个稳定可靠的电商演示不是“正常流程能走通”而是“关键异常都有提示不会直接抛500”。6.3 给订单号加一个全局唯一前缀一个受用于整个项目的细节这个技巧是我在第三次做电商项目时才养成的习惯每个订单号在前缀里直接体现业务和日期比如MALL202409081213456789。它有两层意义。第一层是排查问题方便客服报来一个订单号不用查数据库就能看出是哪天下单、哪个渠道产生的第二层是避免多套环境的数据混淆如果同时跑本地环境和测试环境前缀能立刻区分订单来源。public static String generateOrderSn() { return MALL DateTimeFormatter.ofPattern(yyyyMMddHHmmss).format(LocalDateTime.now()) RandomUtil.randomNumbers(6); }前缀拼时间再加六位随机数每天并发量有限时几乎不会重复。有人会担心随机数撞车所以订单表里还是保留了uk_order_sn唯一索引真撞了数据库会报错重试一次即可。这个做法不依赖任何中间件却能在查日志、对账、客服沟通时省下大量时间。我在每个电商项目的订单服务里都会保留这个习惯希望你用上后也能体会到它的价值。今天的分享就到这里希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Rootly 都废止“小 PR 规则“了:AI 时代,代码评审流程该整个重写

Rootly 都废止“小 PR 规则“了:AI 时代,代码评审流程该整个重写

Rootly 都废止"小 PR 规则"了:AI 时代,代码评审流程该整个重写 【免费下载链接】open-code-review Secure, fast, efficient, battle-tested at Alibabas scale. Hybrid architecture code review tool: deterministic pipelines LLM Agent, …

2026/10/11 12:52:38 阅读更多 →
解决macOS“无法检查恶意软件”提示:Gatekeeper原理与安全放行指南

解决macOS“无法检查恶意软件”提示:Gatekeeper原理与安全放行指南

碰到这类问题的朋友应该不少:好不容易从官网或者某个技术社区下载了一款工具,双击一启动,macOS 直接甩出一行冷冰冰的提示——无法打开“某App”,因为Apple无法检查其是否包含恶意软件。我第一次遇到它是在帮同事处理一台旧 MacBo…

2026/10/11 12:51:38 阅读更多 →
如何为文档站托管MCP服务器:Blume让Claude Code与Cursor直接检索你的文档

如何为文档站托管MCP服务器:Blume让Claude Code与Cursor直接检索你的文档

【免费下载链接】blume The open-source docs framework for humans and agents. 项目地址: https://gitcode.com/gh_mirrors/blum/blume 点击查看 免费下载 Blume 是一个开源文档框架(The open-source docs framework for humans and agents&#xff0…

2026/10/11 12:51:38 阅读更多 →

最新新闻

Flutter跨端迁移OpenHarmony实战:分类浏览模块开发与适配要点

Flutter跨端迁移OpenHarmony实战:分类浏览模块开发与适配要点

前阵子一个做智能硬件的老朋友找我,说手头有个用 Flutter 写的微动漫聚合 Demo,里面分类浏览、卡片列表、详情跳转都齐了,想整个搬到 OpenHarmony 开发板上跑一跑。当时我下意识觉得这事不难——Flutter 本身就是跨端的,换个平台无…

2026/10/11 13:39:03 阅读更多 →
石器时代手游双端源码编译与打包全流程解析

石器时代手游双端源码编译与打包全流程解析

简介:StoneAgeMobileApp是一份面向安卓与iOS双平台的移动端游戏源码,适合移动开发者和开源爱好者研究跨平台应用结构。既能帮助入门者理解App整体架构,也能为进阶开发者提供模块级实现细节。项目围绕‘石器时代’主题,覆盖Java/Ko…

2026/10/11 13:39:03 阅读更多 →
Qwen-Image LoRA微调实战:从原理到参数避坑指南

Qwen-Image LoRA微调实战:从原理到参数避坑指南

简介:面向阿里Qwen-Image(20B)的LoRA训练项目代码包,适合具备多模态模型基础、希望快速完成资源微调与效果优化的开发者。代码与文档围绕三层融合架构(视觉编码器、文本编码器、多模态融合器)和中文优化核心…

2026/10/11 13:39:03 阅读更多 →
zcf 测试体系深度解析:基于 Vitest 的分层测试架构、Mock 策略与 80%+ 覆盖率实践

zcf 测试体系深度解析:基于 Vitest 的分层测试架构、Mock 策略与 80%+ 覆盖率实践

开发工具CLIAI 应用 【免费下载链接】zcf Zero-Config Code Flow for Claude code & Codex 项目地址: https://gitcode.com/gh_mirrors/zc/zcf 点击查看 免费下载 zcf(Zero-Config Code Flow)是一个为 Claude Code 与 Codex 提供一键式配…

2026/10/11 13:39:03 阅读更多 →
从模糊需求到可运行模块:以rea为例的实时数据处理与展示实战

从模糊需求到可运行模块:以rea为例的实时数据处理与展示实战

1. 从“rea”这个标题说起:一个被低估的通用缩写第一次看到“rea”这个标题,很多人会愣一下——三个字母,没有上下文,没有说明,像是谁不小心在键盘上滚了一下。但如果你在技术社区、设计圈或者项目管理群里待过一段时间…

2026/10/11 13:39:03 阅读更多 →
Go后端RESTful API开发最佳实践:从项目布局到性能调优

Go后端RESTful API开发最佳实践:从项目布局到性能调优

做了快五年Go后端,从单体Web服务到微服务都碰过,踩过的RESTful API设计坑能装一箩筐。很多团队把项目搭起来容易,真正写起来才发现边界模糊:handler里塞了一堆业务逻辑,错误处理散落在各处,校验报错格式不统…

2026/10/11 13:38:03 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →