| @@ -1,13 +1,21 @@ | |||
| package me.chanjar.weixin.common.bean; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Builder; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serializable; | |||
| /** | |||
| * jspai signature. | |||
| * | |||
| * @author chanjarster | |||
| */ | |||
| @Data | |||
| @Builder | |||
| @NoArgsConstructor | |||
| @AllArgsConstructor | |||
| public class WxJsapiSignature implements Serializable { | |||
| 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.config.WxMaConfig; | |||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| 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 JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | |||
| /** | |||
| * 获取登录后的session信息 | |||
| * | |||
| @@ -149,6 +151,13 @@ public interface WxMaService { | |||
| */ | |||
| WxMaCodeService getCodeService(); | |||
| /** | |||
| * 返回jsapi操作相关的 API服务类对象 | |||
| * | |||
| * @return WxMaJsapiService | |||
| */ | |||
| WxMaJsapiService getJsapiService(); | |||
| /** | |||
| * 小程序修改服务器地址、成员管理 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; | |||
| 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.config.WxMaConfig; | |||
| import com.google.common.base.Joiner; | |||
| @@ -58,6 +50,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| private WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this); | |||
| private WxMaCodeService codeService = new WxMaCodeServiceImpl(this); | |||
| private WxMaSettingService settingService = new WxMaSettingServiceImpl(this); | |||
| private WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); | |||
| private int retrySleepMillis = 1000; | |||
| private int maxRetryTimes = 5; | |||
| @@ -310,6 +303,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| return this.codeService; | |||
| } | |||
| @Override | |||
| public WxMaJsapiService getJsapiService() { | |||
| return this.jsapiService; | |||
| } | |||
| @Override | |||
| public WxMaSettingService getSettingService() { | |||
| return this.settingService; | |||
| @@ -38,6 +38,25 @@ public interface WxMaConfig { | |||
| */ | |||
| 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 getSecret(); | |||
| @@ -27,7 +27,11 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
| protected volatile String httpProxyUsername; | |||
| protected volatile String httpProxyPassword; | |||
| protected volatile String jsapiTicket; | |||
| protected volatile long jsapiTicketExpiresTime; | |||
| 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; | |||
| } | |||
| @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 | |||
| public void expireAccessToken() { | |||
| 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; | |||
| } | |||
| @Override | |||
| public void setAccessTokenLock(Lock lock) { | |||
| super.accessTokenLock = lock; | |||
| } | |||