各卧唯 发表于 2025-6-5 10:21:40

如何通过接口实现动态二维码的定时刷新

如何通过接口实现动态二维码的定时刷新?

感觉本篇对你有帮助可以关注一下我的微信公众号(深入浅出谈java),会不定期更新知识和面试资料、技巧!!!

一、需求场景

在Web应用中,动态二维码常用于以下场景:

[*]登录验证:微信扫码登录、APP扫码授权
[*]支付场景:支付宝/微信支付码定时刷新
[*]票务系统:电子票二维码防截屏盗用
[*]会员系统:动态会员码累计积分
二、技术方案设计

1、 整体流程

三、前端部分

大致流程

1、请求后端接口(按照现有项目格式即可)
2、后端接口的返回类型定义(byte[])
3、前端接收时定义响应类型(重点:responseType: 'arraybuffer')
4、对后端返回数据进行转化:arrayBufferToBase64
5、对数据和标签进行绑定
具体实现

1、获取后端 二维码 图片接口
        import {
                generateMemberCode
        } from '@/api/inter/member-code.js';

data() {
                        return {
                                codeImg: '',
                                timer: null, // 添加定时器引用,
                        }
                },
                onShow() {
                        // 立即加载一次
                        this.makeMemberCode();

                        // 设置定时器(注意保存引用)
                        this.timer = setInterval(() => {
                                this.makeMemberCode();
                        }, 30000);
                },
                onHide() {
                        // 页面隐藏时清除定时器
                        if (this.timer) {
                                clearInterval(this.timer);
                                this.timer = null;
                        }
                },
      methods: {
                        async makeMemberCode() {
                                const memberId = 1; // 使用假数据中的 id
                                const memberName = '张三'; // 使用假数据中的 nickname
                                const params = {
                                        memberId,
                                        memberName
                                };
                                try {
                  // 调用后端接口(这里可改成符合你项目的请求方式)
                                        const response = await generateMemberCode(params);
                                        console.log('完整响应:', response);

                                        // 调试数据类型
                                        console.log('响应数据类型:', typeof response);

                                        // 处理二进制数据(适用于小程序)
                                        if (typeof response === 'object' && response instanceof ArrayBuffer) {
                                                const base64 = uni.arrayBufferToBase64(response);
                                                this.codeImg = `data:image/png;base64,${base64}`;
                                                console.log('新的 codeImg:', this.codeImg); // 调试输出
                                        } else if (typeof response.data === 'string') {
                                                this.codeImg = `data:image/png;base64,${response.data}`;
                                        }
                                        // 处理URL
                                        else if (response.data.imageUrl) {
                                                this.codeImg = response.data.imageUrl;
                                        }
                                } catch (error) {
                                        console.error('请求失败:', error);
                                        uni.showToast({
                                                title: '会员码加载失败',
                                                icon: 'none'
                                        });
                                }
                        }
      }上面部分用到的代码:member-code.js
import { HttpClient } from '@/api/utils/request.js'; // 确保路径正确
import api from '../../config/api.js'; // 导入 API 配置

const http = new HttpClient(api.base);


/**
* 获取后端二维码
*/
export async function generateMemberCode(params) {
    try {
      const response = await http.getbuffer('/api/wx/generate-qrcode',params);
      console.log('Response:', response);
      return response; // 返回响应数据
    } catch (error) {
      console.error('Error:', error);
      throw error; // 继续抛出错误以供上层处理
    }
}请求接口工具类:request.js
class HttpClient {
        constructor(baseURL) {
                this.baseURL = baseURL;
        }

async getbuffer(url, params = {}) {
                try {
                        //await this.checkTokenAndNavigate(); // 在发送请求前检查 token
                        const queryString = this.buildQueryString(params);
                        const fullURL = `${this.baseURL}${url}${queryString}`;
       
                        return new Promise((resolve, reject) => {
                                uni.request({
                                        url: fullURL,
                                        method: 'GET',
                                        header: {
                                                'Content-Type': 'application/json',
                                                'X-Auth-Token': `${uni.getStorageSync('token')}` // 如果需要在 header 中发送 token
                                        },
                                        responseType: 'arraybuffer', // 指定响应类型为 arraybuffer
                                        success: (response) => {
                                                resolve(response.data);
                                        },
                                        fail: (error) => {
                                                console.error('GET request error:', error);
                                                reject(error);
                                        }
                                });
                        });
                } catch (error) {
                        // 如果 checkTokenAndNavigate 抛出错误(例如没有 token),则这里处理错误
                        return Promise.reject(error);
                }
        }
    buildQueryString(params) {
          if (!params || Object.keys(params).length === 0) {
          return '';
          }
          return '?' + Object.keys(params)
          .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params))
          .join('&');
        }
}

