一、背景与配置
在微信小程序开发中,AccessToken 是调用所有微信API的全局凭证(有效期2小时),而小程序码则是推广业务的重要载体。首先需要在配置文件中设置关键参数:
# application.yml 配置
wx:
appid: wx64b5b88bd3013081
secret: 978c53ac1d6d1acaef7d1ef88e15de09
access-token-key: wx:access_token # Redis存储键名
二、AccessToken获取与缓存实现
核心流程
三、动态生成小程序码
请求参数说明
参数名 | 必填 | 说明 |
---|---|---|
scene | 是 | 场景值(最长32字符),用于携带业务参数(如:orderId=123 ) |
page | 否 | 页面路径(如:pages/order/detail ),需去除开头的/ |
width | 否 | 二维码宽度(默认430px) |
check_path | 否 | 检查页面是否存在(上线必须设为true) |
env_version | 否 | 环境版本(develop开发版/trial体验版/release正式版) |
核心配置
# 微信小程序配置
wx:
appid: wx64b5b88bd3013081
secret: 978c53ac1d6d1acaef7d1ef88e15de09
# Redis中存储access_token的键名
access-token-key: wx:access_token
核心实现代码
/**
*
* @author DaFu
* @className WechatMiniUtils
* @date 2025/6/18
*/
@Slf4j
@Component
public class WeChatUtils {
@Value("${wx.appid}")
private String appid;
@Value("${wx.secret}")
private String secret;
@Value("${wx.access-token-key}")
private String accessTokenKey;
/**
* 接口调用凭据
*/
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";
/**
* 接口不限制的小程序码
*/
private static final String WX_ACO_DE_UN_LIMIT_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=";
@Autowired
private RedisCache redisCache;
/**
* 获取并缓存 AccessToken
*
* @author DaFu
*/
public String getAccessToken() {
String accessToken = redisCache.getCacheObject(accessTokenKey);
if (StringUtils.isNotBlank(accessToken)) {
return accessToken;
}
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(
ACCESS_TOKEN_URL, String.class, appid, secret
);
if (response.getStatusCode() == HttpStatus.OK) {
JSONObject json = JSONObject.parseObject(response.getBody());
if (json.containsKey("access_token")) {
accessToken = json.getString("access_token");
int expiresIn = json.getIntValue("expires_in");
redisCache.setCacheObject(
accessTokenKey,
accessToken,
expiresIn - 120,
TimeUnit.SECONDS
);
return accessToken;
} else {
throw new RuntimeException("获取access_token失败: " + json.getString("errmsg"));
}
}
throw new RuntimeException("微信接口调用失败");
}
/**
* 生成不限制的小程序码
*
* @param scene 场景值(最大32字符)
* @param page 页面路径(如:pages/index/index)
* @return 小程序码的二进制数据
*/
public byte[] getUnLimitQrCode(String scene, String page) {
String accessToken = getAccessToken();
String url = WX_ACO_DE_UN_LIMIT_URL + accessToken;
Map<String, Object> body = new HashMap<>();
body.put("scene", StringUtils.defaultString(scene, ""));
if (StringUtils.isNotEmpty(page)) {
page = page.startsWith("/") ? page.substring(1) : page;
}
body.put("page", StringUtils.defaultString(page, ""));
body.put("width", 430);
//TODO 上线必须是true
body.put("check_path", false);
//TODO 上线正式版为 "release"
body.put("env_version", "develop");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, 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 = JSONObject.parseObject(jsonResponse);
if (json.containsKey("errcode")) {
int errcode = json.getIntValue("errcode");
String errmsg = json.getString("errmsg");
log.error("生成小程序码失败: errcode={}, errmsg={}", errcode, errmsg);
if (errcode == 40001 || errcode == 42001) {
redisCache.deleteObject(accessTokenKey);
return getUnLimitQrCode(scene, page);
}
}
}
return responseBody;
}
throw new RuntimeException("生成小程序码失败: " + new String(response.getBody()));
}
}
四、使用示例
生成订单详情页小程序码
@RestController
public class QrCodeController {
@Autowired
private WeChatUtils weChatUtils;
@GetMapping("/order/qrcode")
public void getOrderQrCode(HttpServletResponse response) throws IOException {
// 生成携带订单ID的小程序码
byte[] qrCode = weChatUtils.getUnLimitQrCode(
"orderId=202507011234",
"pages/order/detail"
);
// 输出图片流
response.setContentType("image/jpeg");
OutputStream os = response.getOutputStream();
os.write(qrCode);
os.flush();
}
}
五、常见问题排查
-
AccessToken获取失败
-
检查
appid
/secret
是否正确 -
确认服务器IP已加入微信白名单
-
查看微信返回的完整错误信息
-
-
小程序码生成报错
-
-
{"errcode":41030,"errmsg":"invalid page hint"}
检查
page
路径是否存在(上线前开启check_path
) -
确认页面路径格式正确(如:
pages/index/index
)
-
-
缓存异常处理
-
Redis不可用时增加降级策略(如本地缓存)
-
监控Token获取频率,避免超出限额
-
总结
本文完整实现了微信小程序开发中的两个关键功能:
-
通过 Redis缓存+提前刷新 策略保障AccessToken的高可用性
-
采用 错误重试+递归调用 机制处理小程序码生成异常
特别提醒:上线前务必修改check_path=true
和env_version=release
,否则可能导致生成无效二维码。