Thực Chiến Phát Triển Mini Program WeChat: Quản Lý AccessToken và Tạo Mã Mini Program

I. Bối Cảnh và Cấu Hình

Trong quá trình phát triển Mini Program WeChat, AccessToken là chứng thực toàn cầu để gọi tất cả API của WeChat (hạn sử dụng 2 giờ), trong khi Mã Mini Program là phương tiện quan trọng để quảng bá dịch vụ. Đầu tiên, cần thiết lập các tham số quan trọng trong tệp cấu hình:

# application.yml cấu hình
wx:
  appId: wx64b5b88bd3013081
  appSecret: 978c53ac1d6d1acaef7d1ef88e15de09
  access-token-key: wx:access_token  # Tên khóa lưu trữ trong Redis

II. Triển Khai Lấy và Lưu Trữ AccessToken

Quy Trình Cốt Lõi

III. Tạo Động Mã Mini Program

Tham Số Yêu Cầu
Tên Tham Số Bắt Buộc Mô Tả
scene Giá值场景 (tối đa 32 ký tự), dùng để mang tham số kinh doanh (ví dụ: orderId=123)
page Không Đường dẫn trang (ví dụ: pages/order/detail), cần bỏ ký tự / ở đầu
width Không Chiều rộng mã QR (mặc định 430px)
check\_path Không Kiểm tra sự tồn tại của trang (**bắt buộc đặt là true khi triển khai**)
env\_version Không Phiên bản môi trường (**develop bản phát triển/trial bản trải nghiệm/release bản chính thức**)
Cấu Hình Cốt Lõi
# Cấu hình Mini Program WeChat
wx:
   appId: wx64b5b88bd3013081
   appSecret: 978c53ac1d6d1acaef7d1ef88e15de09
   # Tên khóa lưu trữ access_token trong Redis
   access-token-key: wx:access_token
Triển Khai Mã Nguồn Cốt Lõi
/**
 *
 * @author MinhDev
 * @className WeChatMiniProgramHelper
 * @date 2023/11/15
 */
@Slf4j
@Component
public class WeChatMiniProgramHelper {
    @Value("${wx.appId}")
    private String appId;

    @Value("${wx.appSecret}")
    private String appSecret;

    @Value("${wx.access-token-key}")
    private String accessTokenKey;
    
    /**
     * URL lấy access token
     */
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";
    
    /**
     * API tạo mã Mini Program không giới hạn
     */
    private static final String WX_CODE_UNLIMIT_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * Lấy và lưu trữ AccessToken
     *
     * @author MinhDev
     */
    public String fetchAccessToken() {
        String accessToken = redisTemplate.opsForValue().get(accessTokenKey);
        if (StringUtils.isNotBlank(accessToken)) {
            return accessToken;
        }
        
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(
                ACCESS_TOKEN_URL, String.class, appId, appSecret
        );
        
        if (response.getStatusCode() == HttpStatus.OK) {
            JSONObject json = JSON.parseObject(response.getBody());
            if (json.containsKey("access_token")) {
                accessToken = json.getString("access_token");
                int expiresIn = json.getIntValue("expires_in");
                redisTemplate.opsForValue().set(
                        accessTokenKey,
                        accessToken,
                        expiresIn - 120,
                        TimeUnit.SECONDS
                );
                return accessToken;
            } else {
                throw new RuntimeException("Lấy access_token thất bại: " + json.getString("errmsg"));
            }
        }
        throw new RuntimeException("Gọi API WeChat thất bại");
    }

