文章目录
- 前言
前言
- 工具类 - WxJsapiSignature.java
importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;importjava.util.UUID;publicclassWxJsapiSignature{/** * 生成微信 JS-SDK 签名 * @param jsapiTicket 微信 jsapi_ticket * @param url 当前页面 URL(不包含 # 后面的部分) * @return 签名信息对象 */publicstaticSignatureResultgenerateSignature(StringjsapiTicket,Stringurl){// 生成随机字符串StringnonceStr=createNonceStr();// 生成时间戳(秒)longtimestamp=System.currentTimeMillis()/1000;// 按字典序拼接字符串Stringstring1="jsapi_ticket="+jsapiTicket+"&noncestr="+nonceStr+"×tamp="+timestamp+"&url="+url;// SHA1 加密Stringsignature=sha1(string1);// 返回结果SignatureResultresult=newSignatureResult();result.setNonceStr(nonceStr);result.setTimestamp(timestamp);result.setSignature(signature);returnresult;}/** * SHA1 加密 */privatestaticStringsha1(Stringstr){try{MessageDigestdigest=MessageDigest.getInstance("SHA-1");digest.update(str.getBytes());byte[]messageDigest=digest.digest();// 转换为十六进制字符串StringBuilderhexString=newStringBuilder();for(byteb:messageDigest){Stringhex=Integer.toHexString(0xFF&b);if(hex.length()==1){hexString.append('0');}hexString.append(hex);}returnhexString.toString();}catch(NoSuchAlgorithmExceptione){thrownewRuntimeException("SHA1 加密失败",e);}}/** * 生成随机字符串 */privatestaticStringcreateNonceStr(){returnUUID.randomUUID().toString().replace("-","").substring(0,16);}}- 签名结果类 - SignatureResult.java
publicclassSignatureResult{privateStringnonceStr;privateLongtimestamp;privateStringsignature;// Getters and SetterspublicStringgetNonceStr(){returnnonceStr;}publicvoidsetNonceStr(StringnonceStr){this.nonceStr=nonceStr;}publicLonggetTimestamp(){returntimestamp;}publicvoidsetTimestamp(Longtimestamp){this.timestamp=timestamp;}publicStringgetSignature(){returnsignature;}publicvoidsetSignature(Stringsignature){this.signature=signature;}}- Controller 示例
importorg.springframework.web.bind.annotation.*;@RestController@RequestMapping("/wechat")publicclassWechatController{@AutowiredprivateWechatServicewechatService;/** * 获取微信 JS-SDK 签名 */@PostMapping("/jsapi-signature")publicResultgetJsapiSignature(@RequestBodySignatureRequestrequest){try{Stringurl=request.getUrl();// 1. 获取 access_token(需要缓存,有效期 7200 秒)StringaccessToken=wechatService.getAccessToken();// 2. 获取 jsapi_ticket(需要缓存,有效期 7200 秒)StringjsapiTicket=wechatService.getJsapiTicket(accessToken);// 3. 生成签名SignatureResultsignature=WxJsapiSignature.generateSignature(jsapiTicket,url);// 4. 返回结果Map<String,Object>data=newHashMap<>();data.put("appId",wechatService.getAppId());data.put("timestamp",signature.getTimestamp());data.put("nonceStr",signature.getNonceStr());data.put("signature",signature.getSignature());returnResult.success(data);}catch(Exceptione){returnResult.error("获取签名失败: "+e.getMessage());}}}- Service 示例(获取 access_token 和 jsapi_ticket)
importorg.springframework.stereotype.Service;importorg.springframework.web.client.RestTemplate;importcom.alibaba.fastjson.JSONObject;@ServicepublicclassWechatService{@Value("${wechat.appId}")privateStringappId;@Value("${wechat.appSecret}")privateStringappSecret;@AutowiredprivateRestTemplaterestTemplate;// 缓存 access_token(实际项目中应该用 Redis 缓存)privateStringcachedAccessToken;privatelongaccessTokenExpireTime;// 缓存 jsapi_ticketprivateStringcachedJsapiTicket;privatelongjsapiTicketExpireTime;/** * 获取 access_token */publicStringgetAccessToken(){// 检查缓存if(cachedAccessToken!=null&&System.currentTimeMillis()<accessTokenExpireTime){returncachedAccessToken;}// 请求微信接口Stringurl="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret="+appSecret;Stringresponse=restTemplate.getForObject(url,String.class);JSONObjectjson=JSONObject.parseObject(response);if(json.containsKey("access_token")){cachedAccessToken=json.getString("access_token");// 提前 5 分钟过期accessTokenExpireTime=System.currentTimeMillis()+(json.getInteger("expires_in")-300)*1000;returncachedAccessToken;}else{thrownewRuntimeException("获取 access_token 失败: "+response);}}/** * 获取 jsapi_ticket */publicStringgetJsapiTicket(StringaccessToken){// 检查缓存if(cachedJsapiTicket!=null&&System.currentTimeMillis()<jsapiTicketExpireTime){returncachedJsapiTicket;}// 请求微信接口Stringurl="https://api.weixin.qq.com/cgi-bin/ticket/getjsapi_ticket?access_token="+accessToken;Stringresponse=restTemplate.getForObject(url,String.class);JSONObjectjson=JSONObject.parseObject(response);if(json.getInteger("errcode")==0){cachedJsapiTicket=json.getString("ticket");// 提前 5 分钟过期jsapiTicketExpireTime=System.currentTimeMillis()+(json.getInteger("expires_in")-300)*1000;returncachedJsapiTicket;}else{thrownewRuntimeException("获取 jsapi_ticket 失败: "+response);}}publicStringgetAppId(){returnappId;}}- 请求对象
publicclassSignatureRequest{privateStringurl;publicStringgetUrl(){returnurl;}publicvoidsetUrl(Stringurl){this.url=url;}}- application.yml 配置
wechat: appId: wxf123456789 appSecret: your_app_secret_here关键点:
SHA1 加密必须转换为小写十六进制字符串
参数拼接顺序必须是字典序:jsapi_ticket、noncestr、timestamp、url
URL 不能 encode,保持原样
access_token 和 jsapi_ticket 必须缓存,避免频繁请求导致限流
时间戳单位是秒,不是毫秒