电子商务订单号生成策略

一般采用: 时间+序列号

20111001+000001 = 20111001000001
规则 : 前面以年月日前缀+序列号每天从1天始 第二天又是从1开始

代码:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.sql.DataSource;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
import org.springframework.batch.support.DatabaseType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;

public class DailyRollingIncrementer implements InitializingBean {

/**
* define bean as :
* <bean id="dailyRollingIncrementer" class="com.book511.incrementer.DailyRollingIncrementer">
* <property name="dataSource" ref="dataSource"/>
* <property name="incrementerName" value="TEST_SEQ"/>
* </bean>
*
* and run sql in MySQL as :
* CREATE TABLE TEST_SEQ (ID BIGINT NOT NULL) ENGINE=MYISAM;
* INSERT INTO TEST_SEQ(id) select 0 from DUAL where not exists (select id from TEST_SEQ);
* CREATE TABLE TEST_SEQ_ROLLING (`YMD` CHAR(8) NOT NULL , `SEQ_NUMBER` BIGINT NULL , PRIMARY KEY (`YMD`) ) ENGINE = MyISAM;
*
*/
private String incrementerName;

private DataSource dataSource;

private DefaultDataFieldMaxValueIncrementerFactory maxValueIncrementerFactory;

private DataFieldMaxValueIncrementer maxValueIncrementer;

private String rollingTable;


private String selectSql;

private String insertSql;

private String currentYmd = null;

private Long currentSeq = null;

private DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYYMMdd");

/**
* Caution !!!
* you should check if return value is not 0
* @return
*/
public long nextLongValue() {
DateTime dt = new DateTime();
String ymd = formatter.print(dt);
return nextLongValue(ymd);
}


public synchronized long nextLongValue(String ymd) {
Long nextSeq = 0L;
try {
nextSeq = maxValueIncrementer.nextLongValue();
System.out.println("nextSeq is : " + nextSeq);
} catch (Exception e) {
return 0L;
}
long number = seqNumber(ymd);
if (number == 0) {
number = nextSeq;
insertSeqNumber(ymd, number);
/*try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}*/
}
return nextSeq - number + 1;
}

private synchronized long seqNumber(String ymd) {
if (ymd != null) {
if (ymd.equals(currentYmd))
return currentSeq;
}
Connection con = null;
PreparedStatement selectPreparedStatement = null;
try {
con = DataSourceUtils.getConnection(getDataSource());
selectPreparedStatement = con.prepareStatement(selectSql);
DataSourceUtils.applyTransactionTimeout(selectPreparedStatement,
getDataSource());
selectPreparedStatement.setString(1, ymd);
ResultSet rs = selectPreparedStatement.executeQuery();
if (rs.next()) {
long seq = (long) rs.getLong(1);
currentYmd = ymd;
currentSeq = seq;
return seq;
}
} catch (Exception ex) {
// throw new DataAccessResourceFailureException(
// "Error ...", ex);
} finally {
JdbcUtils.closeStatement(selectPreparedStatement);
DataSourceUtils.releaseConnection(con, getDataSource());
}
return 0;
}

private synchronized boolean insertSeqNumber(String ymd, long seqNumber) {
Connection con = null;
PreparedStatement selectPreparedStatement = null;
try {
con = DataSourceUtils.getConnection(getDataSource());
PreparedStatement insertPreparedStatement = con
.prepareStatement(insertSql);
insertPreparedStatement.setString(1, ymd);
insertPreparedStatement.setLong(2, seqNumber);
int count = insertPreparedStatement.executeUpdate();
System.out.println("count is : " + count);
return count > 0;
} catch (Exception ex) {
System.out.println("seqNumber = " + seqNumber);
ex.printStackTrace();
} finally {
JdbcUtils.closeStatement(selectPreparedStatement);
DataSourceUtils.releaseConnection(con, getDataSource());
}
return false;
}

public String genSn(int bit){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date dNow = new Date();
String dateStr = sdf.format(dNow).toString();
long v = nextLongValue(dateStr);
String v_s = String.valueOf(v);
int num = getNumBit(v_s);
if(num<bit){
int zeroNum = bit - num;
String zeroStr = "";
for(int i=0;i<zeroNum;i++){
zeroStr += "0";
}
System.out.println("result is : " + zeroStr);
v_s = zeroStr + v_s;
}
if(v_s.length()!=bit){
System.out.println("error");
}
return v_s;
}

public int getNumBit(String str){
if(str!=null){
str = str.trim();
}
int numlen=0;
for (int i=0;i<str.length();i++){
System.out.print(str.substring(i, i+1)+",");//substring把STR的每一个字符取出
numlen++;//计数
}
return numlen;
}


@Override
public void afterPropertiesSet() throws Exception {
DatabaseType incrementerType = DatabaseType.fromMetaData(dataSource);
maxValueIncrementerFactory = new DefaultDataFieldMaxValueIncrementerFactory(
dataSource);
maxValueIncrementer = maxValueIncrementerFactory.getIncrementer(
incrementerType.getProductName(), incrementerName);
rollingTable = incrementerName + "_ROLLING";
selectSql = "select SEQ_NUMBER from " + rollingTable + " where YMD = ?";
insertSql = "insert into " + rollingTable + "(YMD,SEQ_NUMBER) values(?,?)";
}

public String getIncrementerName() {
return incrementerName;
}

public void setIncrementerName(String incrementerName) {
this.incrementerName = incrementerName;
}

public DataSource getDataSource() {
return dataSource;
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

}
### 使用 Redis 生成唯一订单号的方法及实现方案 在电子商务平台中,为了确保每个订单拥有唯一的标识符,通常会借助 Redis 来生成全局唯一 ID。以下是几种常见的方法及其优缺点分析。 #### 方法一:基于自增计数器生成唯一订单号 Redis 提供了一个原子操作 `INCR` 命令,可以通过该命令创建一个自增计数器来生成顺序递增的唯一订单号[^1]。 具体实现如下: ```python import redis # 初始化 Redis 连接 r = redis.StrictRedis(host='localhost', port=6379, db=0) def generate_order_id(): order_key = 'order:id' return r.incr(order_key) ``` 此方法的优点在于简单高效,能够保证订单号严格递增。然而,在分布式环境下可能会遇到单点瓶颈问题,因此需要结合分片或其他技术扩展其可用性和性能。 --- #### 方法二:组合时间戳与随机数生成唯一订单号 这种方法利用当前的时间戳作为主要部分,并附加一定范围内的随机数值以增加唯一性[^2]。由于时间戳本身具有较高的分辨率(毫秒级),再加上随机因子的作用,几乎可以杜绝重复的可能性。 代码示例如下: ```python from datetime import datetime import random def generate_unique_order_id(): timestamp = int(datetime.now().timestamp() * 1000) # 获取毫秒级别时间戳 rand_num = random.randint(1000, 9999) # 随机四位数字 return f"{timestamp}{rand_num}" ``` 尽管这种方式不依赖于 Redis 的状态存储能力,但它仍然可以在 Redis 中缓存已使用的订单号集合以便后续验证是否存在冲突情况。 --- #### 方法三:预分配固定区间并存储到 Redis List 数据结构 预先计算好一批可能用作订单号码的数据放入 Redis 的列表当中,每次获取新订单号时只需弹出队列头部即可完成分配动作[^3]。这种策略特别适合高并发场景下的快速响应需求。 下面是具体的 Python 实现版本: ```python import redis import random # 初始化 Redis 客户端实例 pool = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True) conn = redis.Redis(connection_pool=pool) def init_order_ids(prefix="ORDER_", count_per_list=1000, list_count=90): """初始化订单号池""" all_keys = [] for i in range(list_count): key_name = f"orders:{i}" conn.delete(key_name) # 清理旧数据 ids = [f"{prefix}{random.randint(10000, 99999)}" for _ in range(count_per_list)] conn.lpush(key_name, *ids) all_keys.append(key_name) return all_keys def get_next_order_id(all_keys=None): """从多个键组成的池子里面取出下一个有效订单号""" if not all_keys or len(all_keys)==0: raise Exception("No available keys found.") while True: current_key = random.choice(all_keys) result = conn.rpop(current_key) if result is None and len(conn.keys(f'orders:*'))==0: break elif result is not None: return result raise Exception("All orders exhausted!") ``` 上述函数中的 `init_order_ids()` 负责填充初始资源池;而 `get_next_order_id()` 则负责按需提取未被占用过的编号记录直至耗尽为止。 --- ### 总结比较三种方式的特点 | 方案 | 是否支持分布部署 | 并发处理效率 | 缺点 | |--|--|--|--| | 自增计数器法 | 不完全兼容多节点环境 | 较低 (因锁机制影响)| 单点故障风险较高 | | 时间戳加随机数法 | 支持跨服务器同步调用 | 极高 | 可能存在极小概率碰撞现象 | | 预先加载批量导入法 | 显著优化大规模请求吞吐量表现 | 很高 | 开始前准备工作繁琐 | 综上所述,实际应用过程中应综合考虑业务特性选取最合适的解决方案。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值