    /**
     * Tạo mã Mini Program không giới hạn
     *
     * @param scene Giá值场景 (tối đa 32 ký tự)
     * @param page Đường dẫn trang (ví dụ: pages/index/index)
     * @return Dữ liệu nhị phân của mã Mini Program
     */
    public byte[] generateUnlimitedQrCode(String scene, String page) {
        String accessToken = fetchAccessToken();
        String url = WX_CODE_UNLIMIT_URL + accessToken;
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("scene", StringUtils.defaultString(scene, ""));
        if (StringUtils.isNotEmpty(page)) {
            page = page.startsWith("/") ? page.substring(1) : page;
        }
        requestBody.put("page", StringUtils.defaultString(page, ""));
        requestBody.put("width", 430);
        //TODO Khi triển khai phải đặt là true
        requestBody.put("check_path", false);
        //TODO Khi triển khai bản chính thức phải đặt là "release"
        requestBody.put("env_version", "develop");
        
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
        
        ResponseEntity<byte[]> response = restTemplate.postForEntity(
                url, request, byte[].class
        );
        
        byte[] responseBody = response.getBody();
        if (response.getStatusCode() == HttpStatus.OK) {
            if (responseBody.length > 1 && responseBody[0] == '{') {
                String jsonResponse = new String(responseBody, StandardCharsets.UTF_8);
                JSONObject json = JSON.parseObject(jsonResponse);
                if (json.containsKey("errcode")) {
                    int errcode = json.getIntValue("errcode");
                    String errmsg = json.getString("errmsg");
                    log.error("Tạo mã Mini Program thất bại: errcode={}, errmsg={}", errcode, errmsg);
                    if (errcode == 40001 || errcode == 42001) {
                        redisTemplate.delete(accessTokenKey);
                        return generateUnlimitedQrCode(scene, page);
                    }
                }
            }
            return responseBody;
        }
        throw new RuntimeException("Tạo mã Mini Program thất bại: " + new String(response.getBody()));
    }
}

IV. Ví Dùng Sử Dụng

Tạo mã Mini Program cho trang chi tiết đơn hàng
@RestController
public class MiniProgramCodeController {
    @Autowired
    private WeChatMiniProgramHelper weChatMiniProgramHelper;

    @GetMapping("/order/qrcode")
    public void generateOrderQrCode(HttpServletResponse response) throws IOException {
        // Tạo mã Mini Program chứa ID đơn hàng
        byte[] qrCode = weChatMiniProgramHelper.generateUnlimitedQrCode(
            "orderId=202311151234", 
            "pages/order/detail"
        );
        
        // Xuất luồng ảnh
        response.setContentType("image/jpeg");
        OutputStream os = response.getOutputStream();
        os.write(qrCode);
        os.flush();
    }
}

V. Xử Lý Vấn Đề Thường Gặp

  1. Lấy AccessToken thất bại
  • Kiểm tra appId/appSecret có chính xác không
  • Xác nhận IP máy chủ đã được thêm vào danh sách trắng của WeChat
  • Xem thông tin lỗi đầy đủ trả về từ WeChat
  1. Lỗi tạo mã Mini Program
{"errcode":41030,"errmsg":"invalid page hint"}
  • Kiểm tra đường dẫn trang có tồn tại không (kích hoạt check_path trước khi triển khai)
  • Xác định định dạng đường dẫn trang chính xác (ví dụ: pages/index/index)
  1. Xử lý sự cố lưu trữ
  • Thêm chiến lược dự phòng khi Redis không khả dụng (ví dụ: lưu trữ cục bộ)
  • Theo dõi tần suất lấy Token để tránh vượt quá giới hạn

Tổng Kết

Bài viết đã triển khai hoàn thiện hai chức năng quan trọng trong phát triển Mini Program WeChat:

  1. Chiến lược Redis lưu trữ + làm mới sớm đảm bảo tính sẵn có cao của AccessToken
  2. Cơ chế xử lý lỗi + gọi đệ quy để xử lý ngoại lệ khi tạo mã Mini Program

Lưu ý đặc biệt: Trước khi triển khai, phải thay đổi check_path=trueenv_version=release, nếu không có thể dẫn đến tạo mã QR không hợp lệ.

Thẻ: Mini Program WeChat AccessToken Redis QR Code Generation Java Spring Boot

Đăng vào ngày 22 tháng 6 lúc 02:38