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);
3.2 二维码中心logo
在二维码中心绘制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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战