Java二维码应用

通过接口获取数据并以二维码形式展示出来。
需要解决的主要问题:

  • 二维码获取
  • 二维码展示

1. 抓包微信小程序,获取所需信息

微信电脑版可以登录小程序,通过Charles可以抓包。
配置Charles,可以参考文章:charles使用教程, 只要配置好证书就行
运行Charles,打开微信小程序,找到要抓的接口

1.1 获取token

获取token比较敏感,涉及到一些安全问题。略过。

1.2 获取二维码信息

多刷新几次二维码,轻松找到刷新接口

可以发现,想要获取二维码需要两个关键的信息:token,卡号
而通过接口可以获取二维码字符串,头像地址
能够成功抓到接口信息,再使用代码实现就简单多了

整体的应用使用springboot来实现

1.3 模拟请求刷新二维码

这里使用了webClient发送请求
上代码:

public HashMap<String, String> reqQR(int cardId) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("token.txt"));
String token = reader.readLine();
String cardNo = "";
if (cardId == 0){
cardNo = cardNo0;
}else
if (cardId == 1){
cardNo = cardNo1;
}
log.info("card: {}",cardNo);
String url = "https://app.com/appGzh/getQRcode";
String requestBody = "{\"token\":\"" + token + "\",\"isDefault\":true,\"CardNo\":\""+ cardNo +"\",\"isTrue\":\"1\"}";
log.info(requestBody);
byte[] response = webClient.post()
.uri(url)
.header(HttpHeaders.HOST, "app.com")
.header(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090c11)XWEB/11275")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.header(HttpHeaders.ACCEPT, "*/*")
.header(HttpHeaders.REFERER, "")
.header(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh")
.header("xweb_xhr", "1")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(byte[].class)
.block();
assert response != null;
String responseStr = new String(response, StandardCharsets.UTF_8);
log.info(responseStr);
log.info(cardNo);
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(responseStr);
HashMap<String, String> map = new HashMap<>();
map.put("picPath",jsonNode.get("pic_Path").asText());
map.put("qrStr",jsonNode.get("qr_Str").asText());
return map;
}

通过map封装返回了二维码字符串,头像地址信息

2. 生成二维码

二维码本质上就是字符串,获取到字符串后再转换为二维码形式
二维码的解析与生成选择了google的zxing
pom导包如下:

<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>
</dependency>

代码:

public File conductQR(int cardId) throws IOException {
String QrString = reqQR(cardId).get("qrStr");
int width = 300;
int height = 300;
String filePath = "qr_code_"+cardId+".png";
try {
BitMatrix bitMatrix = new QRCodeWriter().encode(QrString, BarcodeFormat.QR_CODE, width, height);
Path path = FileSystems.getDefault().getPath(filePath);
MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path);
log.info(" create QR code");
} catch (WriterException | IOException e) {
e.printStackTrace();
}
return new File(filePath);
}

可以成功生成二维码:

3. 生成展示图片

能够成功生成二维码,是不是可以更进一步做的更像些。
可以在仿真的背景图上画出二维码

3.1 画二维码

int qrWidth = 380;
int qrHeight = 380;
String charset = "UTF-8";
Map<EncodeHintType, ErrorCorrectionLevel> hintMap = new HashMap<>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//获取二维码字符串
HashMap<String, String> infoMap = reqQR(cardId);
String QrString =infoMap.get("qrStr");
// 生成二维码
BitMatrix bitMatrix = new MultiFormatWriter().encode(
QrString,
BarcodeFormat.QR_CODE,
qrWidth,
qrHeight,
hintMap);
BufferedImage qrImage = toBufferedImage(bitMatrix);

在二维码中心绘制logo

BufferedImage logoImage = ImageIO.read(new File("logo.jpg"));
// 在二维码中间绘制 logo
log.info("draw logo");
int qrImageWidth = qrImage.getWidth();
int qrImageHeight = qrImage.getHeight();
int logoWidth = logoImage.getWidth()/3;
int logoHeight = logoImage.getHeight()/3;
int x_qr = (qrImageWidth - logoWidth) / 2;
int y_qr = (qrImageHeight - logoHeight) / 2;
Graphics2D g2d_qr = qrImage.createGraphics();
g2d_qr.drawImage(logoImage, x_qr, y_qr, logoWidth, logoHeight, null);
g2d_qr.dispose();

3.3 动态头像

通过之前的reqQR(int cardId) 可以拿到头像的地址
将图片放到本地:
在这里使用了一个静态方法,可以将网络地址上的文件保存到本地。

/**
* 将图片转为file
*
* @param urlPath 图片url
* @return File
*/
private static void saveFile(String urlPath,String filePath) throws Exception {
// 构造URL
URL url = new URL(urlPath);
// 打开连接
URLConnection con = url.openConnection();
// 输入流
InputStream is = con.getInputStream();
// 1K的数据缓冲
byte[] bs = new byte[1024];
// 读取到的数据长度
int len;
// 输出的文件流
String filename = filePath; //下载路径及下载图片名称
File file = new File(filename);
FileOutputStream os = new FileOutputStream(file, true);
// 开始读取
while ((len = is.read(bs)) != -1) {
os.write(bs, 0, len);
}
// 完毕,关闭所有链接
os.close();
is.close();
}

