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); } } }