第263集使用hutool生成与解析二维码架构实战:Hutool二维码工具、生成解析与企业级二维码应用方案
|字数总计:7.7k|阅读时长:38分钟|阅读量:
前言
二维码作为现代移动互联网时代的重要信息载体,广泛应用于支付、登录、营销、物流等各个领域。Hutool作为Java生态中优秀的工具库,提供了简洁易用的二维码生成和解析功能。本文从Hutool二维码工具到生成解析,从基础应用到企业级方案,系统梳理基于Hutool的二维码应用完整解决方案。
1.2 二维码应用场景架构
2.1.1 Maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency>
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-qrcode</artifactId> <version>5.8.22</version> </dependency>
<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.1</version> </dependency>
<dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.5.1</version> </version>
|
2.1.2 Gradle依赖
1 2 3 4 5 6 7 8 9
| implementation 'cn.hutool:hutool-all:5.8.22'
implementation 'cn.hutool:hutool-qrcode:5.8.22'
implementation 'com.google.zxing:core:3.5.1' implementation 'com.google.zxing:javase:3.5.1'
|
2.2 基础二维码生成
2.2.1 简单二维码生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
@Service public class BasicQRCodeService {
public String generateSimpleQRCode(String content) { try { QrCodeUtil.generate(content, 300, 300, FileUtil.file("qrcode.png"));
byte[] qrCodeBytes = QrCodeUtil.generatePng(content, 300, 300);
String base64QRCode = Base64.encode(qrCodeBytes);
return base64QRCode;
} catch (Exception e) { log.error("生成二维码失败", e); throw new RuntimeException("生成二维码失败", e); } }
public String generateQRCodeWithLogo(String content, String logoPath) { try { QrConfig config = new QrConfig(300, 300); config.setMargin(1); config.setErrorCorrection(ErrorCorrectionLevel.H);
config.setImg(logoPath);
BufferedImage qrCodeImage = QrCodeUtil.generate(content, config);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(qrCodeImage, "PNG", outputStream); byte[] qrCodeBytes = outputStream.toByteArray();
return Base64.encode(qrCodeBytes);
} catch (Exception e) { log.error("生成带Logo的二维码失败", e); throw new RuntimeException("生成带Logo的二维码失败", e); } }
public String generateColorfulQRCode(String content) { try { QrConfig config = new QrConfig(300, 300); config.setMargin(1); config.setErrorCorrection(ErrorCorrectionLevel.H);
config.setForeColor(Color.BLACK); config.setBackColor(Color.WHITE);
BufferedImage qrCodeImage = QrCodeUtil.generate(content, config);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(qrCodeImage, "PNG", outputStream); byte[] qrCodeBytes = outputStream.toByteArray();
return Base64.encode(qrCodeBytes);
} catch (Exception e) { log.error("生成彩色二维码失败", e); throw new RuntimeException("生成彩色二维码失败", e); } } }
|
2.2.2 高级二维码配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
@Service public class AdvancedQRCodeService {
public String generateAdvancedQRCode(String content) { try { QrConfig config = new QrConfig();
config.setWidth(300); config.setHeight(300);
config.setMargin(2);
config.setErrorCorrection(ErrorCorrectionLevel.M);
config.setForeColor(Color.BLACK); config.setBackColor(Color.WHITE);
config.setImgType(ImageType.PNG);
BufferedImage qrCodeImage = QrCodeUtil.generate(content, config);
return convertToBase64(qrCodeImage);
} catch (Exception e) { log.error("生成高级配置二维码失败", e); throw new RuntimeException("生成高级配置二维码失败", e); } }
public String generateCustomStyleQRCode(String content) { try { QrConfig config = new QrConfig(400, 400);
config.setMargin(3);
config.setErrorCorrection(ErrorCorrectionLevel.H);
config.setForeColor(new Color(0, 102, 204)); config.setBackColor(new Color(255, 255, 240));
BufferedImage qrCodeImage = QrCodeUtil.generate(content, config);
BufferedImage borderedImage = addBorder(qrCodeImage, 20, Color.GRAY);
return convertToBase64(borderedImage);
} catch (Exception e) { log.error("生成自定义样式二维码失败", e); throw new RuntimeException("生成自定义样式二维码失败", e); } }
public List<String> generateBatchQRCodes(List<String> contents) { List<String> qrCodes = new ArrayList<>();
for (String content : contents) { try { String qrCode = generateAdvancedQRCode(content); qrCodes.add(qrCode); } catch (Exception e) { log.error("生成二维码失败: {}", content, e); qrCodes.add(null); } }
return qrCodes; }
private BufferedImage addBorder(BufferedImage image, int borderSize, Color borderColor) { int newWidth = image.getWidth() + 2 * borderSize; int newHeight = image.getHeight() + 2 * borderSize;
BufferedImage borderedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = borderedImage.createGraphics();
g2d.setColor(borderColor); g2d.fillRect(0, 0, newWidth, newHeight);
g2d.drawImage(image, borderSize, borderSize, null); g2d.dispose();
return borderedImage; }
private String convertToBase64(BufferedImage image) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "PNG", outputStream); byte[] imageBytes = outputStream.toByteArray(); return Base64.encode(imageBytes); } }
|
2.3 二维码解析
2.3.1 基础二维码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
@Service public class QRCodeParseService {
public String parseQRCodeFromFile(String filePath) { try { String content = QrCodeUtil.decode(FileUtil.file(filePath)); return content;
} catch (Exception e) { log.error("解析文件二维码失败: {}", filePath, e); throw new RuntimeException("解析文件二维码失败", e); } }
public String parseQRCodeFromBytes(byte[] qrCodeBytes) { try { String content = QrCodeUtil.decode(qrCodeBytes); return content;
} catch (Exception e) { log.error("解析字节数组二维码失败", e); throw new RuntimeException("解析字节数组二维码失败", e); } }
public String parseQRCodeFromBase64(String base64QRCode) { try { byte[] qrCodeBytes = Base64.decode(base64QRCode);
return parseQRCodeFromBytes(qrCodeBytes);
} catch (Exception e) { log.error("解析Base64二维码失败", e); throw new RuntimeException("解析Base64二维码失败", e); } }
public String parseQRCodeFromStream(InputStream inputStream) { try { String content = QrCodeUtil.decode(inputStream); return content;
} catch (Exception e) { log.error("解析输入流二维码失败", e); throw new RuntimeException("解析输入流二维码失败", e); } }
public List<String> parseBatchQRCodes(List<byte[]> qrCodeBytesList) { List<String> contents = new ArrayList<>();
for (byte[] qrCodeBytes : qrCodeBytesList) { try { String content = parseQRCodeFromBytes(qrCodeBytes); contents.add(content); } catch (Exception e) { log.error("批量解析二维码失败", e); contents.add(null); } }
return contents; } }
|
2.3.2 高级二维码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
@Service public class AdvancedQRCodeParseService {
public QRCodeParseResult parseQRCodeWithDetails(byte[] qrCodeBytes) { try { String content = QrCodeUtil.decode(qrCodeBytes);
QRCodeParseResult result = new QRCodeParseResult(); result.setContent(content); result.setSuccess(true); result.setParseTime(System.currentTimeMillis());
QRCodeContentType contentType = analyzeContentType(content); result.setContentType(contentType);
boolean isValid = validateContent(content, contentType); result.setValid(isValid);
return result;
} catch (Exception e) { log.error("解析二维码详细信息失败", e);
QRCodeParseResult result = new QRCodeParseResult(); result.setSuccess(false); result.setErrorMessage(e.getMessage()); result.setParseTime(System.currentTimeMillis());
return result; } }
public QRCodeParseResult parseQRCodeWithSignature(byte[] qrCodeBytes, String secretKey) { try { String content = QrCodeUtil.decode(qrCodeBytes);
boolean signatureValid = validateSignature(content, secretKey);
QRCodeParseResult result = new QRCodeParseResult(); result.setContent(content); result.setSuccess(true); result.setSignatureValid(signatureValid); result.setParseTime(System.currentTimeMillis());
return result;
} catch (Exception e) { log.error("解析带签名的二维码失败", e);
QRCodeParseResult result = new QRCodeParseResult(); result.setSuccess(false); result.setErrorMessage(e.getMessage()); result.setParseTime(System.currentTimeMillis());
return result; } }
private QRCodeContentType analyzeContentType(String content) { if (content.startsWith("http://") || content.startsWith("https://")) { return QRCodeContentType.URL; } else if (content.startsWith("tel:")) { return QRCodeContentType.PHONE; } else if (content.startsWith("mailto:")) { return QRCodeContentType.EMAIL; } else if (content.startsWith("sms:")) { return QRCodeContentType.SMS; } else if (content.startsWith("geo:")) { return QRCodeContentType.LOCATION; } else if (content.startsWith("wifi:")) { return QRCodeContentType.WIFI; } else if (content.matches("^[0-9]+$")) { return QRCodeContentType.NUMBER; } else if (content.matches("^[A-Za-z0-9+/=]+$")) { return QRCodeContentType.BASE64; } else { return QRCodeContentType.TEXT; } }
private boolean validateContent(String content, QRCodeContentType contentType) { switch (contentType) { case URL: return isValidUrl(content); case PHONE: return isValidPhone(content); case EMAIL: return isValidEmail(content); case SMS: return isValidSms(content); case LOCATION: return isValidLocation(content); case WIFI: return isValidWifi(content); case NUMBER: return isValidNumber(content); case BASE64: return isValidBase64(content); default: return true; } }
private boolean validateSignature(String content, String secretKey) { try { String[] parts = content.split("\\|"); if (parts.length != 2) { return false; }
String data = parts[0]; String signature = parts[1];
String expectedSignature = calculateSignature(data, secretKey);
return signature.equals(expectedSignature);
} catch (Exception e) { log.error("验证签名失败", e); return false; } }
private String calculateSignature(String data, String secretKey) { try { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"); mac.init(secretKeySpec);
byte[] signature = mac.doFinal(data.getBytes()); return Base64.encode(signature);
} catch (Exception e) { throw new RuntimeException("计算签名失败", e); } }
private boolean isValidUrl(String url) { try { new URL(url); return true; } catch (Exception e) { return false; } }
private boolean isValidPhone(String phone) { return phone.matches("^tel:\\+?[1-9]\\d{1,14}$"); }
private boolean isValidEmail(String email) { return email.matches("^mailto:[\\w\\.-]+@[\\w\\.-]+\\.[a-zA-Z]{2,}$"); }
private boolean isValidSms(String sms) { return sms.matches("^sms:\\+?[1-9]\\d{1,14}:.*$"); }
private boolean isValidLocation(String location) { return location.matches("^geo:-?\\d+\\.?\\d*,-?\\d+\\.?\\d*$"); }
private boolean isValidWifi(String wifi) { return wifi.startsWith("wifi:"); }
private boolean isValidNumber(String number) { return number.matches("^\\d+$"); }
private boolean isValidBase64(String base64) { try { Base64.decode(base64); return true; } catch (Exception e) { return false; } } }
|
三、企业级二维码应用
3.1 二维码服务架构
3.1.1 二维码服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public interface QRCodeService {
QRCodeResponse generateQRCode(QRCodeRequest request);
QRCodeParseResponse parseQRCode(QRCodeParseRequest request);
List<QRCodeResponse> generateBatchQRCodes(List<QRCodeRequest> requests);
List<QRCodeParseResponse> parseBatchQRCodes(List<QRCodeParseRequest> requests);
QRCodeTemplate getQRCodeTemplate(String templateId);
QRCodeTemplate createQRCodeTemplate(QRCodeTemplateRequest request); }
|
3.1.2 二维码服务实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
|
@Service public class QRCodeServiceImpl implements QRCodeService {
@Autowired private QRCodeRepository qrCodeRepository;
@Autowired private QRCodeTemplateRepository templateRepository;
@Autowired private FileStorageService fileStorageService;
@Autowired private QRCodeCacheService cacheService;
@Override public QRCodeResponse generateQRCode(QRCodeRequest request) { try { validateQRCodeRequest(request);
String cacheKey = generateCacheKey(request); QRCodeResponse cachedResponse = cacheService.get(cacheKey); if (cachedResponse != null) { return cachedResponse; }
QRCodeData qrCodeData = generateQRCodeData(request);
qrCodeRepository.save(qrCodeData);
QRCodeResponse response = buildQRCodeResponse(qrCodeData);
cacheService.put(cacheKey, response, Duration.ofMinutes(30));
return response;
} catch (Exception e) { log.error("生成二维码失败", e); throw new QRCodeException("生成二维码失败", e); } }
@Override public QRCodeParseResponse parseQRCode(QRCodeParseRequest request) { try { validateQRCodeParseRequest(request);
QRCodeParseResult result = parseQRCodeContent(request.getQrCodeData());
QRCodeParseResponse response = buildQRCodeParseResponse(result);
return response;
} catch (Exception e) { log.error("解析二维码失败", e); throw new QRCodeException("解析二维码失败", e); } }
@Override public List<QRCodeResponse> generateBatchQRCodes(List<QRCodeRequest> requests) { List<QRCodeResponse> responses = new ArrayList<>();
requests.parallelStream().forEach(request -> { try { QRCodeResponse response = generateQRCode(request); responses.add(response); } catch (Exception e) { log.error("批量生成二维码失败", e); responses.add(QRCodeResponse.error("生成失败")); } });
return responses; }
@Override public List<QRCodeParseResponse> parseBatchQRCodes(List<QRCodeParseRequest> requests) { List<QRCodeParseResponse> responses = new ArrayList<>();
requests.parallelStream().forEach(request -> { try { QRCodeParseResponse response = parseQRCode(request); responses.add(response); } catch (Exception e) { log.error("批量解析二维码失败", e); responses.add(QRCodeParseResponse.error("解析失败")); } });
return responses; }
private QRCodeData generateQRCodeData(QRCodeRequest request) { try { QrConfig config = createQrConfig(request);
BufferedImage qrCodeImage = QrCodeUtil.generate(request.getContent(), config);
String imageUrl = saveQRCodeImage(qrCodeImage, request);
QRCodeData qrCodeData = new QRCodeData(); qrCodeData.setId(UUID.randomUUID().toString()); qrCodeData.setContent(request.getContent()); qrCodeData.setImageUrl(imageUrl); qrCodeData.setWidth(request.getWidth()); qrCodeData.setHeight(request.getHeight()); qrCodeData.setCreateTime(System.currentTimeMillis()); qrCodeData.setExpireTime(request.getExpireTime());
return qrCodeData;
} catch (Exception e) { throw new RuntimeException("生成二维码数据失败", e); } }
private QrConfig createQrConfig(QRCodeRequest request) { QrConfig config = new QrConfig();
config.setWidth(request.getWidth()); config.setHeight(request.getHeight());
config.setMargin(request.getMargin());
config.setErrorCorrection(request.getErrorCorrection());
config.setForeColor(request.getForeColor()); config.setBackColor(request.getBackColor());
if (request.getLogoPath() != null) { config.setImg(request.getLogoPath()); }
return config; }
private String saveQRCodeImage(BufferedImage image, QRCodeRequest request) { try { String fileName = generateFileName(request);
String imageUrl = fileStorageService.saveImage(image, fileName);
return imageUrl;
} catch (Exception e) { throw new RuntimeException("保存二维码图片失败", e); } }
private String generateFileName(QRCodeRequest request) { String timestamp = String.valueOf(System.currentTimeMillis()); String hash = DigestUtil.md5Hex(request.getContent()); return String.format("qrcode_%s_%s.png", timestamp, hash); }
private String generateCacheKey(QRCodeRequest request) { StringBuilder key = new StringBuilder(); key.append("qrcode:"); key.append(DigestUtil.md5Hex(request.getContent())); key.append(":"); key.append(request.getWidth()); key.append("x"); key.append(request.getHeight()); key.append(":"); key.append(request.getMargin()); return key.toString(); } }
|
3.2 二维码模板管理
3.2.1 二维码模板服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
@Service public class QRCodeTemplateService {
@Autowired private QRCodeTemplateRepository templateRepository;
@Autowired private QRCodeService qrCodeService;
public QRCodeTemplate createTemplate(QRCodeTemplateRequest request) { try { validateTemplateRequest(request);
QRCodeTemplate template = new QRCodeTemplate(); template.setId(UUID.randomUUID().toString()); template.setName(request.getName()); template.setDescription(request.getDescription()); template.setWidth(request.getWidth()); template.setHeight(request.getHeight()); template.setMargin(request.getMargin()); template.setErrorCorrection(request.getErrorCorrection()); template.setForeColor(request.getForeColor()); template.setBackColor(request.getBackColor()); template.setLogoPath(request.getLogoPath()); template.setCreateTime(System.currentTimeMillis()); template.setStatus(TemplateStatus.ACTIVE);
templateRepository.save(template);
return template;
} catch (Exception e) { log.error("创建二维码模板失败", e); throw new QRCodeException("创建二维码模板失败", e); } }
public QRCodeResponse generateQRCodeWithTemplate(String templateId, String content) { try { QRCodeTemplate template = templateRepository.findById(templateId) .orElseThrow(() -> new QRCodeException("模板不存在"));
if (template.getStatus() != TemplateStatus.ACTIVE) { throw new QRCodeException("模板已禁用"); }
QRCodeRequest request = new QRCodeRequest(); request.setContent(content); request.setWidth(template.getWidth()); request.setHeight(template.getHeight()); request.setMargin(template.getMargin()); request.setErrorCorrection(template.getErrorCorrection()); request.setForeColor(template.getForeColor()); request.setBackColor(template.getBackColor()); request.setLogoPath(template.getLogoPath());
return qrCodeService.generateQRCode(request);
} catch (Exception e) { log.error("使用模板生成二维码失败", e); throw new QRCodeException("使用模板生成二维码失败", e); } }
public QRCodeTemplate updateTemplate(String templateId, QRCodeTemplateRequest request) { try { QRCodeTemplate template = templateRepository.findById(templateId) .orElseThrow(() -> new QRCodeException("模板不存在"));
template.setName(request.getName()); template.setDescription(request.getDescription()); template.setWidth(request.getWidth()); template.setHeight(request.getHeight()); template.setMargin(request.getMargin()); template.setErrorCorrection(request.getErrorCorrection()); template.setForeColor(request.getForeColor()); template.setBackColor(request.getBackColor()); template.setLogoPath(request.getLogoPath()); template.setUpdateTime(System.currentTimeMillis());
templateRepository.save(template);
return template;
} catch (Exception e) { log.error("更新二维码模板失败", e); throw new QRCodeException("更新二维码模板失败", e); } }
public void deleteTemplate(String templateId) { try { QRCodeTemplate template = templateRepository.findById(templateId) .orElseThrow(() -> new QRCodeException("模板不存在"));
template.setStatus(TemplateStatus.DELETED); template.setDeleteTime(System.currentTimeMillis());
templateRepository.save(template);
} catch (Exception e) { log.error("删除二维码模板失败", e); throw new QRCodeException("删除二维码模板失败", e); } }
public List<QRCodeTemplate> getTemplateList(TemplateQueryRequest request) { try { Specification<QRCodeTemplate> spec = buildTemplateSpecification(request);
List<QRCodeTemplate> templates = templateRepository.findAll(spec);
return templates;
} catch (Exception e) { log.error("获取模板列表失败", e); throw new QRCodeException("获取模板列表失败", e); } }
private Specification<QRCodeTemplate> buildTemplateSpecification(TemplateQueryRequest request) { return (root, query, cb) -> { List<Predicate> predicates = new ArrayList<>();
if (request.getStatus() != null) { predicates.add(cb.equal(root.get("status"), request.getStatus())); }
if (StringUtils.hasText(request.getName())) { predicates.add(cb.like(root.get("name"), "%" + request.getName() + "%")); }
if (request.getStartTime() != null) { predicates.add(cb.greaterThanOrEqualTo(root.get("createTime"), request.getStartTime())); }
if (request.getEndTime() != null) { predicates.add(cb.lessThanOrEqualTo(root.get("createTime"), request.getEndTime())); }
return cb.and(predicates.toArray(new Predicate[0])); }; }
private void validateTemplateRequest(QRCodeTemplateRequest request) { if (StringUtils.isEmpty(request.getName())) { throw new QRCodeException("模板名称不能为空"); }
if (request.getWidth() <= 0 || request.getHeight() <= 0) { throw new QRCodeException("模板尺寸必须大于0"); }
if (request.getMargin() < 0) { throw new QRCodeException("边距不能小于0"); } } }
|
3.3 二维码缓存服务
3.3.1 缓存服务实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
@Service public class QRCodeCacheService {
@Autowired private RedisTemplate<String, Object> redisTemplate;
@Autowired private CaffeineCache localCache;
private final String QR_CODE_PREFIX = "qrcode:"; private final String TEMPLATE_PREFIX = "template:";
public <T> T get(String key, Class<T> clazz) { try { T value = localCache.getIfPresent(key); if (value != null) { return value; }
Object redisValue = redisTemplate.opsForValue().get(key); if (redisValue != null) { T result = convertValue(redisValue, clazz); localCache.put(key, result); return result; }
return null;
} catch (Exception e) { log.error("获取缓存失败: {}", key, e); return null; } }
public void put(String key, Object value, Duration duration) { try { localCache.put(key, value);
redisTemplate.opsForValue().set(key, value, duration);
} catch (Exception e) { log.error("设置缓存失败: {}", key, e); } }
public void evict(String key) { try { localCache.invalidate(key);
redisTemplate.delete(key);
} catch (Exception e) { log.error("删除缓存失败: {}", key, e); } }
public <T> Map<String, T> getBatch(List<String> keys, Class<T> clazz) { Map<String, T> result = new HashMap<>();
for (String key : keys) { T value = get(key, clazz); if (value != null) { result.put(key, value); } }
return result; }
public void putBatch(Map<String, Object> keyValues, Duration duration) { for (Map.Entry<String, Object> entry : keyValues.entrySet()) { put(entry.getKey(), entry.getValue(), duration); } }
@Scheduled(fixedRate = 300000) public void cleanupExpiredCache() { try { localCache.cleanUp();
cleanupRedisExpiredKeys();
} catch (Exception e) { log.error("清理过期缓存失败", e); } }
private void cleanupRedisExpiredKeys() { try { Set<String> qrCodeKeys = redisTemplate.keys(QR_CODE_PREFIX + "*");
for (String key : qrCodeKeys) { Long ttl = redisTemplate.getExpire(key); if (ttl != null && ttl <= 0) { redisTemplate.delete(key); } }
} catch (Exception e) { log.error("清理Redis过期键失败", e); } }
@SuppressWarnings("unchecked") private <T> T convertValue(Object value, Class<T> clazz) { if (value == null) { return null; }
if (clazz.isAssignableFrom(value.getClass())) { return (T) value; }
try { ObjectMapper mapper = new ObjectMapper(); return mapper.convertValue(value, clazz); } catch (Exception e) { log.error("转换值类型失败", e); return null; } } }
|
四、二维码应用场景
4.1 支付场景应用
4.1.1 支付二维码生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
@Service public class PaymentQRCodeService {
@Autowired private QRCodeService qrCodeService;
@Autowired private PaymentService paymentService;
public PaymentQRCodeResponse generatePaymentQRCode(PaymentQRCodeRequest request) { try { PaymentOrder order = paymentService.createOrder(request);
String paymentContent = buildPaymentContent(order);
QRCodeRequest qrCodeRequest = new QRCodeRequest(); qrCodeRequest.setContent(paymentContent); qrCodeRequest.setWidth(300); qrCodeRequest.setHeight(300); qrCodeRequest.setMargin(2); qrCodeRequest.setErrorCorrection(ErrorCorrectionLevel.H); qrCodeRequest.setForeColor(Color.BLACK); qrCodeRequest.setBackColor(Color.WHITE); qrCodeRequest.setExpireTime(order.getExpireTime());
QRCodeResponse qrCodeResponse = qrCodeService.generateQRCode(qrCodeRequest);
PaymentQRCodeResponse response = new PaymentQRCodeResponse(); response.setOrderId(order.getId()); response.setQrCodeImage(qrCodeResponse.getImageUrl()); response.setQrCodeContent(paymentContent); response.setAmount(order.getAmount()); response.setExpireTime(order.getExpireTime()); response.setStatus(PaymentStatus.PENDING);
return response;
} catch (Exception e) { log.error("生成支付二维码失败", e); throw new PaymentException("生成支付二维码失败", e); } }
public PaymentQRCodeParseResponse parsePaymentQRCode(String qrCodeContent) { try { PaymentContent paymentContent = parsePaymentContent(qrCodeContent);
boolean isValid = validatePaymentContent(paymentContent);
PaymentQRCodeParseResponse response = new PaymentQRCodeParseResponse(); response.setValid(isValid); response.setOrderId(paymentContent.getOrderId()); response.setAmount(paymentContent.getAmount()); response.setMerchantId(paymentContent.getMerchantId()); response.setProductName(paymentContent.getProductName());
return response;
} catch (Exception e) { log.error("解析支付二维码失败", e); throw new PaymentException("解析支付二维码失败", e); } }
private String buildPaymentContent(PaymentOrder order) { PaymentContent content = new PaymentContent(); content.setOrderId(order.getId()); content.setAmount(order.getAmount()); content.setMerchantId(order.getMerchantId()); content.setProductName(order.getProductName()); content.setTimestamp(System.currentTimeMillis()); content.setSignature(calculateSignature(content));
return JSON.toJSONString(content); }
private PaymentContent parsePaymentContent(String qrCodeContent) { try { return JSON.parseObject(qrCodeContent, PaymentContent.class); } catch (Exception e) { throw new PaymentException("解析支付内容失败", e); } }
private boolean validatePaymentContent(PaymentContent content) { try { String expectedSignature = calculateSignature(content); if (!expectedSignature.equals(content.getSignature())) { return false; }
long currentTime = System.currentTimeMillis(); long timeDiff = Math.abs(currentTime - content.getTimestamp()); if (timeDiff > 5 * 60 * 1000) { return false; }
PaymentOrder order = paymentService.getOrder(content.getOrderId()); if (order == null || order.getStatus() != PaymentStatus.PENDING) { return false; }
return true;
} catch (Exception e) { log.error("验证支付内容失败", e); return false; } }
private String calculateSignature(PaymentContent content) { try { StringBuilder data = new StringBuilder(); data.append(content.getOrderId()); data.append(content.getAmount()); data.append(content.getMerchantId()); data.append(content.getTimestamp());
Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec("payment-secret".getBytes(), "HmacSHA256"); mac.init(secretKeySpec);
byte[] signature = mac.doFinal(data.toString().getBytes()); return Base64.encode(signature);
} catch (Exception e) { throw new PaymentException("计算签名失败", e); } } }
|
4.2 登录场景应用
4.2.1 扫码登录二维码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
@Service public class LoginQRCodeService {
@Autowired private QRCodeService qrCodeService;
@Autowired private LoginSessionService sessionService;
public LoginQRCodeResponse generateLoginQRCode(LoginQRCodeRequest request) { try { LoginSession session = sessionService.createSession(request);
String loginContent = buildLoginContent(session);
QRCodeRequest qrCodeRequest = new QRCodeRequest(); qrCodeRequest.setContent(loginContent); qrCodeRequest.setWidth(300); qrCodeRequest.setHeight(300); qrCodeRequest.setMargin(2); qrCodeRequest.setErrorCorrection(ErrorCorrectionLevel.H); qrCodeRequest.setForeColor(Color.BLACK); qrCodeRequest.setBackColor(Color.WHITE); qrCodeRequest.setExpireTime(session.getExpireTime());
QRCodeResponse qrCodeResponse = qrCodeService.generateQRCode(qrCodeRequest);
LoginQRCodeResponse response = new LoginQRCodeResponse(); response.setSessionId(session.getId()); response.setQrCodeImage(qrCodeResponse.getImageUrl()); response.setQrCodeContent(loginContent); response.setExpireTime(session.getExpireTime()); response.setStatus(LoginStatus.PENDING);
return response;
} catch (Exception e) { log.error("生成登录二维码失败", e); throw new LoginException("生成登录二维码失败", e); } }
public LoginQRCodeParseResponse parseLoginQRCode(String qrCodeContent) { try { LoginContent loginContent = parseLoginContent(qrCodeContent);
boolean isValid = validateLoginContent(loginContent);
LoginQRCodeParseResponse response = new LoginQRCodeParseResponse(); response.setValid(isValid); response.setSessionId(loginContent.getSessionId()); response.setClientId(loginContent.getClientId()); response.setRedirectUrl(loginContent.getRedirectUrl());
return response;
} catch (Exception e) { log.error("解析登录二维码失败", e); throw new LoginException("解析登录二维码失败", e); } }
private String buildLoginContent(LoginSession session) { LoginContent content = new LoginContent(); content.setSessionId(session.getId()); content.setClientId(session.getClientId()); content.setRedirectUrl(session.getRedirectUrl()); content.setTimestamp(System.currentTimeMillis()); content.setSignature(calculateLoginSignature(content));
return JSON.toJSONString(content); }
private LoginContent parseLoginContent(String qrCodeContent) { try { return JSON.parseObject(qrCodeContent, LoginContent.class); } catch (Exception e) { throw new LoginException("解析登录内容失败", e); } }
private boolean validateLoginContent(LoginContent content) { try { String expectedSignature = calculateLoginSignature(content); if (!expectedSignature.equals(content.getSignature())) { return false; }
long currentTime = System.currentTimeMillis(); long timeDiff = Math.abs(currentTime - content.getTimestamp()); if (timeDiff > 5 * 60 * 1000) { return false; }
LoginSession session = sessionService.getSession(content.getSessionId()); if (session == null || session.getStatus() != LoginStatus.PENDING) { return false; }
return true;
} catch (Exception e) { log.error("验证登录内容失败", e); return false; } }
private String calculateLoginSignature(LoginContent content) { try { StringBuilder data = new StringBuilder(); data.append(content.getSessionId()); data.append(content.getClientId()); data.append(content.getTimestamp());
Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec("login-secret".getBytes(), "HmacSHA256"); mac.init(secretKeySpec);
byte[] signature = mac.doFinal(data.toString().getBytes()); return Base64.encode(signature);
} catch (Exception e) { throw new LoginException("计算登录签名失败", e); } } }
|
4.3 营销场景应用
4.3.1 营销二维码生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
@Service public class MarketingQRCodeService {
@Autowired private QRCodeService qrCodeService;
@Autowired private MarketingCampaignService campaignService;
public MarketingQRCodeResponse generateMarketingQRCode(MarketingQRCodeRequest request) { try { MarketingCampaign campaign = campaignService.createCampaign(request);
String marketingContent = buildMarketingContent(campaign);
QRCodeRequest qrCodeRequest = new QRCodeRequest(); qrCodeRequest.setContent(marketingContent); qrCodeRequest.setWidth(300); qrCodeRequest.setHeight(300); qrCodeRequest.setMargin(2); qrCodeRequest.setErrorCorrection(ErrorCorrectionLevel.M); qrCodeRequest.setForeColor(Color.BLACK); qrCodeRequest.setBackColor(Color.WHITE); qrCodeRequest.setLogoPath(campaign.getLogoPath()); qrCodeRequest.setExpireTime(campaign.getExpireTime());
QRCodeResponse qrCodeResponse = qrCodeService.generateQRCode(qrCodeRequest);
MarketingQRCodeResponse response = new MarketingQRCodeResponse(); response.setCampaignId(campaign.getId()); response.setQrCodeImage(qrCodeResponse.getImageUrl()); response.setQrCodeContent(marketingContent); response.setCampaignName(campaign.getName()); response.setDiscount(campaign.getDiscount()); response.setExpireTime(campaign.getExpireTime()); response.setStatus(CampaignStatus.ACTIVE);
return response;
} catch (Exception e) { log.error("生成营销二维码失败", e); throw new MarketingException("生成营销二维码失败", e); } }
public MarketingQRCodeParseResponse parseMarketingQRCode(String qrCodeContent) { try { MarketingContent marketingContent = parseMarketingContent(qrCodeContent);
boolean isValid = validateMarketingContent(marketingContent);
MarketingQRCodeParseResponse response = new MarketingQRCodeParseResponse(); response.setValid(isValid); response.setCampaignId(marketingContent.getCampaignId()); response.setCampaignName(marketingContent.getCampaignName()); response.setDiscount(marketingContent.getDiscount()); response.setProductId(marketingContent.getProductId()); response.setRedirectUrl(marketingContent.getRedirectUrl());
return response;
} catch (Exception e) { log.error("解析营销二维码失败", e); throw new MarketingException("解析营销二维码失败", e); } }
private String buildMarketingContent(MarketingCampaign campaign) { MarketingContent content = new MarketingContent(); content.setCampaignId(campaign.getId()); content.setCampaignName(campaign.getName()); content.setDiscount(campaign.getDiscount()); content.setProductId(campaign.getProductId()); content.setRedirectUrl(campaign.getRedirectUrl()); content.setTimestamp(System.currentTimeMillis()); content.setSignature(calculateMarketingSignature(content));
return JSON.toJSONString(content); }
private MarketingContent parseMarketingContent(String qrCodeContent) { try { return JSON.parseObject(qrCodeContent, MarketingContent.class); } catch (Exception e) { throw new MarketingException("解析营销内容失败", e); } }
private boolean validateMarketingContent(MarketingContent content) { try { String expectedSignature = calculateMarketingSignature(content); if (!expectedSignature.equals(content.getSignature())) { return false; }
long currentTime = System.currentTimeMillis(); long timeDiff = Math.abs(currentTime - content.getTimestamp()); if (timeDiff > 24 * 60 * 60 * 1000) { return false; }
MarketingCampaign campaign = campaignService.getCampaign(content.getCampaignId()); if (campaign == null || campaign.getStatus() != CampaignStatus.ACTIVE) { return false; }
return true;
} catch (Exception e) { log.error("验证营销内容失败", e); return false; } }
private String calculateMarketingSignature(MarketingContent content) { try { StringBuilder data = new StringBuilder(); data.append(content.getCampaignId()); data.append(content.getProductId()); data.append(content.getTimestamp());
Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec("marketing-secret".getBytes(), "HmacSHA256"); mac.init(secretKeySpec);
byte[] signature = mac.doFinal(data.toString().getBytes()); return Base64.encode(signature);
} catch (Exception e) { throw new MarketingException("计算营销签名失败", e); } } }
|
五、企业级部署方案
5.1 容器化部署
5.1.1 Docker配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| FROM openjdk:8-jdk-alpine
RUN apk add --no-cache ttf-dejavu
WORKDIR /app
COPY target/qrcode-service-*.jar app.jar
ENV JAVA_OPTS="-Xms512m -Xmx1g -XX:+UseG1GC"
EXPOSE 8080
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| version: '3.8' services: qrcode-service: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - REDIS_HOST=redis - MYSQL_HOST=mysql depends_on: - redis - mysql restart: unless-stopped
redis: image: redis:6.2-alpine ports: - "6379:6379" volumes: - redis_data:/data restart: unless-stopped
mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=root123 - MYSQL_DATABASE=qrcode ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql restart: unless-stopped
volumes: redis_data: mysql_data:
|
5.2 监控告警
5.2.1 监控指标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
@Component public class QRCodeMetrics {
private final MeterRegistry meterRegistry;
public QRCodeMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; }
public void recordQRCodeGenerated() { Counter.builder("qrcode.generated") .description("二维码生成次数") .register(meterRegistry) .increment(); }
public void recordQRCodeParsed() { Counter.builder("qrcode.parsed") .description("二维码解析次数") .register(meterRegistry) .increment(); }
public void recordQRCodeGenerationFailed() { Counter.builder("qrcode.generation.failed") .description("二维码生成失败次数") .register(meterRegistry) .increment(); }
public void recordQRCodeParseFailed() { Counter.builder("qrcode.parse.failed") .description("二维码解析失败次数") .register(meterRegistry) .increment(); }
public void recordResponseTime(String operation, long duration) { Timer.builder("qrcode.response.time") .description("响应时间") .tag("operation", operation) .register(meterRegistry) .record(duration, TimeUnit.MILLISECONDS); } }
|
5.2.2 告警规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| groups: - name: qrcode_alerts rules: - alert: HighQRCodeGenerationFailureRate expr: rate(qrcode_generation_failed[5m]) / rate(qrcode_generated[5m] + qrcode_generation_failed[5m]) > 0.1 for: 2m labels: severity: warning annotations: summary: "二维码生成失败率过高" description: "二维码生成失败率超过10%,当前值: {{ $value }}" - alert: HighQRCodeParseFailureRate expr: rate(qrcode_parse_failed[5m]) / rate(qrcode_parsed[5m] + qrcode_parse_failed[5m]) > 0.1 for: 2m labels: severity: warning annotations: summary: "二维码解析失败率过高" description: "二维码解析失败率超过10%,当前值: {{ $value }}" - alert: HighResponseTime expr: qrcode_response_time{quantile="0.95"} > 2000 for: 2m labels: severity: warning annotations: summary: "二维码服务响应时间过长" description: "二维码服务响应时间P95超过2秒,当前值: {{ $value }}ms"
|
六、总结
使用Hutool生成与解析二维码,通过合理的架构设计和应用场景实现,能够为企业提供高效、稳定的二维码服务。本文从Hutool基础使用到企业级应用,从二维码生成解析到各种应用场景,系统梳理了基于Hutool的二维码应用完整解决方案。
6.1 关键要点
- 工具选择:Hutool提供了简洁易用的二维码生成和解析功能
- 架构设计:采用分层架构,支持模板管理和缓存优化
- 应用场景:支持支付、登录、营销等多种应用场景
- 安全机制:通过签名验证确保二维码内容的安全性
- 性能优化:通过缓存和批量处理提高系统性能
6.2 最佳实践
- 配置管理:使用模板管理二维码的样式和参数
- 缓存优化:使用多级缓存提高二维码生成和解析性能
- 安全防护:通过签名验证防止二维码内容被篡改
- 监控告警:建立完善的监控体系,及时发现和处理问题
- 容器化部署:使用Docker和Kubernetes实现服务的容器化部署
通过以上措施,可以构建一个高效、稳定、可扩展的二维码服务系统,为企业的各种业务场景提供支持。