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 @ 2024-10-28 15:57  渔樵江渚  阅读(112)  评论(0)    收藏  举报