【JAVA】支付,积分相关代码开发总结
一、前言
如果做支付系统或者积分系统我们都知道,里面的数字类型和计算规则是我们平时需要注意的。
如金钱的数据类型,金钱的加减乘除,积分的数目,积分的计算规则等等。比如商城的页面金额0.01元,是否我们数据库里就是存储的是0.01呢?有时候我们也碰到过0.0001元的,那么在JAVA系统里怎么处理这些数据类型,下面根据实践经验进行简单的总结。
二、数据类型
1、积分数量我们可以用Long类型去表示,如果使用Integer类型可能不够。
2、金额的页面类型我们可以使用BigDecimal去表示, 这里特别需要注意的事BigDecimal的加减乘除
在Java中,BigDecimal的加减乘除运算需通过add()
、subtract()
、multiply()
和divide()
方法实现,直接使用算术运算符会导致精度丢失或计算错误。
(1)加法:使用add()
方法。
BigDecimal num1 = new BigDecimal("2");
BigDecimal num2 = new BigDecimal("6");
BigDecimal sum = num1.add(num2); // 结果:8
(2)减法:使用subtract()
方法。
BigDecimal difference = num2.subtract(num1); // 结果:4
BigDecimal difference = num2.subtract(num1); // 结果:4
(3)乘法:使用multiply()
方法。
BigDecimal product = num1.multiply(num2); // 结果:12
(4)除法:使用divide()
方法(需指定精度和舍入模式)
BigDecimal quotient = num2.divide(num1, 2, RoundingMode.HALF_UP); // 结果:3.00
注意事项
构造方法选择:优先使用String
参数构造,避免double
参数导致精度问题
// 正确示例
new BigDecimal("0.1"); // 精确
// 错误示例
new BigDecimal(0.1); // 存在精度误差
使用Hutool工具类库运算BigDecimal的加减乘除、保留两位小数。
@Test
public void operationBigDecimal()
{BigDecimal bigDecimal1 = new BigDecimal("10.2567");BigDecimal bigDecimal2 = new BigDecimal("2.236");//加法BigDecimal addResult = NumberUtil.add(bigDecimal1,bigDecimal2);System.out.println("加法运算结果:" + addResult);//减法BigDecimal subResult = NumberUtil.sub(bigDecimal1,bigDecimal2);System.out.println("减法运算结果:" + subResult);//乘法BigDecimal mulResult = NumberUtil.mul(bigDecimal1,bigDecimal2);System.out.println("乘法运算结果:" + mulResult);//除法BigDecimal divResult = NumberUtil.div(bigDecimal1,bigDecimal2);System.out.println("除法运算结果:" + divResult);//保留两位小数BigDecimal roundResult = NumberUtil.round(bigDecimal1,2);System.out.println("保留两位小数:" + roundResult);
}
BigDecimal的四舍五入,并保留两位小数。
@Test
public void roundTest()
{BigDecimal bigDecimal1 = new BigDecimal("12.233");BigDecimal bigDecimal2 = new BigDecimal("12.288");//四舍五入,并保留两位小数BigDecimal round1 = bigDecimal1.setScale(2, BigDecimal.ROUND_HALF_UP);BigDecimal round2 = bigDecimal2.setScale(2, BigDecimal.ROUND_HALF_UP);System.out.println("数值1:" + bigDecimal1);System.out.println("四舍五入:" + round1);System.out.println("-----------------");System.out.println("数值2:" + bigDecimal2);System.out.println("四舍五入:" + round2);
}
如果是互联网金融行业,所有在进行计算的时候尽量使用 ROUND_HALF_EVEN
以上都是页面的数据类型
在数据库里我们可以通过存Long类型,单位是 毫来存储金额
三、处理订单的时候,我们往往经常需要一个唯一的订单号和交易流水号,怎么生成这两个号码更好?
博主建议使用雪花算法就行,上一节里面写有,使用雪花算法注意使用每台部署的服务器标识就是生成,以防生成号码重复
package cn.ctg.zlt.framework.common.util.string;import lombok.Data;
import org.springframework.context.annotation.Configuration;/*** @ClassName SnowflakeIdWorker* @Description TODO* @Author jiwenjian* @Date 2024/8/13 下午3:55*/
@Configuration
@Data
public class SnowflakeIdWorkerUtils {/** 工作机器ID(0~31) */private long workerId;/** 数据中心ID(0~31) */private long datacenterId;/** 订单支付单号 1开头 */private static final String PAY_CODE = "1";/** 订单退款单号 2开头 */private static final String REFUND_CODE = "2";/** 云卡充值单号 3开头 */private static final String ACCOUNT_CODE = "3";/** 积分批次号 4开头 */private static final String POINT_CODE = "4";/** 积分费用入账批次号 5开头 */private static final String POINT_EXPENSE_CODE = "5";/** 首次关注公众号发放积分订单号 6开头 */private static final String FIRST_FOLLOW_ISSUE_POINT_CODE = "6";/** 新用户注册发放积分订单号 7开头 */private static final String ISSUE_REGISTER_TASK_POINT_CODE = "7";// ==============================Fields===========================================/** 开始时间截 (2015-01-01) */private final long twepoch = 1420041600000L;/** 机器id所占的位数 */private final long workerIdBits = 5L;/** 数据标识id所占的位数 */private final long datacenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数*/public SnowflakeIdWorkerUtils() {}/*** 支付单号* @return*/public String getOrderNo() {return PAY_CODE+ nextId();}/*** 退款单号* @return*/public String getRefundCode() {return REFUND_CODE+ nextId();}/*** 云卡充值单号* @return*/public String getAccountCode() {return ACCOUNT_CODE+ nextId();}/*** 积分批次号* @return*/public String getPointNo() {return POINT_CODE+ nextId();}/*** 积分费用入账批次号* @return*/public String getPointExpenseNo() {return POINT_EXPENSE_CODE+ nextId();}/*** 首次关注公众号发放积分订单号* @return*/public String getFirstFollowIssuePointNo() {return FIRST_FOLLOW_ISSUE_POINT_CODE+ nextId();}/*** 新用户注册发放积分订单号* @return*/public String getIssueRegisterTaskPointNo() {return ISSUE_REGISTER_TASK_POINT_CODE+ nextId();}/*** 构造函数* @param workerId 工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdWorkerUtils(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/** 测试 */public static void main(String[] args) {SnowflakeIdWorkerUtils idWorker = new SnowflakeIdWorkerUtils(1, 1);for (int i = 0; i < 10; i++) {String id = idWorker.getPointNo();System.out.println(id);}}
}