├── src ├── main │ ├── resources │ │ ├── static │ │ │ ├── MP_verify_B0vMQLCguxRzP1Rc.txt │ │ │ └── demo.html │ │ ├── application-dev.yml │ │ ├── application-prod.yml │ │ ├── application-test.yml │ │ ├── config │ │ │ └── application.yml │ │ └── logback-spring.xml │ └── java │ │ └── net │ │ └── javadog │ │ └── springbootwexin │ │ ├── SpringbootWexinApplication.java │ │ ├── service │ │ └── WxService.java │ │ ├── controller │ │ └── WxInitController.java │ │ ├── common │ │ └── AjaxJson.java │ │ └── utils │ │ └── WxUtil.java └── test │ └── java │ └── net │ └── javadog │ └── springbootwexin │ └── SpringbootWexinApplicationTests.java ├── pom.xml └── README.md /src/main/resources/static/MP_verify_B0vMQLCguxRzP1Rc.txt: -------------------------------------------------------------------------------- 1 | B0vMQLCguxRzP1Rc -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | # 开发环境配置 2 | spring: 3 | profiles: dev 4 | 5 | #端口设置 6 | server: 7 | port: 8000 8 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | # 生产环境配置 2 | spring: 3 | profiles: prod 4 | 5 | #端口设置 6 | server: 7 | port: 8002 -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | # 测试环境配置 2 | spring: 3 | profiles: test 4 | 5 | #端口设置 6 | server: 7 | port: 8001 -------------------------------------------------------------------------------- /src/test/java/net/javadog/springbootwexin/SpringbootWexinApplicationTests.java: -------------------------------------------------------------------------------- 1 | package net.javadog.springbootwexin; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringbootWexinApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/javadog/springbootwexin/SpringbootWexinApplication.java: -------------------------------------------------------------------------------- 1 | package net.javadog.springbootwexin; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringbootWexinApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringbootWexinApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | #激活配置文件 4 | active: prod 5 | #配置静态资源路径 6 | resources: 7 | static-locations: classpath:/static/ 8 | 9 | #日志相关 10 | logging: 11 | #配置文件日志路径 12 | config: classpath:logback-spring.xml 13 | 14 | #微信相关配置 15 | wx: 16 | #appId (到时候换成自己公众号的) 17 | appId: wx4ad618620f8c3528 18 | #appSecret(到时候换成自己公众号的) 19 | appSecret: b772c7863b29e270aa86e40f9b9e6215 20 | #参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token) 21 | jssdk_accesstoken_url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 22 | #用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket) 23 | jssdk_getticket_url: https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi -------------------------------------------------------------------------------- /src/main/java/net/javadog/springbootwexin/service/WxService.java: -------------------------------------------------------------------------------- 1 | package net.javadog.springbootwexin.service; 2 | 3 | import lombok.Getter; 4 | import net.javadog.springbootwexin.utils.WxUtil; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Service; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | /** 12 | * 一个低端小气没档次的程序狗 JavaDog 13 | * blog.javadog.net 14 | * 15 | * @BelongsProject: springboot-wexin 16 | * @BelongsPackage: net.javadog.springbootwexin.service 17 | * @Author: hdx 18 | * @CreateTime: 2020-02-14 20:43 19 | * @Description: 微信相关service 20 | */ 21 | @Service 22 | public class WxService { 23 | @Getter 24 | private static String AppId; 25 | @Value("${wx.appId}") 26 | public void setAppId(String appId) { 27 | AppId = appId; 28 | } 29 | /** 30 | *@Author: hdx 31 | *@CreateTime: 20:46 2020/2/14 32 | *@param: shareUrl 分享的url 33 | *@Description: 初始化JSSDKConfig 34 | */ 35 | public Map initJSSDKConfig(String url) throws Exception { 36 | //获取AccessToken 37 | String accessToken = WxUtil.getJSSDKAccessToken(); 38 | //获取JssdkGetticket 39 | String jsapiTicket = WxUtil.getJssdkGetticket(accessToken); 40 | String timestamp = Long.toString(System.currentTimeMillis() / 1000); 41 | String nonceStr = UUID.randomUUID().toString(); 42 | String signature = WxUtil.buildJSSDKSignature(jsapiTicket,timestamp,nonceStr,url); 43 | Map map = new HashMap(); 44 | map.put("url", url); 45 | map.put("jsapi_ticket", jsapiTicket); 46 | map.put("nonceStr", nonceStr); 47 | map.put("timestamp", timestamp); 48 | map.put("signature", signature); 49 | map.put("appid", AppId); 50 | return map; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/javadog/springbootwexin/controller/WxInitController.java: -------------------------------------------------------------------------------- 1 | package net.javadog.springbootwexin.controller; 2 | import net.javadog.springbootwexin.common.AjaxJson; 3 | import net.javadog.springbootwexin.service.WxService; 4 | import net.javadog.springbootwexin.utils.WxUtil; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import java.util.Map; 12 | 13 | /** 14 | * 一个低端小气没档次的程序狗 JavaDog 15 | * blog.javadog.net 16 | * 17 | * @BelongsProject: springboot-wexin 18 | * @BelongsPackage: net.javadog.springbootwexin.controller 19 | * @Author: hdx 20 | * @CreateTime: 2020-02-14 14:52 21 | * @Description: 微信初始化接入Controller控制器 22 | */ 23 | @RestController 24 | @RequestMapping("/weixin") 25 | public class WxInitController { 26 | protected Logger logger = LoggerFactory.getLogger(getClass()); 27 | 28 | @Autowired 29 | private WxService wxService; 30 | 31 | /** 32 | *@Author: hdx 33 | *@CreateTime: 20:39 2020/2/14 34 | *@param: shareUrl 分享url地址 35 | *@Description: 初始化微信JSSDK Config信息 36 | 1.先通过appId和appSecret参数请求指定微信地址 获取AccessToken 37 | 2.在通过第一步中的AccessToken作为参数请求微信地址 获取jsapi_ticket临时票据(此处不考虑调用频率,使用者根据情况放入缓存或定时任务) 38 | 3.通过第二步的JssdkGetticket和timestamp,nonceStr,url作为参数请求微信地址,获取签名signature 39 | 4.将第三步获得的signature和jsapi_ticket,nonceStr,timestamp,url返回给前端,作为Config初始化验证的信息 40 | */ 41 | @RequestMapping("/initWXJSSDKConfigInfo") 42 | public AjaxJson initWXJSConfig (@RequestParam(required = false) String url) throws Exception{ 43 | logger.info("url=" + url); 44 | String json = ""; 45 | try { 46 | Map map = wxService.initJSSDKConfig(url); 47 | json = WxUtil.mapToJson(map); 48 | }catch (Exception e){ 49 | AjaxJson.fail(e.getMessage()); 50 | } 51 | return AjaxJson.ok(json); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/static/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 测试jssdk 8 | 9 | 10 | 11 | 12 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.4.RELEASE 9 | 10 | 11 | net.javadog 12 | springboot-wexin 13 | 0.0.1-SNAPSHOT 14 | springboot-wexin 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | org.junit.vintage 34 | junit-vintage-engine 35 | 36 | 37 | 38 | 39 | 40 | com.google.code.gson 41 | gson 42 | 2.6.2 43 | 44 | 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | provided 50 | 51 | 52 | 53 | 54 | org.slf4j 55 | slf4j-api 56 | 57 | 58 | ch.qos.logback 59 | logback-core 60 | 61 | 62 | ch.qos.logback 63 | logback-classic 64 | 65 | 66 | 67 | 68 | springboot-wexin 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | maven-compiler-plugin 76 | 77 | 1.8 78 | 1.8 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/main/java/net/javadog/springbootwexin/common/AjaxJson.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2005-2020 jhmis All rights reserved. 3 | */ 4 | package net.javadog.springbootwexin.common; 5 | 6 | import com.fasterxml.jackson.annotation.JsonIgnore; 7 | 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | 11 | 12 | /** 13 | * $.ajax后需要接受的JSON 14 | * 15 | */ 16 | public class AjaxJson { 17 | 18 | private boolean success = true;// 是否成功 19 | private String errorCode = "-1";//错误代码 20 | private String msg = "操作成功";// 提示信息 21 | private Long count; //返回表格记录数量 22 | private List data; //返回表格数据 23 | private LinkedHashMap body = new LinkedHashMap();//封装json的map 24 | 25 | public static AjaxJson ok(){ 26 | AjaxJson j = new AjaxJson(); 27 | return j; 28 | } 29 | 30 | public static AjaxJson ok(String msg){ 31 | AjaxJson j = new AjaxJson(); 32 | j.setMsg(msg); 33 | return j; 34 | } 35 | 36 | public static AjaxJson ok(String msg, Object object){ 37 | AjaxJson j = new AjaxJson(); 38 | j.setMsg(msg); 39 | j.setResult(object); 40 | return j; 41 | } 42 | 43 | public static AjaxJson ok(Object object){ 44 | AjaxJson j = new AjaxJson(); 45 | j.setResult(object); 46 | return j; 47 | } 48 | 49 | public static AjaxJson fail(String errorMsg){ 50 | AjaxJson j = new AjaxJson(); 51 | j.setSuccess(false); 52 | j.setErrorCode("999");//默认错误码 53 | j.setMsg(errorMsg); 54 | return j; 55 | } 56 | 57 | public static AjaxJson fail(String errorCode,String errorMsg){ 58 | AjaxJson j = new AjaxJson(); 59 | j.setSuccess(false); 60 | j.setErrorCode(errorCode); 61 | j.setMsg(errorMsg); 62 | return j; 63 | } 64 | //返回不分页的layui表数据 65 | public static AjaxJson layuiTable(List list){ 66 | AjaxJson j = new AjaxJson(); 67 | j.setSuccess(true); 68 | j.setCount(Long.valueOf(list.size())); 69 | j.setData(list); 70 | return j; 71 | } 72 | public LinkedHashMap getBody() { 73 | return body; 74 | } 75 | 76 | public void setBody(LinkedHashMap body) { 77 | this.body = body; 78 | } 79 | 80 | public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key 81 | body.put(key, value); 82 | } 83 | 84 | public void remove(String key){ 85 | body.remove(key); 86 | } 87 | 88 | /** 89 | * 直接设置result内容 90 | * @param result 91 | */ 92 | public void setResult(Object result){ 93 | body.put("result", result); 94 | } 95 | @JsonIgnore//返回对象时忽略此属性 96 | public Object getResult(){ 97 | return body.get("result"); 98 | } 99 | 100 | public String getMsg() { 101 | return msg; 102 | } 103 | 104 | public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg 105 | this.msg = msg; 106 | } 107 | 108 | 109 | public boolean isSuccess() { 110 | return success; 111 | } 112 | 113 | public void setSuccess(boolean success) { 114 | this.success = success; 115 | } 116 | 117 | 118 | public void setErrorCode(String errorCode) { 119 | this.errorCode = errorCode; 120 | } 121 | 122 | public String getErrorCode() { 123 | return errorCode; 124 | } 125 | 126 | public Long getCount() { 127 | return count; 128 | } 129 | 130 | public void setCount(Long count) { 131 | this.count = count; 132 | } 133 | 134 | public List getData() { 135 | return data; 136 | } 137 | 138 | public void setData(List data) { 139 | this.data = data; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/net/javadog/springbootwexin/utils/WxUtil.java: -------------------------------------------------------------------------------- 1 | package net.javadog.springbootwexin.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.reflect.TypeToken; 5 | import lombok.Getter; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.client.RestTemplate; 10 | import java.security.MessageDigest; 11 | import java.util.Map; 12 | 13 | /** 14 | * 一个低端小气没档次的程序狗 JavaDog 15 | * blog.javadog.net 16 | * 17 | * @BelongsProject: springboot-wexin 18 | * @BelongsPackage: net.javadog.springbootwexin.utils 19 | * @Author: hdx 20 | * @CreateTime: 2020-02-14 21:19 21 | * @Description: 微信工具类 22 | */ 23 | @Component 24 | public class WxUtil { 25 | @Getter 26 | protected static String AppId; 27 | @Getter 28 | protected static String AppSecret; 29 | @Getter 30 | protected static String JssdkAccesstokenUrl; 31 | @Getter 32 | protected static String JssdkGetticketUrl; 33 | 34 | @Value("${wx.appId}") 35 | public void setAppId(String appId) { 36 | AppId = appId; 37 | } 38 | 39 | @Value("${wx.appSecret}") 40 | public void setAppSecret(String appSecret) { 41 | AppSecret = appSecret; 42 | } 43 | 44 | @Value("${wx.jssdk_accesstoken_url}") 45 | public void setJssdkAccesstokenUrl(String jssdkAccesstokenUrl) { 46 | JssdkAccesstokenUrl = jssdkAccesstokenUrl; 47 | } 48 | 49 | @Value("${wx.jssdk_getticket_url}") 50 | public void setJssdkGetticketUrl(String jssdkGetticketUrl) { 51 | JssdkGetticketUrl = jssdkGetticketUrl; 52 | } 53 | 54 | /** 55 | *@Author: hdx 56 | *@CreateTime: 21:31 2020/2/14 57 | *@param: * @param null 58 | *@Description: 59 | 60 | */ 61 | public static String getJSSDKAccessToken() { 62 | String token = null; 63 | String url = JssdkAccesstokenUrl.replaceAll("APPID", 64 | AppId).replaceAll("APPSECRET", 65 | AppSecret); 66 | String json = postRequestForWeiXinService(url); 67 | Map map = jsonToMap(json); 68 | if (map != null) { 69 | token = (String) map.get("access_token"); 70 | } 71 | return token; 72 | } 73 | 74 | /** 75 | *@Author: hdx 76 | *@CreateTime: 21:40 2020/2/14 77 | *@param: * @param null 78 | *@Description: 根据accessToken获取JssdkGetticket 79 | 80 | */ 81 | public static String getJssdkGetticket(String accessToken) { 82 | String url = JssdkGetticketUrl.replaceAll("ACCESS_TOKEN", accessToken); 83 | String json = postRequestForWeiXinService(url); 84 | Map map = jsonToMap(json); 85 | String jsapi_ticket = null; 86 | if (map != null) { 87 | jsapi_ticket = (String) map.get("ticket"); 88 | } 89 | return jsapi_ticket; 90 | } 91 | 92 | /** 93 | *@Author: hdx 94 | *@CreateTime: 21:41 2020/2/14 95 | *@param:ticket 根据accessToken生成的JssdkGetticket 96 | *@param:timestamp 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 97 | *@param:nonceStr 随机字符串 98 | *@param:url 当前网页的URL 99 | *@Description: 构建分享链接的签名 100 | 101 | */ 102 | public static String buildJSSDKSignature(String ticket,String timestamp,String nonceStr ,String url) throws Exception { 103 | String orderedString = "jsapi_ticket=" + ticket 104 | + "&noncestr=" + nonceStr + "×tamp=" + timestamp 105 | + "&url=" + url; 106 | 107 | return sha1(orderedString); 108 | } 109 | 110 | /** 111 | * sha1 加密JSSDK微信配置参数获取签名。 112 | * 113 | * @return 114 | */ 115 | public static String sha1(String orderedString) throws Exception { 116 | String ciphertext = null; 117 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 118 | byte[] digest = md.digest(orderedString.getBytes()); 119 | ciphertext = byteToStr(digest); 120 | return ciphertext.toLowerCase(); 121 | } 122 | /** 123 | * 将字节数组转换为十六进制字符串 124 | * 125 | * @param byteArray 126 | * @return 127 | */ 128 | private static String byteToStr(byte[] byteArray) { 129 | String strDigest = ""; 130 | for (int i = 0; i < byteArray.length; i++) { 131 | strDigest += byteToHexStr(byteArray[i]); 132 | } 133 | return strDigest; 134 | } 135 | /** 136 | * 将字节转换为十六进制字符串 137 | * 138 | * @param mByte 139 | * @return 140 | */ 141 | private static String byteToHexStr(byte mByte) { 142 | char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 143 | char[] tempArr = new char[2]; 144 | tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; 145 | tempArr[1] = Digit[mByte & 0X0F]; 146 | 147 | String s = new String(tempArr); 148 | return s; 149 | } 150 | /** 151 | *@Author: hdx 152 | *@CreateTime: 21:49 2020/2/14 153 | *@param: map 154 | *@Description: mapToJson 155 | 156 | */ 157 | public static String mapToJson(Map map){ 158 | Gson gson = new Gson(); 159 | String json = gson.toJson(map); 160 | return json; 161 | } 162 | 163 | /** 164 | *@Author: hdx 165 | *@CreateTime: 21:37 2020/2/14 166 | *@param: json 167 | *@Description: jsonToMap 168 | 169 | */ 170 | private static Map jsonToMap(String json) { 171 | Gson gons = new Gson(); 172 | Map map = gons.fromJson(json, new TypeToken(){}.getType()); 173 | return map; 174 | } 175 | 176 | /** 177 | *@Author: hdx 178 | *@CreateTime: 21:36 2020/2/14 179 | *@param: * @param null 180 | *@Description: 调取微信接口 181 | 182 | */ 183 | private static String postRequestForWeiXinService(String getAccessTokenUrl) { 184 | RestTemplate restTemplate = new RestTemplate(); 185 | ResponseEntity postForEntity = restTemplate.postForEntity(getAccessTokenUrl, null, String.class); 186 | String json = postForEntity.getBody(); 187 | return json; 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | logback 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | info 28 | 29 | 30 | ${CONSOLE_LOG_PATTERN} 31 | 32 | UTF-8 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ${log.path}/log_debug.log 43 | 44 | 45 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 46 | UTF-8 47 | 48 | 49 | 50 | 51 | ${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log 52 | 53 | 100MB 54 | 55 | 56 | 15 57 | 58 | 59 | 60 | debug 61 | ACCEPT 62 | DENY 63 | 64 | 65 | 66 | 67 | 68 | 69 | ${log.path}/log_info.log 70 | 71 | 72 | *************%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 73 | UTF-8 74 | 75 | 76 | 77 | 78 | ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 79 | 80 | 100MB 81 | 82 | 83 | 15 84 | 85 | 86 | 87 | info 88 | ACCEPT 89 | DENY 90 | 91 | 92 | 93 | 94 | 95 | 96 | ${log.path}/log_warn.log 97 | 98 | 99 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 100 | UTF-8 101 | 102 | 103 | 104 | ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 105 | 106 | 100MB 107 | 108 | 109 | 15 110 | 111 | 112 | 113 | warn 114 | ACCEPT 115 | DENY 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | ${log.path}/log_error.log 124 | 125 | 126 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 127 | UTF-8 128 | 129 | 130 | 131 | ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 132 | 133 | 100MB 134 | 135 | 136 | 15 137 | 138 | 139 | 140 | ERROR 141 | ACCEPT 142 | DENY 143 | 144 | 145 | 146 | 156 | 157 | 158 | 163 | 164 | 165 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-wexin 2 | 史上最全接入微信JSSDK菜鸟教程 3 | > 虽然我很菜鸟,但我还想分享 4 | > **有需要csdn积分下载需求的同学**,评论关注回复我咋都可以,**免费给大家下载**⚽ 5 | > 我坚信爱分享的人运气一定不差,进步很快乐,分享更快乐😬 6 | 7 | ### GitHub 8 | 先给猴急的客官上干货代码,[接入微信JSSDK GitHub地址](https://github.com/javadog-net/springboot-wexin) 9 | 10 | ### 前言 11 | 事情的起因是因为疫情严重,领导要求做一个专题页,能够尽可能帮助所需要的人。 12 | 于是乎本狗与同事挑灯奋战,加班加点赶工出来。 13 | 部署上线完成,用微信内置浏览器分享后,理想状态应该是这样的,如下图⬇️ 14 | 15 | ![](https://img-blog.csdnimg.cn/20200223141656148.png) 16 | 17 | 但是,结果却不是理想的这样,默默地留下了没有技术的泪水,如下图⬇️ 18 | ![](https://img-blog.csdnimg.cn/20200223141735564.png) 19 | 20 | 竟然没有关键字和展示图片,在本菜狗的不懈努力下,终于承认技术不行,去请教了大佬,得出如下结论。 21 | 22 | - 1.微信内置浏览器分享若需要自定义展示描述及右侧封面图,必须接入**微信JSSDK**,并且一定需要有配合本站的**微信公众号(appId和appSecret)才可自定义分享,切记小程序(appId和appSecret)的不可以** 23 | - 2.非微信分享,如QQ浏览器,UC浏览器等各大厂商,会根据自身定义获取HTML页面中的TDK(title,description,keyword),举例UC浏览器分享⬇️ 24 | ![](https://img-blog.csdnimg.cn/20200223142824730.png) 25 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223143025602.png) 26 | 27 | 所以,对接微信JSSDK,势在必行! 28 | ******* 29 | ### Tip 30 | 史上最详细的**接入微信JSSDK菜鸟教程**,本文全面的记录了接入微信JSSDK的步骤,具体的代码及遇到的坑,并且展示发布最终效果,并将代码发布GitHub。随篇幅较长,但史上最全。大佬勿喷,新手入门,亲测可用!!! 31 | 32 | #### 本文试用人群 33 | - 需要接入微信JSSDK却**看不懂文档**的同学 34 | - 看懂文档但是**实操不知如何下手**的同学 35 | - 下了手但是出错不知道**如何调试修改**的同学 36 | - 成功接入过但是想**重温具体流程**的同学 37 | 38 | #### 本文目标 39 | - 实战进行H5网站**微信自定义分享** 40 | - 实战进行H5网站**调取相册选取图片** 41 | 42 | 放松心态,慢慢来看 43 | 44 | ************ 45 | ### 正文 46 | #### 官方文档 47 | 任何平台接入,官方文档是标杆,虽有些关键点一笔带过,我们也要通读有个印象,点击[微信官方文档](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html)打开文档,如下⬇️ 48 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223145741670.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 49 | ##### 总览 50 | - **1.x是接入关键步骤,需仔细品读**,与接入有关 51 | - 2.x - 12.x 具体接口接入,需要对接时具体参考 52 | - 13.x 需要注意下,**api_ticket 微信临时票据**,与接入有关 53 | - 16-22 均是附录,可查阅错误列表对应含义,及接口菜单列表等描述 54 | ****** 55 | #### 实操步骤 56 | 使用IDEA工具,新建SpringBoot项目,项目名为springboot-wexin,目录结构如下 57 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223153820797.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 58 | **AjaxJson.java** - 自定义接口返回前台数据格式的封装类 59 | ```java 60 | /** 61 | * Copyright © 2005-2020 jhmis All rights reserved. 62 | */ 63 | package net.javadog.springbootwexin.common; 64 | 65 | import com.fasterxml.jackson.annotation.JsonIgnore; 66 | 67 | import java.util.LinkedHashMap; 68 | import java.util.List; 69 | 70 | 71 | /** 72 | * $.ajax后需要接受的JSON 73 | * 74 | */ 75 | public class AjaxJson { 76 | 77 | private boolean success = true;// 是否成功 78 | private String errorCode = "-1";//错误代码 79 | private String msg = "操作成功";// 提示信息 80 | private Long count; //返回表格记录数量 81 | private List data; //返回表格数据 82 | private LinkedHashMap body = new LinkedHashMap();//封装json的map 83 | 84 | public static AjaxJson ok(){ 85 | AjaxJson j = new AjaxJson(); 86 | return j; 87 | } 88 | 89 | public static AjaxJson ok(String msg){ 90 | AjaxJson j = new AjaxJson(); 91 | j.setMsg(msg); 92 | return j; 93 | } 94 | 95 | public static AjaxJson ok(String msg, Object object){ 96 | AjaxJson j = new AjaxJson(); 97 | j.setMsg(msg); 98 | j.setResult(object); 99 | return j; 100 | } 101 | 102 | public static AjaxJson ok(Object object){ 103 | AjaxJson j = new AjaxJson(); 104 | j.setResult(object); 105 | return j; 106 | } 107 | 108 | public static AjaxJson fail(String errorMsg){ 109 | AjaxJson j = new AjaxJson(); 110 | j.setSuccess(false); 111 | j.setErrorCode("999");//默认错误码 112 | j.setMsg(errorMsg); 113 | return j; 114 | } 115 | 116 | public static AjaxJson fail(String errorCode,String errorMsg){ 117 | AjaxJson j = new AjaxJson(); 118 | j.setSuccess(false); 119 | j.setErrorCode(errorCode); 120 | j.setMsg(errorMsg); 121 | return j; 122 | } 123 | //返回不分页的layui表数据 124 | public static AjaxJson layuiTable(List list){ 125 | AjaxJson j = new AjaxJson(); 126 | j.setSuccess(true); 127 | j.setCount(Long.valueOf(list.size())); 128 | j.setData(list); 129 | return j; 130 | } 131 | public LinkedHashMap getBody() { 132 | return body; 133 | } 134 | 135 | public void setBody(LinkedHashMap body) { 136 | this.body = body; 137 | } 138 | 139 | public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key 140 | body.put(key, value); 141 | } 142 | 143 | public void remove(String key){ 144 | body.remove(key); 145 | } 146 | 147 | /** 148 | * 直接设置result内容 149 | * @param result 150 | */ 151 | public void setResult(Object result){ 152 | body.put("result", result); 153 | } 154 | @JsonIgnore//返回对象时忽略此属性 155 | public Object getResult(){ 156 | return body.get("result"); 157 | } 158 | 159 | public String getMsg() { 160 | return msg; 161 | } 162 | 163 | public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg 164 | this.msg = msg; 165 | } 166 | 167 | 168 | public boolean isSuccess() { 169 | return success; 170 | } 171 | 172 | public void setSuccess(boolean success) { 173 | this.success = success; 174 | } 175 | 176 | 177 | public void setErrorCode(String errorCode) { 178 | this.errorCode = errorCode; 179 | } 180 | 181 | public String getErrorCode() { 182 | return errorCode; 183 | } 184 | 185 | public Long getCount() { 186 | return count; 187 | } 188 | 189 | public void setCount(Long count) { 190 | this.count = count; 191 | } 192 | 193 | public List getData() { 194 | return data; 195 | } 196 | 197 | public void setData(List data) { 198 | this.data = data; 199 | } 200 | } 201 | ``` 202 | ***** 203 | **WxInitController.java** - 微信初始化接入Controller控制器 204 | ```java 205 | package net.javadog.springbootwexin.controller; 206 | import net.javadog.springbootwexin.common.AjaxJson; 207 | import net.javadog.springbootwexin.service.WxService; 208 | import net.javadog.springbootwexin.utils.WxUtil; 209 | import org.slf4j.Logger; 210 | import org.slf4j.LoggerFactory; 211 | import org.springframework.beans.factory.annotation.Autowired; 212 | import org.springframework.web.bind.annotation.RequestMapping; 213 | import org.springframework.web.bind.annotation.RequestParam; 214 | import org.springframework.web.bind.annotation.RestController; 215 | import java.util.Map; 216 | 217 | /** 218 | * 一个低端小气没档次的程序狗 JavaDog 219 | * blog.javadog.net 220 | * 221 | * @BelongsProject: springboot-wexin 222 | * @BelongsPackage: net.javadog.springbootwexin.controller 223 | * @Author: hdx 224 | * @CreateTime: 2020-02-14 14:52 225 | * @Description: 微信初始化接入Controller控制器 226 | */ 227 | @RestController 228 | @RequestMapping("/weixin") 229 | public class WxInitController { 230 | protected Logger logger = LoggerFactory.getLogger(getClass()); 231 | 232 | @Autowired 233 | private WxService wxService; 234 | 235 | /** 236 | *@Author: hdx 237 | *@CreateTime: 20:39 2020/2/14 238 | *@param: shareUrl 分享url地址 239 | *@Description: 初始化微信JSSDK Config信息 240 | 1.先通过appId和appSecret参数请求指定微信地址 获取AccessToken 241 | 2.在通过第一步中的AccessToken作为参数请求微信地址 获取jsapi_ticket临时票据(此处不考虑调用频率,使用者根据情况放入缓存或定时任务) 242 | 3.通过第二步的JssdkGetticket和timestamp,nonceStr,url作为参数请求微信地址,获取签名signature 243 | 4.将第三步获得的signature和jsapi_ticket,nonceStr,timestamp,url返回给前端,作为Config初始化验证的信息 244 | */ 245 | @RequestMapping("/initWXJSSDKConfigInfo") 246 | public AjaxJson initWXJSConfig (@RequestParam(required = false) String url) throws Exception{ 247 | logger.info("url=" + url); 248 | String json = ""; 249 | try { 250 | Map map = wxService.initJSSDKConfig(url); 251 | json = WxUtil.mapToJson(map); 252 | }catch (Exception e){ 253 | AjaxJson.fail(e.getMessage()); 254 | } 255 | return AjaxJson.ok(json); 256 | } 257 | 258 | } 259 | ``` 260 | ***** 261 | **WxService.java** - 初始化JSSDKConfig 262 | ```java 263 | package net.javadog.springbootwexin.service; 264 | 265 | import lombok.Getter; 266 | import net.javadog.springbootwexin.utils.WxUtil; 267 | import org.springframework.beans.factory.annotation.Value; 268 | import org.springframework.stereotype.Service; 269 | import java.util.HashMap; 270 | import java.util.Map; 271 | import java.util.UUID; 272 | 273 | /** 274 | * 一个低端小气没档次的程序狗 JavaDog 275 | * blog.javadog.net 276 | * 277 | * @BelongsProject: springboot-wexin 278 | * @BelongsPackage: net.javadog.springbootwexin.service 279 | * @Author: hdx 280 | * @CreateTime: 2020-02-14 20:43 281 | * @Description: 微信相关service 282 | */ 283 | @Service 284 | public class WxService { 285 | @Getter 286 | private static String AppId; 287 | @Value("${wx.appId}") 288 | public void setAppId(String appId) { 289 | AppId = appId; 290 | } 291 | /** 292 | *@Author: hdx 293 | *@CreateTime: 20:46 2020/2/14 294 | *@param: shareUrl 分享的url 295 | *@Description: 初始化JSSDKConfig 296 | */ 297 | public Map initJSSDKConfig(String url) throws Exception { 298 | //获取AccessToken 299 | String accessToken = WxUtil.getJSSDKAccessToken(); 300 | //获取JssdkGetticket 301 | String jsapiTicket = WxUtil.getJssdkGetticket(accessToken); 302 | String timestamp = Long.toString(System.currentTimeMillis() / 1000); 303 | String nonceStr = UUID.randomUUID().toString(); 304 | String signature = WxUtil.buildJSSDKSignature(jsapiTicket,timestamp,nonceStr,url); 305 | Map map = new HashMap(); 306 | map.put("url", url); 307 | map.put("jsapi_ticket", jsapiTicket); 308 | map.put("nonceStr", nonceStr); 309 | map.put("timestamp", timestamp); 310 | map.put("signature", signature); 311 | map.put("appid", AppId); 312 | return map; 313 | } 314 | } 315 | ``` 316 | ***** 317 | **WxUtil.java** - 微信工具类 318 | ```java 319 | package net.javadog.springbootwexin.utils; 320 | 321 | import com.google.gson.Gson; 322 | import com.google.gson.reflect.TypeToken; 323 | import lombok.Getter; 324 | import org.springframework.beans.factory.annotation.Value; 325 | import org.springframework.http.ResponseEntity; 326 | import org.springframework.stereotype.Component; 327 | import org.springframework.web.client.RestTemplate; 328 | import java.security.MessageDigest; 329 | import java.util.Map; 330 | 331 | /** 332 | * 一个低端小气没档次的程序狗 JavaDog 333 | * blog.javadog.net 334 | * 335 | * @BelongsProject: springboot-wexin 336 | * @BelongsPackage: net.javadog.springbootwexin.utils 337 | * @Author: hdx 338 | * @CreateTime: 2020-02-14 21:19 339 | * @Description: 微信工具类 340 | */ 341 | @Component 342 | public class WxUtil { 343 | @Getter 344 | protected static String AppId; 345 | @Getter 346 | protected static String AppSecret; 347 | @Getter 348 | protected static String JssdkAccesstokenUrl; 349 | @Getter 350 | protected static String JssdkGetticketUrl; 351 | 352 | @Value("${wx.appId}") 353 | public void setAppId(String appId) { 354 | AppId = appId; 355 | } 356 | 357 | @Value("${wx.appSecret}") 358 | public void setAppSecret(String appSecret) { 359 | AppSecret = appSecret; 360 | } 361 | 362 | @Value("${wx.jssdk_accesstoken_url}") 363 | public void setJssdkAccesstokenUrl(String jssdkAccesstokenUrl) { 364 | JssdkAccesstokenUrl = jssdkAccesstokenUrl; 365 | } 366 | 367 | @Value("${wx.jssdk_getticket_url}") 368 | public void setJssdkGetticketUrl(String jssdkGetticketUrl) { 369 | JssdkGetticketUrl = jssdkGetticketUrl; 370 | } 371 | 372 | /** 373 | *@Author: hdx 374 | *@CreateTime: 21:31 2020/2/14 375 | *@param: * @param null 376 | *@Description: 377 | 378 | */ 379 | public static String getJSSDKAccessToken() { 380 | String token = null; 381 | String url = JssdkAccesstokenUrl.replaceAll("APPID", 382 | AppId).replaceAll("APPSECRET", 383 | AppSecret); 384 | String json = postRequestForWeiXinService(url); 385 | Map map = jsonToMap(json); 386 | if (map != null) { 387 | token = (String) map.get("access_token"); 388 | } 389 | return token; 390 | } 391 | 392 | /** 393 | *@Author: hdx 394 | *@CreateTime: 21:40 2020/2/14 395 | *@param: * @param null 396 | *@Description: 根据accessToken获取JssdkGetticket 397 | 398 | */ 399 | public static String getJssdkGetticket(String accessToken) { 400 | String url = JssdkGetticketUrl.replaceAll("ACCESS_TOKEN", accessToken); 401 | String json = postRequestForWeiXinService(url); 402 | Map map = jsonToMap(json); 403 | String jsapi_ticket = null; 404 | if (map != null) { 405 | jsapi_ticket = (String) map.get("ticket"); 406 | } 407 | return jsapi_ticket; 408 | } 409 | 410 | /** 411 | *@Author: hdx 412 | *@CreateTime: 21:41 2020/2/14 413 | *@param:ticket 根据accessToken生成的JssdkGetticket 414 | *@param:timestamp 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 415 | *@param:nonceStr 随机字符串 416 | *@param:url 当前网页的URL 417 | *@Description: 构建分享链接的签名 418 | 419 | */ 420 | public static String buildJSSDKSignature(String ticket,String timestamp,String nonceStr ,String url) throws Exception { 421 | String orderedString = "jsapi_ticket=" + ticket 422 | + "&noncestr=" + nonceStr + "×tamp=" + timestamp 423 | + "&url=" + url; 424 | 425 | return sha1(orderedString); 426 | } 427 | 428 | /** 429 | * sha1 加密JSSDK微信配置参数获取签名。 430 | * 431 | * @return 432 | */ 433 | public static String sha1(String orderedString) throws Exception { 434 | String ciphertext = null; 435 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 436 | byte[] digest = md.digest(orderedString.getBytes()); 437 | ciphertext = byteToStr(digest); 438 | return ciphertext.toLowerCase(); 439 | } 440 | /** 441 | * 将字节数组转换为十六进制字符串 442 | * 443 | * @param byteArray 444 | * @return 445 | */ 446 | private static String byteToStr(byte[] byteArray) { 447 | String strDigest = ""; 448 | for (int i = 0; i < byteArray.length; i++) { 449 | strDigest += byteToHexStr(byteArray[i]); 450 | } 451 | return strDigest; 452 | } 453 | /** 454 | * 将字节转换为十六进制字符串 455 | * 456 | * @param mByte 457 | * @return 458 | */ 459 | private static String byteToHexStr(byte mByte) { 460 | char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 461 | char[] tempArr = new char[2]; 462 | tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; 463 | tempArr[1] = Digit[mByte & 0X0F]; 464 | 465 | String s = new String(tempArr); 466 | return s; 467 | } 468 | /** 469 | *@Author: hdx 470 | *@CreateTime: 21:49 2020/2/14 471 | *@param: map 472 | *@Description: mapToJson 473 | 474 | */ 475 | public static String mapToJson(Map map){ 476 | Gson gson = new Gson(); 477 | String json = gson.toJson(map); 478 | return json; 479 | } 480 | 481 | /** 482 | *@Author: hdx 483 | *@CreateTime: 21:37 2020/2/14 484 | *@param: json 485 | *@Description: jsonToMap 486 | 487 | */ 488 | private static Map jsonToMap(String json) { 489 | Gson gons = new Gson(); 490 | Map map = gons.fromJson(json, new TypeToken(){}.getType()); 491 | return map; 492 | } 493 | 494 | /** 495 | *@Author: hdx 496 | *@CreateTime: 21:36 2020/2/14 497 | *@param: * @param null 498 | *@Description: 调取微信接口 499 | 500 | */ 501 | private static String postRequestForWeiXinService(String getAccessTokenUrl) { 502 | RestTemplate restTemplate = new RestTemplate(); 503 | ResponseEntity postForEntity = restTemplate.postForEntity(getAccessTokenUrl, null, String.class); 504 | String json = postForEntity.getBody(); 505 | return json; 506 | } 507 | 508 | } 509 | ``` 510 | ***** 511 | **SpringbootWexinApplication.java** - SpringBoot启动类 512 | ```java 513 | package net.javadog.springbootwexin; 514 | 515 | import org.springframework.boot.SpringApplication; 516 | import org.springframework.boot.autoconfigure.SpringBootApplication; 517 | 518 | @SpringBootApplication 519 | public class SpringbootWexinApplication { 520 | 521 | public static void main(String[] args) { 522 | SpringApplication.run(SpringbootWexinApplication.class, args); 523 | } 524 | 525 | } 526 | ``` 527 | ***** 528 | **config/application.yml** - 基础配置文件 529 | ```yml 530 | spring: 531 | profiles: 532 | #激活配置文件 533 | active: prod 534 | #配置静态资源路径 535 | resources: 536 | static-locations: classpath:/static/ 537 | 538 | #日志相关 539 | logging: 540 | #配置文件日志路径 541 | config: classpath:logback-spring.xml 542 | 543 | #微信相关配置 544 | wx: 545 | #appId (到时候换成自己公众号的) 546 | appId: wx4ad618620f8c3528 547 | #appSecret(到时候换成自己公众号的) 548 | appSecret: b772c7863b29e270aa86e40f9b9e6215 549 | #参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token) 550 | jssdk_accesstoken_url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 551 | #用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket) 552 | jssdk_getticket_url: https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi 553 | ``` 554 | **application-dev.yml** -开发配置文件(可选) 555 | ```yml 556 | # 开发环境配置 557 | spring: 558 | profiles: dev 559 | 560 | #端口设置 561 | server: 562 | port: 8000 563 | ``` 564 | 565 | **application-prod.yml** -生产配置文件(因JS接口安全域名限制,则采取正式生产配置) 566 | ```yml 567 | # 生产环境配置 568 | spring: 569 | profiles: prod 570 | 571 | #端口设置 572 | server: 573 | port: 8002 574 | ``` 575 | 576 | **application-test.yml** -测试配置文件(可选) 577 | ```yml 578 | # 生产环境配置 579 | spring: 580 | profiles: prod 581 | 582 | #端口设置 583 | server: 584 | port: 8002 585 | ``` 586 | ****** 587 | **demo.html ** - 测试h5页面 588 | ```html 589 | 590 | 591 | 592 | 593 | 594 | 595 | 测试jssdk 596 | 597 | 598 | 599 | 600 | 661 | 662 | 663 | 664 | 665 | 666 | ``` 667 | ****** 668 | 🔥nginx 配置,此处不是项目中的代码!!! 669 | **nginx.config** - nginx服务器配置 670 | ```shell 671 | server 672 | { 673 | listen 80; #监听端口设为 80。 674 | server_name wxjssdk.javadog.net; #请绑定自己的前缀域名。 675 | location / { 676 | proxy_set_header HOST $host; 677 | proxy_set_header X-Forwarded-Proto $scheme; 678 | proxy_set_header X-Real-IP $remote_addr; 679 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 680 | proxy_pass http://127.0.0.1:8002/; 681 | } 682 | } 683 | ``` 684 | **MP_verify_B0vMQLCguxRzP1Rc.txt** - JS接口安全域名验证文件(到时候替换成自己公众号上的),必须在域名根路径下可以访问 685 | ```java 686 | #一定把自己公众号上的txt验证文件放上!!! 687 | B0vMQLCguxRzP1Rc 688 | ``` 689 | ****** 690 | ##### 步骤详解 691 | 打开文档JSSDK使用步骤段落,如下⬇️ 692 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223151159594.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 693 | ##### 1.绑定域名 694 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224091753515.png) 695 | 先登录[微信公众平台](https://mp.weixin.qq.com)进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。如下⬇️ 696 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223151711529.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 697 | 点击设置如下⬇️ 698 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223151951781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 699 | ###### 关键点 700 | - 1.只能是三个域名或路径,**中文,ip,带端口等路径均不可** 701 | - 2.域名必须是ICP备案过的,有些同学**使用内网穿透花生壳之类的无法设置JS安全域名** 702 | - 3.必须将txt文件放置安全域名所对应的目录,如wxjssdk.javadog.net/xxx.txt。可由nginx配置,只要能访问即可,**如果访问不到则无法设置JS安全域名** 703 | 704 | ##### 2.引入JS文件 705 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224091726780.png) 706 | 实际引用在我们的项目Demo.html页面中第9行,如 707 | ![](https://img-blog.csdnimg.cn/20200224091137161.png) 708 | ##### 3.通过config接口注入权限验证配置 709 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224091822341.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 710 | ###### 关键点 711 | 必须在**后台开放一个对外获取config接口注入权限的接口** 712 | 对应我们代码中**WxInitController.java 中的initWXJSSDKConfigInfo()**方法,会返回文档中所需的必填项**appId,timestamp,nonceStr,signature**验证参数 713 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224092342176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 714 | 715 | ###### 实现步骤 716 | 1.先通过appId和appSecret参数请求指定微信地址 获取AccessToken 717 | 2.在通过第一步中的AccessToken作为参数请求微信地址 获取jsapi_ticket临时票据(此处不考虑调用频率,使用者根据情况放入缓存或定时任务) 718 | 3.通过第二步的JssdkGetticket和timestamp,nonceStr,url作为参数请求微信地址,获取签名signature 719 | 4.将第三步获得的signature和jsapi_ticket,nonceStr,timestamp,url返回给前端,作为Config初始化验证的信息 720 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224094803470.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 721 | 722 | - 1.先通过appId和appSecret参数请求指定微信地址 获取**AccessToken** 723 | 对应我们代码中WxUtil.java第61行**getJSSDKAccessToken()**方法 724 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224094129176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 725 | 通过**自己公众号的appId和appSecret**调用**微信服务器access_token接口地址获取token**,地址如下 726 | https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 727 | 将其中APPID和APPSECRET**替换成自己公众号的appId和appSecret**,调取后返回Json字符串结果,获取access_token 728 | 729 | - 2.通过第一步中的AccessToken作为参数请求微信地址 获取jsapi_ticket临时票据 730 | 对应我们代码中WxUtil.java第81行**getJssdkGetticket()**方法 731 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224095454973.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 732 | 通过上一步获得的**access_token**调用**微信服务器jsapi_ticket接口地址获取jsapi_ticket**,地址如下 733 | https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi 734 | 将其中ACCESS_TOKEN**替换成上一步获取的access_token**,调取后返回Json字符串结果,获取jsapi_ticket 735 | 736 | - 3.通过第二步的JssdkGetticket和timestamp,nonceStr,url作为参数请求微信地址,**获取签名signature** 737 | 对应我们代码中WxUtil.java第102行buildJSSDKSignature()方法 738 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224101244262.png) 739 | 通过上一步获得的**jsapi_ticket**,加上timestamp(支付签名时间戳),nonceStr(随机字符串),url(当前网页的URL),按照字段名的ASCII 码从小到大排序(字典序)后通过sha1进行签名,生成最终签名数据。 740 | 对应我们代码中WxUtil.java第115行sha1()方法 741 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224153849877.png) 742 | - 4.前端成功接到返回值 743 | 744 | 对应我们代码中demo.html第16行$.ajax方法 745 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224160028903.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 746 | 接口返回值为 747 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224155638274.png) 748 | JSON.parse(msg)转化一下JSON对象,对应我们代码中的Demo.html中24行,转化后数据做wx.config接口注入权限验证,对应我们代码demo.html第37行 749 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224155914945.png) 750 | 751 | ##### 4.通过ready接口处理成功验证 752 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224205700482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 753 | ###### 关键点 754 | **所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行** 755 | 在我们代码在demo.html第46行,自定义分享接口,需要在页面初始化加载时就放入ready才可生效 756 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224205909331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 757 | **反之不需要初始化加载的即可通过用户事件触发执行即可** 758 | 在我们代码在demo.html第63行,用户点击按钮触发-拍照或从手机相册中选图接口 759 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224211957299.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 760 | #### 发布 761 | 我采用的IDEA插件Alibaba Cloud Toolkit工具一键部署本地应用到ECS服务器,可百度或等我下篇文章详解一下这个插件的用法。 762 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224213138387.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 763 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224213221413.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 764 | Target ECS:目标服务器,我买的是阿里的服务器,则直接可以搜索到。 765 | Target Directory: 目标目录,需要把打成的jar包上传至哪个路径下 如:/usr/local/hdx/web/ 766 | Command: 上传后执行的操作命令 如:nohup java -jar /usr/local/hdx/web/springboot-wexin.jar 767 | 768 | 发布成功后会在终端出现成功提示信息 769 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224213613751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 770 | 然后大功告成,访问一下试试 [http://wxjssdk.javadog.net/demo.html](http://wxjssdk.javadog.net/demo.html) 771 | 如果调试推荐使用**微信开发者工具**,也就是 772 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224214236995.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 773 | 切记**配置nginx**和**服务器安全端口访问权限**!!!否则会404哦!!! 774 | 775 | 776 | #### 测试 777 | - 1.先来测试下拍照或从手机相册中选图接口 778 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224215022360.gif) 779 | 调试正常 780 | 781 | - 2.再来测试微信内置分享 782 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224215535267.gif) 783 | 调试报错,这是个小坑。本狗在这调试了好久,原因出在个人的**订阅号是没有自定义分享权限**的!! 784 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224215906222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 785 | 786 | #### 小坑总结 787 | 1. 订阅号和服务号所涉及权限不同,需详细查看微信开发文档,查询公众号权限 788 | 2. IP白名单未设置,会报40164 789 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224220440497.png) 790 | IP白名单需要在公众号**安全中心设置** 791 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224220551848.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI1OTg2MDU5,size_16,color_FFFFFF,t_70) 792 | 3. invalid signature 签名异常 793 | 建议使用微信JSSDK[签名验证工具](https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign)验证是否有误 794 | 795 | 我是JavaDog,谢谢博友耐心看完, 抽空来我狗窝🐕瞅瞅呗 [blog.javadog.net](https://blog.javadog.net) 796 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/f6728ecded5c4d58a667f6540dc67d20.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YURvZ-eoi-W6j-axqg==,size_20,color_FFFFFF,t_70,g_se,x_16) 797 | 798 | --------------------------------------------------------------------------------