package com.zy.acs.common.utils;
|
|
import com.zy.acs.framework.common.Cools;
|
|
import java.math.BigInteger;
|
import java.nio.charset.Charset;
|
import java.nio.charset.StandardCharsets;
|
import java.util.Arrays;
|
import java.util.Locale;
|
|
/**
|
* 地码编解码规则,按进程级配置生效。
|
*/
|
public final class QrCodeCodecSupport {
|
|
private static volatile CodecConfig config = CodecConfig.defaultConfig();
|
|
private QrCodeCodecSupport() {
|
}
|
|
public static void configure(String mode, Integer bytes, Integer displayLength, String charsetName) {
|
config = CodecConfig.of(mode, bytes, displayLength, charsetName);
|
}
|
|
public static int qrCodeBytes() {
|
return config.bytes;
|
}
|
|
public static String normalize(String qrCode) {
|
if (Cools.isEmpty(qrCode)) {
|
throw new IllegalArgumentException("qrCode must not be empty");
|
}
|
if (config.mode == Mode.NUMERIC) {
|
return normalizeNumericValue(qrCode, config.bytes, config.displayLength);
|
}
|
return normalizeStringValue(qrCode, config.bytes, config.charset);
|
}
|
|
public static String decode(byte[] bytes, int pos) {
|
byte[] qrCodeBytes = Utils.sliceWithReverse(bytes, pos, config.bytes);
|
if (config.mode == Mode.NUMERIC) {
|
return Utils.zeroFill(unsignedValue(qrCodeBytes).toString(), config.displayLength);
|
}
|
return stripPadding(new String(qrCodeBytes, config.charset));
|
}
|
|
public static byte[] encode(String qrCode) {
|
if (config.mode == Mode.NUMERIC) {
|
return Utils.reverse(toFixedNumericBytes(qrCode, config.bytes));
|
}
|
return Utils.reverse(toFixedStringBytes(qrCode, config.bytes, config.charset));
|
}
|
|
private static BigInteger unsignedValue(byte[] bytes) {
|
return bytes.length == 0 ? BigInteger.ZERO : new BigInteger(1, bytes);
|
}
|
|
private static byte[] toFixedNumericBytes(String qrCode, int size) {
|
String value = qrCode == null ? "" : qrCode.trim();
|
if (value.isEmpty()) {
|
value = "0";
|
}
|
BigInteger numeric = parseUnsignedNumericValue(value, qrCode);
|
byte[] raw = numeric.toByteArray();
|
if (raw.length > 1 && raw[0] == 0) {
|
raw = Arrays.copyOfRange(raw, 1, raw.length);
|
}
|
if (raw.length > size) {
|
throw new IllegalArgumentException("qrCode exceeds configured byte size: " + qrCode);
|
}
|
byte[] result = new byte[size];
|
System.arraycopy(raw, 0, result, size - raw.length, raw.length);
|
return result;
|
}
|
|
private static byte[] toFixedStringBytes(String qrCode, int size, Charset charset) {
|
String value = normalizeStringValue(qrCode, size, charset);
|
return value.getBytes(charset);
|
}
|
|
private static String normalizeNumericValue(String qrCode, int size, int displayLength) {
|
BigInteger numeric = parseUnsignedNumericValue(qrCode, qrCode);
|
byte[] raw = numeric.toByteArray();
|
if (raw.length > 1 && raw[0] == 0) {
|
raw = Arrays.copyOfRange(raw, 1, raw.length);
|
}
|
if (raw.length > size) {
|
throw new IllegalArgumentException("qrCode exceeds configured byte size: " + qrCode);
|
}
|
return Utils.zeroFill(numeric.toString(), displayLength);
|
}
|
|
private static String normalizeStringValue(String qrCode, int size, Charset charset) {
|
String value = qrCode == null ? "" : qrCode.trim();
|
byte[] raw = value.getBytes(charset);
|
if (raw.length != size) {
|
throw new IllegalArgumentException("qrCode byte length must be " + size + ": " + qrCode);
|
}
|
return value;
|
}
|
|
private static BigInteger parseUnsignedNumericValue(String value, String originalValue) {
|
if (!value.chars().allMatch(Character::isDigit)) {
|
throw new IllegalArgumentException("qrCode must be numeric: " + originalValue);
|
}
|
BigInteger numeric = new BigInteger(value);
|
if (numeric.signum() < 0) {
|
throw new IllegalArgumentException("qrCode must not be negative: " + originalValue);
|
}
|
return numeric;
|
}
|
|
private static String stripPadding(String value) {
|
int end = value.length();
|
while (end > 0 && value.charAt(end - 1) == '\u0000') {
|
end--;
|
}
|
return value.substring(0, end);
|
}
|
|
private enum Mode {
|
NUMERIC,
|
STRING;
|
|
static Mode of(String value) {
|
if (value == null) {
|
return NUMERIC;
|
}
|
String normalized = value.trim().toUpperCase(Locale.ROOT);
|
if ("STRING".equals(normalized)) {
|
return STRING;
|
}
|
return NUMERIC;
|
}
|
}
|
|
private static final class CodecConfig {
|
private final Mode mode;
|
private final int bytes;
|
private final int displayLength;
|
private final Charset charset;
|
|
private CodecConfig(Mode mode, int bytes, int displayLength, Charset charset) {
|
this.mode = mode;
|
this.bytes = bytes;
|
this.displayLength = displayLength;
|
this.charset = charset;
|
}
|
|
static CodecConfig defaultConfig() {
|
return new CodecConfig(Mode.NUMERIC, 4, 8, StandardCharsets.US_ASCII);
|
}
|
|
static CodecConfig of(String mode, Integer bytes, Integer displayLength, String charsetName) {
|
Mode resolvedMode = Mode.of(mode);
|
int resolvedBytes = bytes == null || bytes <= 0 ? 4 : bytes;
|
int resolvedDisplayLength = displayLength == null || displayLength <= 0 ? 8 : displayLength;
|
Charset resolvedCharset = charsetName == null || charsetName.trim().isEmpty()
|
? StandardCharsets.US_ASCII
|
: Charset.forName(charsetName.trim());
|
return new CodecConfig(resolvedMode, resolvedBytes, resolvedDisplayLength, resolvedCharset);
|
}
|
}
|
}
|