【业务编程题】订单的ID生成 文章目录题目要求解题思路1.各个字段的字符串转化2.递增顺序数字的线程安全问题3.订单-订单ID的映射最终代码题目要求在电商项目中订单号是用来跟踪和识别每一个订单的唯一标识。为了保证订单号的唯一性需要设计一个订单号生成器可以根据日期顺序数字和用户ID生成订单号。现已经有订单类(Order)如下privateDatecreateDate;//订单创建时间publicprivateStringorderSerial;//订单顺序数字privateStringuserId;//订单所属用户ID请实现一个订单号生成与查询系统。需要实现 Solution 类中的两个方法publicStringgenerateOrderNumber(Datedate,StringuserID);// 根据日期、顺序数字和用户ID生成订单号publicOrdergetOrder(StringorderNumber);// 根据订单号获取相关的订单信息返回一个Order对象订单号格式三个部分按顺序拼接共 19 位日期8 位 YYYYMMDD顺序数字 5 位每天从 00001 开始递增超过 99999 后重置用户 ID6 位英文字母和数字组成已提供的 Order 类如下classOrder{privateDatecreateDate;// 订单创建时间privateStringorderSerial;// 订单顺序数字privateStringuserID;// 订单所属用户IDpublicOrder(DatecreateDate,StringorderSerial,StringuserID){this.createDatecreateDate;this.orderSerialorderSerial;this.userIDuserID;}publicDategetCreateDate(){returncreateDate;}publicStringgetOrderSerial(){returnorderSerial;}publicStringgetUserID(){returnuserID;}}需要实现的 Solution 类publicclassSolution{publicStringgenerateOrderNumber(Datedate,StringuserID){// TODO: 实现订单号生成逻辑}publicOrdergetOrder(StringorderNumber){// TODO: 实现订单查询逻辑}}补充说明顺序数字每天从 00001 开始格式化为 5 位不足补零超过 99999 后重置userID 保证长度为 6 位由英文字母和数字组成如果获取订单存在异常就抛出 IllegalArgumentException示例如下// 输入: date 2023-11-02, userID ABC123, 输出: 2023110200001ABC123StringorderNumsolution.generateOrderNumber(date,ABC123);Orderordersolution.getOrder(orderNum);order.getCreateDate();// 注意是 Data 格式的 Wed Nov 02 00:00:00 CST 2023order.getOrderSerial();// 00001order.getUserID();// ABC123解题思路1.各个字段的字符串转化Date 转化字符串使用SimpleDateFormat方法privateSimpleDateFormatsdfnewSimpleDateFormat(yyyyMMdd);StringdateStringsdf.format(date);//日期订单顺序数字的生成应根据当天的时间进行递增同一天内进行递增递增到99999就重新循环前置 0 的补全使用String.format(%05d, seq)UserID 字段直接 get()2.递增顺序数字的线程安全问题MapString, Integer线程 A 和 B 同时 get 得到相同的值后递增修改会出现丢失更新的线程不安全问题。A get 1B get 1A put 2B put 2MapString, AtomicIntegerincrementAndGet() 是原子的方法incrementAndGet() 是原子递增的但是获取 Map 中的 AtomicInteger 实例不是原子的。A 执行 getOrDefaultMap 中无键返回新建的 aiA new AtomicInteger(0)在 put 键之前B 执行 getOrDefault同样设置为 0 递增为1后还是出现覆盖问题ConcurrentHashMapString, Integer同样的问题 get 和 put 是分开的同样可能获取到相同值再进行覆盖。ConcurrentHashMapString, AtomicInteger线程A incrementAndGet() 返回 99999内部变为 100000线程 B返回 100000内部变为 100001两个线程同时进入count99999判断内部。ConcurrentHashMapString, Integer配合compute本质上是让获取-判断-覆盖这一整个流程原子化代码如下。3.订单-订单ID的映射每次订单 ID 生成后则 new Order 创建新订单映射到MapString, Order中等到getOrder() 直接从 Map 中查询最终代码classOrder{privateDatecreateDate;//创建订单时间privateStringorderSerial;//顺序数字privateStringuserId;//用户IDpublicOrder(DatecreateDate,StringorderSerial,StringuserId){this.createDatecreateDate;this.orderSerialorderSerial;this.userIduserId;}publicStringgetOrderSerial(){returnorderSerial;}publicStringgetUserId(){returnuserId;}publicDategetCreateDate(){returncreateDate;}}classSolution{//订单号-订单映射privateMapString,OrderordernewHashMap();//日期-订单号顺序计数器每个日期/当天的订单号顺序计数递增privateConcurrentHashMapString,IntegerdailyCounternewConcurrentHashMap();//日期格式化工具类java.text.SimpleDateFormatsdfnewjava.text.SimpleDateFormat(yyyyMMdd);publicSolution(){}publicStringgenerateOrderNumber(Datedate,StringuserID){//Date类型转换为字符串String类型StringdateStringsdf.format(date);//日期//获取当前日期的订单号顺序计数器,当天内由1递增//V compute(K key, BiFunction? super K, ? super V, ? extends V remappingFunction)//Lambda 的返回值就是要放回 Map 中的新值intcountdailyCounter.compute(dateString,(k,v)-{//如果v为null,则返回1,否则返回v1intnext(vnull)?1:v1;returnnext99999?1:next;});//前位置补充0StringorderSerialStringString.format(%05d,count);//拼接StringOrderIddateStringorderSerialStringuserID;//创建新订单OrdernewOrdernewOrder(date,orderSerialString,userID);order.put(OrderId,newOrder);returnOrderId;}publicOrdergetOrder(StringorderNumber){//如果存在异常就抛出IllegalArgumentExceptionif(!order.containsKey(orderNumber)){thrownewIllegalArgumentException(Invalid order number);}returnorder.get(orderNumber);}}