| @@ -1,13 +1,21 @@ | |||||
| package me.chanjar.weixin.common.bean; | package me.chanjar.weixin.common.bean; | ||||
| import lombok.AllArgsConstructor; | |||||
| import lombok.Builder; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| /** | /** | ||||
| * jspai signature. | * jspai signature. | ||||
| * | |||||
| * @author chanjarster | |||||
| */ | */ | ||||
| @Data | @Data | ||||
| @Builder | |||||
| @NoArgsConstructor | |||||
| @AllArgsConstructor | |||||
| public class WxJsapiSignature implements Serializable { | public class WxJsapiSignature implements Serializable { | ||||
| private static final long serialVersionUID = -1116808193154384804L; | private static final long serialVersionUID = -1116808193154384804L; | ||||
| @@ -0,0 +1,48 @@ | |||||
| package cn.binarywang.wx.miniapp.api; | |||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * jsapi相关接口 | |||||
| * Created by BinaryWang on 2018/8/5. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxMaJsapiService { | |||||
| /** | |||||
| * 获得jsapi_ticket的url | |||||
| */ | |||||
| String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||||
| /** | |||||
| * 获得jsapi_ticket,不强制刷新jsapi_ticket | |||||
| * | |||||
| * @see #getJsapiTicket(boolean) | |||||
| */ | |||||
| String getJsapiTicket() throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获得jsapi_ticket | |||||
| * 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||||
| * | |||||
| * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||||
| * </pre> | |||||
| * | |||||
| * @param forceRefresh 强制刷新 | |||||
| */ | |||||
| String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 创建调用jsapi时所需要的签名 | |||||
| * | |||||
| * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||||
| * </pre> | |||||
| */ | |||||
| WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||||
| } | |||||
| @@ -2,6 +2,7 @@ package cn.binarywang.wx.miniapp.api; | |||||
| import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | ||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | import cn.binarywang.wx.miniapp.config.WxMaConfig; | ||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
| @@ -17,6 +18,7 @@ public interface WxMaService { | |||||
| String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; | String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; | ||||
| String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | ||||
| /** | /** | ||||
| * 获取登录后的session信息 | * 获取登录后的session信息 | ||||
| * | * | ||||
| @@ -149,6 +151,13 @@ public interface WxMaService { | |||||
| */ | */ | ||||
| WxMaCodeService getCodeService(); | WxMaCodeService getCodeService(); | ||||
| /** | |||||
| * 返回jsapi操作相关的 API服务类对象 | |||||
| * | |||||
| * @return WxMaJsapiService | |||||
| */ | |||||
| WxMaJsapiService getJsapiService(); | |||||
| /** | /** | ||||
| * 小程序修改服务器地址、成员管理 API | * 小程序修改服务器地址、成员管理 API | ||||
| * | * | ||||
| @@ -0,0 +1,75 @@ | |||||
| package cn.binarywang.wx.miniapp.api.impl; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaJsapiService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.google.gson.JsonParser; | |||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.RandomUtils; | |||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/8/5. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class WxMaJsapiServiceImpl implements WxMaJsapiService { | |||||
| private static final JsonParser JSON_PARSER = new JsonParser(); | |||||
| private WxMaService wxMaService; | |||||
| public WxMaJsapiServiceImpl(WxMaService wxMaService) { | |||||
| this.wxMaService = wxMaService; | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket() throws WxErrorException { | |||||
| return getJsapiTicket(false); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||||
| Lock lock = this.wxMaService.getWxMaConfig().getJsapiTicketLock(); | |||||
| try { | |||||
| lock.lock(); | |||||
| if (forceRefresh) { | |||||
| this.wxMaService.getWxMaConfig().expireJsapiTicket(); | |||||
| } | |||||
| if (this.wxMaService.getWxMaConfig().isJsapiTicketExpired()) { | |||||
| String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL, null); | |||||
| JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||||
| this.wxMaService.getWxMaConfig().updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||||
| } | |||||
| } finally { | |||||
| lock.unlock(); | |||||
| } | |||||
| return this.wxMaService.getWxMaConfig().getJsapiTicket(); | |||||
| } | |||||
| @Override | |||||
| public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||||
| long timestamp = System.currentTimeMillis() / 1000; | |||||
| String randomStr = RandomUtils.getRandomStr(); | |||||
| String jsapiTicket = getJsapiTicket(false); | |||||
| String signature = SHA1.genWithAmple("jsapi_ticket=" + jsapiTicket, | |||||
| "noncestr=" + randomStr, "timestamp=" + timestamp, "url=" + url); | |||||
| return WxJsapiSignature | |||||
| .builder() | |||||
| .appId(this.wxMaService.getWxMaConfig().getAppid()) | |||||
| .timestamp(timestamp) | |||||
| .nonceStr(randomStr) | |||||
| .url(url) | |||||
| .signature(signature) | |||||
| .build(); | |||||
| } | |||||
| } | |||||
| @@ -1,14 +1,6 @@ | |||||
| package cn.binarywang.wx.miniapp.api.impl; | package cn.binarywang.wx.miniapp.api.impl; | ||||
| import cn.binarywang.wx.miniapp.api.WxMaAnalysisService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaMediaService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaMsgService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaQrcodeService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaSettingService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaTemplateService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaUserService; | |||||
| import cn.binarywang.wx.miniapp.api.*; | |||||
| import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | ||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | import cn.binarywang.wx.miniapp.config.WxMaConfig; | ||||
| import com.google.common.base.Joiner; | import com.google.common.base.Joiner; | ||||
| @@ -58,6 +50,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||||
| private WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this); | private WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this); | ||||
| private WxMaCodeService codeService = new WxMaCodeServiceImpl(this); | private WxMaCodeService codeService = new WxMaCodeServiceImpl(this); | ||||
| private WxMaSettingService settingService = new WxMaSettingServiceImpl(this); | private WxMaSettingService settingService = new WxMaSettingServiceImpl(this); | ||||
| private WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); | |||||
| private int retrySleepMillis = 1000; | private int retrySleepMillis = 1000; | ||||
| private int maxRetryTimes = 5; | private int maxRetryTimes = 5; | ||||
| @@ -310,6 +303,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||||
| return this.codeService; | return this.codeService; | ||||
| } | } | ||||
| @Override | |||||
| public WxMaJsapiService getJsapiService() { | |||||
| return this.jsapiService; | |||||
| } | |||||
| @Override | @Override | ||||
| public WxMaSettingService getSettingService() { | public WxMaSettingService getSettingService() { | ||||
| return this.settingService; | return this.settingService; | ||||
| @@ -38,6 +38,25 @@ public interface WxMaConfig { | |||||
| */ | */ | ||||
| void updateAccessToken(String accessToken, int expiresInSeconds); | void updateAccessToken(String accessToken, int expiresInSeconds); | ||||
| String getJsapiTicket(); | |||||
| Lock getJsapiTicketLock(); | |||||
| boolean isJsapiTicketExpired(); | |||||
| /** | |||||
| * 强制将jsapi ticket过期掉 | |||||
| */ | |||||
| void expireJsapiTicket(); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param jsapiTicket 新的jsapi ticket值 | |||||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||||
| */ | |||||
| void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||||
| String getAppid(); | String getAppid(); | ||||
| String getSecret(); | String getSecret(); | ||||
| @@ -27,7 +27,11 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||||
| protected volatile String httpProxyUsername; | protected volatile String httpProxyUsername; | ||||
| protected volatile String httpProxyPassword; | protected volatile String httpProxyPassword; | ||||
| protected volatile String jsapiTicket; | |||||
| protected volatile long jsapiTicketExpiresTime; | |||||
| protected Lock accessTokenLock = new ReentrantLock(); | protected Lock accessTokenLock = new ReentrantLock(); | ||||
| protected Lock jsapiTicketLock = new ReentrantLock(); | |||||
| /** | /** | ||||
| * 临时文件目录 | * 临时文件目录 | ||||
| @@ -70,6 +74,33 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||||
| this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | ||||
| } | } | ||||
| @Override | |||||
| public String getJsapiTicket() { | |||||
| return this.jsapiTicket; | |||||
| } | |||||
| @Override | |||||
| public Lock getJsapiTicketLock() { | |||||
| return this.jsapiTicketLock; | |||||
| } | |||||
| @Override | |||||
| public boolean isJsapiTicketExpired() { | |||||
| return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||||
| } | |||||
| @Override | |||||
| public void expireJsapiTicket() { | |||||
| this.jsapiTicketExpiresTime = 0; | |||||
| } | |||||
| @Override | |||||
| public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||||
| this.jsapiTicket = jsapiTicket; | |||||
| // 预留200秒的时间 | |||||
| this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||||
| } | |||||
| @Override | @Override | ||||
| public void expireAccessToken() { | public void expireAccessToken() { | ||||
| this.expiresTime = 0; | this.expiresTime = 0; | ||||
| @@ -0,0 +1,45 @@ | |||||
| package cn.binarywang.wx.miniapp.api.impl; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||||
| import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||||
| import com.google.inject.Inject; | |||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import org.testng.annotations.Guice; | |||||
| import org.testng.annotations.Test; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/8/5. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Test | |||||
| @Guice(modules = ApiTestModule.class) | |||||
| public class WxMaJsapiServiceImplTest { | |||||
| @Inject | |||||
| private WxMaService wxService; | |||||
| @Inject | |||||
| private WxMaConfig wxMaConfig; | |||||
| @Test | |||||
| public void testGetJsapiTicket() throws WxErrorException { | |||||
| assertThat(this.wxService.getJsapiService().getJsapiTicket()).isNotBlank(); | |||||
| } | |||||
| @Test | |||||
| public void testGetJsapiTicket1() throws WxErrorException { | |||||
| assertThat(this.wxService.getJsapiService().getJsapiTicket(true)).isNotBlank(); | |||||
| } | |||||
| @Test | |||||
| public void testCreateJsapiSignature() throws WxErrorException { | |||||
| final WxJsapiSignature jsapiSignature = this.wxService.getJsapiService().createJsapiSignature("http://www.qq.com"); | |||||
| System.out.println(jsapiSignature); | |||||
| assertThat(jsapiSignature).isNotNull(); | |||||
| } | |||||
| } | |||||
| @@ -54,6 +54,7 @@ public class TestConfig extends WxMaInMemoryConfig { | |||||
| this.templateId = templateId; | this.templateId = templateId; | ||||
| } | } | ||||
| @Override | |||||
| public void setAccessTokenLock(Lock lock) { | public void setAccessTokenLock(Lock lock) { | ||||
| super.accessTokenLock = lock; | super.accessTokenLock = lock; | ||||
| } | } | ||||