export {
        HttpClient
};接口地址Api 配置类:API.js
// 生产环境
const prod = {
base: "http://XXXXXX:8099",
};

// 开发环境
const dev = {
base: "http://XXXXXXX:8099",
};

// 默认生产环境
let api = prod;

// 如果是开发环境
if (process.env.NODE_ENV === "development") {
api = dev;
}

// 微信小程序和App的打包方式建议为生产环境,所以这块直接条件编译赋值()
// #ifdef MP-WEIXIN || APP-PLUS
// 这个直接使用的是 dev 地址
api = dev;
// #endif

export default {
...api,
};2、模板进行渲染
        <view >
                                        <image :src="`${codeImg}`" ></image>
                                </view>四、后端部分

具体实现

1、添加依赖
    <dependency>
      <groupId>com.google.zxing</groupId>
      core</artifactId>
      <version>3.4.1</version>
    </dependency>
    <dependency>
      <groupId>com.google.zxing</groupId>
      javase</artifactId>
      <version>3.4.1</version>
    </dependency>2、接口实现controller
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayOutputStream;
import java.nio.file.FileSystems;
import java.util.HashMap;
import java.util.Map;

@RestController
public class QRCodeController {

    @GetMapping("/generate-qrcode")
    public ResponseEntity<byte[]> generateQRCode(
            @RequestParam String memberId,
            @RequestParam String memberName) throws WriterException {
      
      // 构建二维码内容(内容可根据自己业务动态修改)
      // 建议加上唯一标识,安全码、起始时间、状态等,保证二维码安全性
      String qrContent = "Member ID: " + memberId + ", Member Name: " + memberName;

      // 设置二维码参数
      int width = 300;
      int height = 300;
      String imageFormat = "PNG"; // 图片格式

      Map<EncodeHintType, Object> hints = new HashMap<>();
      hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");

      // 生成二维码
      QRCodeWriter qrCodeWriter = new QRCodeWriter();
      BitMatrix bitMatrix = qrCodeWriter.encode(qrContent, BarcodeFormat.QR_CODE, width, height, hints);

      // 将二维码写入字节数组输出流
      ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream();
      MatrixToImageWriter.writeToStream(bitMatrix, imageFormat, pngOutputStream);

      // 返回二维码图片
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.IMAGE_PNG);
      return new ResponseEntity<>(pngOutputStream.toByteArray(), headers, HttpStatus.OK);
    }
}接口测试
GET http://localhost:8080/generate-qrcode?memberId=12345&memberName=JohnDoe

前端处理后端的数据时,在一定要指定相应类型:responseType: 'arraybuffer'

五、效果展示


六、安全性设计

风险防御方案二维码盗用30秒短时效性 + 单次有效性验证接口暴力请求限流策略(如Guava RateLimiter)中间人攻击全站HTTPS + 数据签名XSS攻击前端输入过滤 + CSP安全策略七、总结

关键技术栈:

[*]前端定时器 + 二进制流处理
[*]后端二维码生成 + 状态管理
[*]高效的缓存策略
最佳实践建议:

[*]始终为二维码添加时效性和唯一性标识
[*]敏感操作需二次确认(如扫码后的授权确认)
[*]监控二维码使用率,优化生成策略
通过前后端协作,动态二维码既能提升用户体验,又能有效保障系统安全性。
最后文章有啥不对,欢迎大佬在评论区指点!!!
如果感觉对你有帮助就点赞推荐或者关注一下吧!!!


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 如何通过接口实现动态二维码的定时刷新