package com.xiaopeng.order.utils;
|
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.stereotype.Component;
|
|
import javax.crypto.Mac;
|
import javax.crypto.spec.SecretKeySpec;
|
import java.security.InvalidKeyException;
|
import java.security.NoSuchAlgorithmException;
|
import java.security.SecureRandom;
|
|
/**
|
* 蓝牙解锁Token生成工具类
|
* 根据设计方案实现Token的生成
|
*/
|
@Component
|
public class BluetoothUnlockTokenUtil {
|
|
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
private static final int RANDOM_BYTES_LENGTH = 16; // 16字节随机数
|
private static final int HMAC_BYTES_LENGTH = 16; // 16字节HMAC (取前16字节)
|
private static final int LONG_BYTE_SIZE = 8; // Long类型占用8字节
|
|
@Value("${bluetooth.unlock.secret:123456}")
|
private String secret;
|
|
/**
|
* 生成蓝牙解锁Token
|
*
|
* @param userId 用户ID
|
* @param orderId 订单ID
|
* @param vehicleId 车辆ID
|
* @param expirationTime 订单预估结束时间
|
* @return 64字符的Token (32字符随机数 + 32字符HMAC)
|
*/
|
public String generateBluetoothUnlockToken(long userId, long orderId,
|
long vehicleId, long expirationTime) {
|
try {
|
// 1. 使用安全随机数生成器生成16字节随机数
|
SecureRandom secureRandom = new SecureRandom();
|
byte[] randomBytes = new byte[RANDOM_BYTES_LENGTH];
|
secureRandom.nextBytes(randomBytes);
|
|
// 2. 拼接二进制数据:随机数(16字节) + userId(8字节) + orderId(8字节) + vehicleId(8字节) + expirationTime(8字节)
|
byte[] dataToSign = new byte[RANDOM_BYTES_LENGTH + 4 * LONG_BYTE_SIZE];
|
|
// 复制随机数
|
System.arraycopy(randomBytes, 0, dataToSign, 0, RANDOM_BYTES_LENGTH);
|
|
// 复制userId (转换为字节数组)
|
longToBytes(userId, dataToSign, RANDOM_BYTES_LENGTH);
|
|
// 复制orderId (转换为字节数组)
|
longToBytes(orderId, dataToSign, RANDOM_BYTES_LENGTH + LONG_BYTE_SIZE);
|
|
// 复制vehicleId (转换为字节数组)
|
longToBytes(vehicleId, dataToSign, RANDOM_BYTES_LENGTH + 2 * LONG_BYTE_SIZE);
|
|
// 复制expirationTime (转换为字节数组)
|
longToBytes(expirationTime, dataToSign, RANDOM_BYTES_LENGTH + 3 * LONG_BYTE_SIZE);
|
|
// 3. 使用服务器端密钥对上述48字节计算HMAC-SHA256
|
byte[] hmacFull = calculateHmac(secret, dataToSign);
|
|
// 4. 取HMAC结果的前16字节
|
byte[] hmacPrefix = new byte[HMAC_BYTES_LENGTH];
|
System.arraycopy(hmacFull, 0, hmacPrefix, 0, HMAC_BYTES_LENGTH);
|
|
// 5. 最终Token = 十六进制(随机数) + 十六进制(HMAC前16字节)
|
String randomHex = bytesToHex(randomBytes);
|
String hmacHex = bytesToHex(hmacPrefix);
|
|
return randomHex + hmacHex;
|
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
throw new RuntimeException("生成Token失败", e);
|
}
|
}
|
|
|
|
/**
|
* 将long值转换为8字节,并存入指定位置
|
*
|
* @param value 要转换的long值
|
* @param dest 目标字节数组
|
* @param offset 偏移量
|
*/
|
private static void longToBytes(long value, byte[] dest, int offset) {
|
for (int i = 0; i < LONG_BYTE_SIZE; i++) {
|
dest[offset + i] = (byte) ((value >> (8 * (LONG_BYTE_SIZE - 1 - i))) & 0xFF);
|
}
|
}
|
|
/**
|
* 计算HMAC-SHA256
|
*
|
* @param secret 密钥
|
* @param data 待计算的数据
|
* @return HMAC-SHA256结果
|
* @throws NoSuchAlgorithmException
|
* @throws InvalidKeyException
|
*/
|
private static byte[] calculateHmac(String secret, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
|
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), HMAC_ALGORITHM);
|
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
|
mac.init(secretKeySpec);
|
return mac.doFinal(data);
|
}
|
|
/**
|
* 将字节数组转换为十六进制字符串
|
*
|
* @param bytes 字节数组
|
* @return 十六进制字符串
|
*/
|
private static String bytesToHex(byte[] bytes) {
|
StringBuilder sb = new StringBuilder();
|
for (byte b : bytes) {
|
String hex = Integer.toHexString(b & 0xFF);
|
if (hex.length() == 1) {
|
sb.append('0');
|
}
|
sb.append(hex);
|
}
|
return sb.toString();
|
}
|
|
}
|