在背景图片上绘制头像

String urlPath = infoMap.get("picPath");
log.info("avatarPath:{}", urlPath);
//获取头像到本地
saveFile(urlPath,"avatarPic_"+cardId+".jpg");
BufferedImage avatarImage = ImageIO.read(new File("avatarPic_"+cardId+".jpg"));
int avatarWidth = avatarImage.getWidth()/2;
int avatarHeight = avatarImage.getHeight()/2;
// 创建一个与头像相同大小的圆形遮罩
log.info("draw avatar......");
BufferedImage mask = new BufferedImage(avatarWidth, avatarHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D maskGraphics = mask.createGraphics();
maskGraphics.setComposite(AlphaComposite.Clear);
maskGraphics.fillRect(0, 0, avatarWidth, avatarHeight);
maskGraphics.setComposite(AlphaComposite.Src);
maskGraphics.setColor(Color.WHITE);
maskGraphics.fill(new Ellipse2D.Float(0, 0, avatarWidth, avatarHeight));
// 在头像上应用圆形遮罩
BufferedImage roundedAvatar = new BufferedImage(avatarWidth, avatarHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D avatarGraphics = roundedAvatar.createGraphics();
avatarGraphics.drawImage(avatarImage, 0, 0, null);
avatarGraphics.setComposite(AlphaComposite.DstIn);
avatarGraphics.drawImage(mask, 0, 0, null);
avatarGraphics.dispose();
// 在背景图片上方绘制圆形头像
Graphics2D g2d_avator = backgroundImage.createGraphics();
g2d_avator.drawImage(roundedAvatar, (bgWidth - avatarWidth) / 2, 20, avatarWidth, avatarHeight, null);
g2d_avator.dispose();

3.4 二维码绘制到背景图上

// 读取背景图片
//String backgroundPath = "background_"+cardId+".jpg";
String backgroundPath = "background.jpg";
BufferedImage backgroundImage = ImageIO.read(new File(backgroundPath));
int bgWidth = backgroundImage.getWidth();
int bgHeight = backgroundImage.getHeight();
log.info("draw QR code......");
Graphics2D g2d = backgroundImage.createGraphics();
int x = (bgWidth - qrWidth) / 2;
int y = (bgHeight - qrHeight) / 2-60;
g2d.drawImage(qrImage, x, y, qrWidth, qrHeight, null);
g2d.dispose();
// 保存合成后的图片
ImageIO.write(backgroundImage, "png", new File("imageCode_"+cardId+".png"));

3.5 最终效果:
访问接口: http://localhost:8080/getImage/1/615Q66
成功在浏览器上获取二维码图片

4. 其他

为了确保二维码使用的时效性,添加了一个重置接口。

4.1 重置

生成一个随机的字符串,拼接到请求路径。
当需要过期操作时,可以重置字符串进而重置访问路径,以此确保时效性。

public String buildRandomID() throws IOException {
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
// 生成大写字母或数字
int choice = random.nextInt(2);
if (choice == 0) {
// 生成大写字母(A-Z 的 ASCII 码范围是 65-90)
sb.append((char) (random.nextInt(26) + 65));
} else {
// 生成数字(0-9 的 ASCII 码范围是 48-57)
sb.append((char) (random.nextInt(10) + 48));
}
}
FileWriter fw = new FileWriter("RandomID.txt",false);
fw.write(sb.toString());
log.info("reset ID:{}", sb.toString());
fw.close();
return sb.toString();
}

4.2 校验

public boolean verifyID(String id) throws IOException {
String filePath = "RandomID.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line = reader.readLine();
log.info("verify ID:{}", line);
return line.equals(id);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

处理请求之前先进行校验:

if(qrService.verifyID(randomID))

4.3 多用户操作

可以将用户ID入参,但用户ID不能直接暴露在外,

String cardNo = "";
if (cardId == 0){
cardNo = cardNo0;
}else
if (cardId == 1){
cardNo = cardNo1;
}
posted @   渔樵江渚  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示