From ebca091a83a9a32b0724999fbbcc63043fdd76b7 Mon Sep 17 00:00:00 2001 From: quyixiao <2621048238@qq.com> Date: Wed, 12 Nov 2025 13:04:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/heyu/api/data/utils/WeChatUtils.java | 144 ++++++++++++------ .../request/vv/AppShortLinkRequest.java | 11 ++ .../api/controller/vv/AppLinkController.java | 16 ++ .../controller/vv/AppQrCodeController.java | 4 +- 4 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 api-third/src/main/java/com/heyu/api/alibaba/request/vv/AppShortLinkRequest.java diff --git a/api-mapper/src/main/java/com/heyu/api/data/utils/WeChatUtils.java b/api-mapper/src/main/java/com/heyu/api/data/utils/WeChatUtils.java index a9dc749..55879f6 100644 --- a/api-mapper/src/main/java/com/heyu/api/data/utils/WeChatUtils.java +++ b/api-mapper/src/main/java/com/heyu/api/data/utils/WeChatUtils.java @@ -37,6 +37,8 @@ public class WeChatUtils { public static final String wechatQrcodeUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token="; + public static final String genwxashortlinkUrl= "https://api.weixin.qq.com/wxa/genwxashortlink?access_token="; + @Autowired private RedisUtils redisUtils; @@ -48,6 +50,14 @@ public class WeChatUtils { @Value("${eb.config.weixin.pay.appSecret}") private String appSecret; + /*** + * 重试次数 + */ + public final static int try_count = 3; + + public final static String access_token_is_invalid = "access_token is invalid"; + + public static final String jsapi_acessToken = "jsapi_acessToken"; @@ -88,29 +98,23 @@ public class WeChatUtils { return null; } - public AccessTokenDTO getAccessTokenDTO() { + public AccessTokenDTO getAccessTokenDTO(boolean useCache) { AccessTokenDTO accessTokenDTO = null; String acessTokenJson = redisUtils.get(jsapi_acessToken); - if (StringUtils.isNotEmpty(acessTokenJson)) { + if (StringUtils.isNotEmpty(acessTokenJson) && useCache) { + log.info("getAccessTokenDTO redis acessToken :{}", acessTokenJson); accessTokenDTO = JSONObject.parseObject(acessTokenJson, AccessTokenDTO.class); } else { - accessTokenDTO = getAccessToken(appid, appSecret); + String url = String.format(wechatAccessTokenUrl, appid, appSecret); + String responseBody = HttpUtils.doGet(url); + log.info("getAccessToken responseBody:{}", responseBody); + accessTokenDTO = JSONObject.parseObject(responseBody, AccessTokenDTO.class); redisUtils.set(jsapi_acessToken, JSON.toJSONString(accessTokenDTO), RedisUtils.one_hours); } return accessTokenDTO; } - // 获取access_token - private AccessTokenDTO getAccessToken(String appid, String appSecret) { - String url = String.format(wechatAccessTokenUrl, appid, appSecret); - String responseBody = HttpUtils.doGet(url); - log.info("getAccessToken responseBody:{}", responseBody); - AccessTokenDTO accessTokenDTO = JSONObject.parseObject(responseBody, AccessTokenDTO.class); - return accessTokenDTO; - } - - /** * 生成小程序跳转短链 code * https://blog.csdn.net/weixin_44548582/article/details/131868367 @@ -122,23 +126,29 @@ public class WeChatUtils { * @return */ public String jumpAppletShortUrl(String path, String query, Integer days) { - String accessToken = getAccessTokenDTO().getAccessToken(); + for (int i = 0; i < try_count; i++) { + String accessToken = getAccessTokenDTO(true).getAccessToken(); + String url = linkUrl + accessToken; + cn.hutool.json.JSONObject body = cn.hutool.json.JSONUtil.createObj(); + body.putOpt("path", path); // 通过 URL Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query 。path 为空时会跳转小程序主页 + body.putOpt("query", query); // 通过 URL Link 进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~% + //链接过期类型:0时间戳 1间隔天数 + body.putOpt("expire_type", 1); // 默认值0.小程序 URL Link 失效类型,失效时间:0,失效间隔天数:1 - String url = linkUrl + accessToken; - cn.hutool.json.JSONObject body = cn.hutool.json.JSONUtil.createObj(); - body.putOpt("path", path); // 通过 URL Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query 。path 为空时会跳转小程序主页 - body.putOpt("query", query); // 通过 URL Link 进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~% - //链接过期类型:0时间戳 1间隔天数 - body.putOpt("expire_type", 1); // 默认值0.小程序 URL Link 失效类型,失效时间:0,失效间隔天数:1 + //指定失效天数,最多30 + days = (days == null || days > 30) ? 30 : days; + body.putOpt("expire_interval", days); // 到期失效的URL Link的失效间隔天数。生成的到期失效URL Link在该间隔时间到达前有效。最长间隔天数为30天。expire_type 为 1 必填 - //指定失效天数,最多30 - days = (days == null || days > 30) ? 30 : days; - body.putOpt("expire_interval", days); // 到期失效的URL Link的失效间隔天数。生成的到期失效URL Link在该间隔时间到达前有效。最长间隔天数为30天。expire_type 为 1 必填 - - body.putOpt("env_version", "trial"); // 默认值"release"。要打开的小程序版本。正式版为 "release",体验版为"trial",开发版为"develop",仅在微信外打开时生效。 - String result = HttpUtil.post(url, body.toJSONString(2)); - log.info("jumpAppletShortUrl params:{}, result:{}", JSON.toJSONString(body), result); - return result; + body.putOpt("env_version", "trial"); // 默认值"release"。要打开的小程序版本。正式版为 "release",体验版为"trial",开发版为"develop",仅在微信外打开时生效。 + String result = HttpUtil.post(url, body.toJSONString(2)); + log.info("jumpAppletShortUrl url:{} params:{}, result:{}", url, JSON.toJSONString(body), result); + if (StringUtils.isNotEmpty(result) && result.contains(access_token_is_invalid)) { + getAccessTokenDTO(false); + continue; + } + return result; + } + return null; } @@ -153,26 +163,70 @@ public class WeChatUtils { * @return */ public String jumpAppletSchemeUrl(String path, String query, Integer days) { - String url = schemeUrl + getAccessTokenDTO().getAccessToken(); - cn.hutool.json.JSONObject body = cn.hutool.json.JSONUtil.createObj(); - cn.hutool.json.JSONObject jumpWxa = cn.hutool.json.JSONUtil.createObj(); - jumpWxa.putOpt("path", path); // 通过 scheme 码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query。path 为空时会跳转小程序主页。 - jumpWxa.putOpt("query", query); // 通过 scheme 码进入小程序时的 query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~%` - jumpWxa.putOpt("env_version", "release"); // 默认值"release"。要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop",仅在微信外打开时生效。 - body.putOpt("jump_wxa", jumpWxa); // 跳转到的目标小程序信息。 - //链接过期类型:0时间戳 1间隔天数 - body.putOpt("expire_type", 1); // 默认值0,到期失效的 scheme 码失效类型,失效时间:0,失效间隔天数:1 - body.putOpt("is_expire", true); - //指定失效天数,最多30 - days = (days == null || days > 30) ? 30 : days; - body.putOpt("expire_interval", days); // 到期失效的 scheme 码的失效间隔天数。生成的到期失效 scheme 码在该间隔时间到达前有效。最长间隔天数为30天。is_expire 为 true 且 expire_type 为 1 时必填 - String post = HttpUtil.post(url, body.toJSONString(2)); - log.info("jumpAppletSchemeUrl params:{}, result:{}", JSON.toJSONString(body), post); - cn.hutool.json.JSONObject result = cn.hutool.json.JSONUtil.parseObj(post); - return result.toJSONString(2); + for(int i = 0 ; i < try_count ;i ++){ + String url = schemeUrl + getAccessTokenDTO(true).getAccessToken(); + cn.hutool.json.JSONObject body = cn.hutool.json.JSONUtil.createObj(); + cn.hutool.json.JSONObject jumpWxa = cn.hutool.json.JSONUtil.createObj(); + jumpWxa.putOpt("path", path); // 通过 scheme 码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query。path 为空时会跳转小程序主页。 + jumpWxa.putOpt("query", query); // 通过 scheme 码进入小程序时的 query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~%` + jumpWxa.putOpt("env_version", "release"); // 默认值"release"。要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop",仅在微信外打开时生效。 + body.putOpt("jump_wxa", jumpWxa); // 跳转到的目标小程序信息。 + //链接过期类型:0时间戳 1间隔天数 + body.putOpt("expire_type", 1); // 默认值0,到期失效的 scheme 码失效类型,失效时间:0,失效间隔天数:1 + body.putOpt("is_expire", true); + //指定失效天数,最多30 + days = (days == null || days > 30) ? 30 : days; + body.putOpt("expire_interval", days); // 到期失效的 scheme 码的失效间隔天数。生成的到期失效 scheme 码在该间隔时间到达前有效。最长间隔天数为30天。is_expire 为 true 且 expire_type 为 1 时必填 + String post = HttpUtil.post(url, body.toJSONString(2)); + + + if(StringUtils.isNotEmpty(post) && post.contains(access_token_is_invalid)){ + getAccessTokenDTO(false); + continue; + } + + log.info("jumpAppletSchemeUrl url:{} params:{}, result:{}",url, JSON.toJSONString(body), post); + cn.hutool.json.JSONObject result = cn.hutool.json.JSONUtil.parseObj(post); + return result.toJSONString(2); + + } + + + return null; + } + + /** + * scheme 跳转微信小程序,需中转H5 + * + *https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/short-link/generateShortLink.html + * @return + */ + public String genShortlink(String path, String query, Integer days) { + String resul = null; + for(int i = 0 ; i < try_count ;i ++){ + String url = genwxashortlinkUrl + getAccessTokenDTO(true).getAccessToken(); + cn.hutool.json.JSONObject body = cn.hutool.json.JSONUtil.createObj(); + body.putOpt("page_url", path); //通过 Short Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,可携带 query,最大1024个字符 + body.putOpt("page_title", query); // 页面标题,不能包含违法信息,超过20字符会用... 截断代替 + body.putOpt("is_permanent", "false"); // 默认值false。生成的 Short Link 类型,短期有效:false,永久有效:true + + String post = HttpUtil.post(url, body.toJSONString(2)); + log.info("genShortlink url:{} params:{}, result:{}",url, JSON.toJSONString(body), url); + if(StringUtils.isNotEmpty(post) && post.contains(access_token_is_invalid)){ + getAccessTokenDTO(false); + continue; + } + log.info("genShortlink params:{}, result:{}", JSON.toJSONString(body), post); + cn.hutool.json.JSONObject result = cn.hutool.json.JSONUtil.parseObj(post); + return result.toJSONString(2); + + } + return null; + } + } diff --git a/api-third/src/main/java/com/heyu/api/alibaba/request/vv/AppShortLinkRequest.java b/api-third/src/main/java/com/heyu/api/alibaba/request/vv/AppShortLinkRequest.java new file mode 100644 index 0000000..eacded0 --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/alibaba/request/vv/AppShortLinkRequest.java @@ -0,0 +1,11 @@ +package com.heyu.api.alibaba.request.vv; + + +import lombok.Data; + +@Data +public class AppShortLinkRequest extends AppBaseRequest { + + + +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppLinkController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppLinkController.java index 017e518..270e795 100644 --- a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppLinkController.java +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppLinkController.java @@ -3,6 +3,7 @@ package com.heyu.api.controller.vv; import com.heyu.api.alibaba.request.vv.AppLinkRequest; import com.heyu.api.alibaba.request.vv.AppSchemeRequest; +import com.heyu.api.alibaba.request.vv.AppShortLinkRequest; import com.heyu.api.common.annotation.Describe; import com.heyu.api.data.utils.R; import com.heyu.api.data.utils.WeChatUtils; @@ -38,6 +39,21 @@ public class AppLinkController { } + + /*** + * 短链接 + * http://localhost:8888/app/create/shortLink + */ + @Describe("生成") + @RequestMapping("/shortLink") + public R shortlink(@RequestBody AppShortLinkRequest request) { + String url = weChatUtils.genShortlink("abc", "89ew8392", 30); + return R.ok(); + } + + + + /*** * 链接 * http://localhost:8888/app/create/scheme diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppQrCodeController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppQrCodeController.java index b0b52ac..46a32cc 100644 --- a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppQrCodeController.java +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppQrCodeController.java @@ -46,18 +46,16 @@ public class AppQrCodeController { @RequestMapping("/create") public R create(@RequestBody AppQrCodeRequest request) { - AccessTokenDTO accessTokenDTO = weChatUtils.getAccessTokenDTO(); + AccessTokenDTO accessTokenDTO = weChatUtils.getAccessTokenDTO(true); // 当前登录账号 String accountId = "123456"; String url = WeChatUtils.wechatQrcodeUrl + accessTokenDTO.getAccessToken(); Map body = new HashMap<>(); - // 场景码,请根据业务场景与【前端】约定 // 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) body.put("scene", String.format("a=%s", accountId)); - body.put("env_version", envVersion); // body.put("page", envVersion); 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置 // 透明