Showing
5 changed files
with
90 additions
and
26 deletions
| ... | @@ -7,6 +7,7 @@ import com.infoloop.tianting.server.StorageService; | ... | @@ -7,6 +7,7 @@ import com.infoloop.tianting.server.StorageService; |
| 7 | import com.infoloop.tianting.server.StorageServiceFactory; | 7 | import com.infoloop.tianting.server.StorageServiceFactory; |
| 8 | import com.infoloop.tianting.store.LoginCodeStore; | 8 | import com.infoloop.tianting.store.LoginCodeStore; |
| 9 | import com.infoloop.tianting.store.WeeklyTaskStore; | 9 | import com.infoloop.tianting.store.WeeklyTaskStore; |
| 10 | +import com.infoloop.tianting.store.WxAccessTokenStore; | ||
| 10 | import com.infoloop.tianting.utils.SmsUtil; | 11 | import com.infoloop.tianting.utils.SmsUtil; |
| 11 | import io.lettuce.core.api.StatefulRedisConnection; | 12 | import io.lettuce.core.api.StatefulRedisConnection; |
| 12 | import org.springframework.beans.factory.annotation.Autowired; | 13 | import org.springframework.beans.factory.annotation.Autowired; |
| ... | @@ -33,6 +34,12 @@ public class AppConfig { | ... | @@ -33,6 +34,12 @@ public class AppConfig { |
| 33 | } | 34 | } |
| 34 | 35 | ||
| 35 | @Bean | 36 | @Bean |
| 37 | + public WxAccessTokenStore wxAccessTokenStore(@Autowired @Qualifier(REDIS_CONNECTION) final StatefulRedisConnection<String, String> connection) { | ||
| 38 | + final var commands = connection.sync(); | ||
| 39 | + return new WxAccessTokenStore(commands); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Bean | ||
| 36 | public WeeklyTaskStore weeklyTaskStore(@Autowired @Qualifier(REDIS_CONNECTION) final StatefulRedisConnection<String, String> connection) { | 43 | public WeeklyTaskStore weeklyTaskStore(@Autowired @Qualifier(REDIS_CONNECTION) final StatefulRedisConnection<String, String> connection) { |
| 37 | final var commands = connection.sync(); | 44 | final var commands = connection.sync(); |
| 38 | final var expireSeconds = Duration.ofDays(8).getSeconds(); | 45 | final var expireSeconds = Duration.ofDays(8).getSeconds(); | ... | ... |
| ... | @@ -4,6 +4,8 @@ public class RedisKeyConstants { | ... | @@ -4,6 +4,8 @@ public class RedisKeyConstants { |
| 4 | 4 | ||
| 5 | public static final String REPEAT_SUBMIT = "REPEAT_SUBMIT"; | 5 | public static final String REPEAT_SUBMIT = "REPEAT_SUBMIT"; |
| 6 | 6 | ||
| 7 | + public static final String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN"; | ||
| 8 | + | ||
| 7 | public static String getSMSCodeKey(String phone) { | 9 | public static String getSMSCodeKey(String phone) { |
| 8 | return "sms_code:" + phone; | 10 | return "sms_code:" + phone; |
| 9 | } | 11 | } | ... | ... |
| ... | @@ -16,14 +16,7 @@ public class WxUserDto { | ... | @@ -16,14 +16,7 @@ public class WxUserDto { |
| 16 | @NoArgsConstructor | 16 | @NoArgsConstructor |
| 17 | @AllArgsConstructor | 17 | @AllArgsConstructor |
| 18 | public static class WxUserOpenIdDto { | 18 | public static class WxUserOpenIdDto { |
| 19 | - public String openid; | 19 | + private String openid; |
| 20 | - } | ||
| 21 | - | ||
| 22 | - @Data | ||
| 23 | - @Builder | ||
| 24 | - @ApiModel(description = "WxUserOpenIdDto") | ||
| 25 | - public static class WxUserOpenIdDto1 { | ||
| 26 | - public String openId; | ||
| 27 | } | 20 | } |
| 28 | 21 | ||
| 29 | @Data | 22 | @Data |
| ... | @@ -32,7 +25,8 @@ public class WxUserDto { | ... | @@ -32,7 +25,8 @@ public class WxUserDto { |
| 32 | @NoArgsConstructor | 25 | @NoArgsConstructor |
| 33 | @AllArgsConstructor | 26 | @AllArgsConstructor |
| 34 | public static class WxUserTokenDto { | 27 | public static class WxUserTokenDto { |
| 35 | - public String access_token; | 28 | + private String access_token; |
| 29 | + private int expires_in; | ||
| 36 | } | 30 | } |
| 37 | 31 | ||
| 38 | @Data | 32 | @Data |
| ... | @@ -41,8 +35,8 @@ public class WxUserDto { | ... | @@ -41,8 +35,8 @@ public class WxUserDto { |
| 41 | @NoArgsConstructor | 35 | @NoArgsConstructor |
| 42 | @AllArgsConstructor | 36 | @AllArgsConstructor |
| 43 | public static class WxUserPhoneInfoDto { | 37 | public static class WxUserPhoneInfoDto { |
| 44 | - public String phoneNumber; | 38 | + private String phoneNumber; |
| 45 | - public String countryCode; | 39 | + private String countryCode; |
| 46 | } | 40 | } |
| 47 | 41 | ||
| 48 | 42 | ||
| ... | @@ -52,19 +46,10 @@ public class WxUserDto { | ... | @@ -52,19 +46,10 @@ public class WxUserDto { |
| 52 | @NoArgsConstructor | 46 | @NoArgsConstructor |
| 53 | @AllArgsConstructor | 47 | @AllArgsConstructor |
| 54 | public static class WxUserPhoneResponseDto { | 48 | public static class WxUserPhoneResponseDto { |
| 55 | - public Integer errcode; | 49 | + private Integer errcode; |
| 56 | - public String errmsg; | 50 | + private String errmsg; |
| 57 | - public String phone; | 51 | + private String phone; |
| 58 | - public WxUserPhoneInfoDto phone_info; | 52 | + private WxUserPhoneInfoDto phone_info; |
| 59 | - } | ||
| 60 | - | ||
| 61 | - @Data | ||
| 62 | - @Builder | ||
| 63 | - @ApiModel(description = "WxUserCodeDto") | ||
| 64 | - @NoArgsConstructor | ||
| 65 | - @AllArgsConstructor | ||
| 66 | - public static class WxUserCodeDto { | ||
| 67 | - public String code; | ||
| 68 | } | 53 | } |
| 69 | 54 | ||
| 70 | } | 55 | } | ... | ... |
| ... | @@ -3,9 +3,11 @@ package com.infoloop.tianting.service.client; | ... | @@ -3,9 +3,11 @@ package com.infoloop.tianting.service.client; |
| 3 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserOpenIdDto; | 3 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserOpenIdDto; |
| 4 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserPhoneResponseDto; | 4 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserPhoneResponseDto; |
| 5 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto; | 5 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto; |
| 6 | +import com.infoloop.tianting.store.WxAccessTokenStore; | ||
| 6 | import com.infoloop.tianting.utils.JsonUtil; | 7 | import com.infoloop.tianting.utils.JsonUtil; |
| 7 | import lombok.RequiredArgsConstructor; | 8 | import lombok.RequiredArgsConstructor; |
| 8 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | +import org.apache.commons.lang3.StringUtils; | ||
| 9 | import org.springframework.beans.factory.annotation.Autowired; | 11 | import org.springframework.beans.factory.annotation.Autowired; |
| 10 | import org.springframework.beans.factory.annotation.Value; | 12 | import org.springframework.beans.factory.annotation.Value; |
| 11 | import org.springframework.http.HttpEntity; | 13 | import org.springframework.http.HttpEntity; |
| ... | @@ -32,6 +34,8 @@ public class WxMiniProgramHttpClient { | ... | @@ -32,6 +34,8 @@ public class WxMiniProgramHttpClient { |
| 32 | 34 | ||
| 33 | private final RestTemplate restTemplate; | 35 | private final RestTemplate restTemplate; |
| 34 | 36 | ||
| 37 | + private final WxAccessTokenStore wxAccessTokenStore; | ||
| 38 | + | ||
| 35 | @Value(MINI_PROGRAM_APP_ID) | 39 | @Value(MINI_PROGRAM_APP_ID) |
| 36 | private String appId; | 40 | private String appId; |
| 37 | 41 | ||
| ... | @@ -40,7 +44,7 @@ public class WxMiniProgramHttpClient { | ... | @@ -40,7 +44,7 @@ public class WxMiniProgramHttpClient { |
| 40 | 44 | ||
| 41 | public WxUserPhoneResponseDto getUserPhoneInfoByCode(String code) { | 45 | public WxUserPhoneResponseDto getUserPhoneInfoByCode(String code) { |
| 42 | try { | 46 | try { |
| 43 | - String accessToken = fetchAccessToken(); | 47 | + String accessToken = fetchStableAccessToken(); |
| 44 | HttpHeaders headers = new HttpHeaders(); | 48 | HttpHeaders headers = new HttpHeaders(); |
| 45 | headers.setContentType(MediaType.APPLICATION_JSON); | 49 | headers.setContentType(MediaType.APPLICATION_JSON); |
| 46 | Map<String, Object> jsonDataMap = new HashMap<>(); | 50 | Map<String, Object> jsonDataMap = new HashMap<>(); |
| ... | @@ -55,6 +59,11 @@ public class WxMiniProgramHttpClient { | ... | @@ -55,6 +59,11 @@ public class WxMiniProgramHttpClient { |
| 55 | 59 | ||
| 56 | private String fetchAccessToken() { | 60 | private String fetchAccessToken() { |
| 57 | log.info("Fetching WeChat access token..."); | 61 | log.info("Fetching WeChat access token..."); |
| 62 | + final var code = wxAccessTokenStore.getAccessToken(); | ||
| 63 | + if (StringUtils.isNotEmpty(code)) { | ||
| 64 | + log.info("Using cached access token: {}", code); | ||
| 65 | + return code; | ||
| 66 | + } | ||
| 58 | ResponseEntity<String> tokenResponse = restTemplate.getForEntity(TOKEN_URL, String.class, appId, appSecret); | 67 | ResponseEntity<String> tokenResponse = restTemplate.getForEntity(TOKEN_URL, String.class, appId, appSecret); |
| 59 | if (!tokenResponse.getStatusCode().is2xxSuccessful() || tokenResponse.getBody() == null) { | 68 | if (!tokenResponse.getStatusCode().is2xxSuccessful() || tokenResponse.getBody() == null) { |
| 60 | log.error("Failed to fetch access token: {}", tokenResponse); | 69 | log.error("Failed to fetch access token: {}", tokenResponse); |
| ... | @@ -62,7 +71,45 @@ public class WxMiniProgramHttpClient { | ... | @@ -62,7 +71,45 @@ public class WxMiniProgramHttpClient { |
| 62 | } | 71 | } |
| 63 | final var wxUserTokenDto = JsonUtil.readJsonAs(tokenResponse.getBody(), WxUserTokenDto.class); | 72 | final var wxUserTokenDto = JsonUtil.readJsonAs(tokenResponse.getBody(), WxUserTokenDto.class); |
| 64 | log.info("WeChat access token received: {}", wxUserTokenDto); | 73 | log.info("WeChat access token received: {}", wxUserTokenDto); |
| 65 | - return wxUserTokenDto.getAccess_token(); | 74 | + final var accessToken = wxUserTokenDto.getAccess_token(); |
| 75 | + wxAccessTokenStore.setAccessToken(accessToken, wxUserTokenDto.getExpires_in()); | ||
| 76 | + return accessToken; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + private final Object lock = new Object(); | ||
| 80 | + | ||
| 81 | + public String fetchStableAccessToken() { | ||
| 82 | + log.info("Fetching WeChat Stable access token..."); | ||
| 83 | + String code = wxAccessTokenStore.getAccessToken(); | ||
| 84 | + if (StringUtils.isNotEmpty(code)) { | ||
| 85 | + log.info("Using cached Stable access token: {}", code); | ||
| 86 | + return code; | ||
| 87 | + } | ||
| 88 | + synchronized (lock) { | ||
| 89 | + code = wxAccessTokenStore.getAccessToken(); | ||
| 90 | + if (StringUtils.isNotEmpty(code)) { | ||
| 91 | + log.info("Using cached Stable access token after lock: {}", code); | ||
| 92 | + return code; | ||
| 93 | + } | ||
| 94 | + String url = "https://api.weixin.qq.com/cgi-bin/stable_token"; | ||
| 95 | + HttpHeaders headers = new HttpHeaders(); | ||
| 96 | + headers.setContentType(MediaType.APPLICATION_JSON); | ||
| 97 | + Map<String, String> requestBody = new HashMap<>(); | ||
| 98 | + requestBody.put("grant_type", "client_credential"); | ||
| 99 | + requestBody.put("appid", appId); | ||
| 100 | + requestBody.put("secret", appSecret); | ||
| 101 | + HttpEntity<Map<String, String>> request = new HttpEntity<>(requestBody, headers); | ||
| 102 | + ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class); | ||
| 103 | + if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) { | ||
| 104 | + log.error("Failed to fetch Stable access token: {}", response); | ||
| 105 | + throw new IllegalArgumentException("获取 stable access_token 失败"); | ||
| 106 | + } | ||
| 107 | + WxUserTokenDto tokenDto = JsonUtil.readJsonAs(response.getBody(), WxUserTokenDto.class); | ||
| 108 | + log.info("WeChat Stable access token received: {}", tokenDto); | ||
| 109 | + final var accessToken = tokenDto.getAccess_token(); | ||
| 110 | + wxAccessTokenStore.setAccessToken(accessToken, tokenDto.getExpires_in()); | ||
| 111 | + return accessToken; | ||
| 112 | + } | ||
| 66 | } | 113 | } |
| 67 | 114 | ||
| 68 | private WxUserPhoneResponseDto fetchPhoneNumber(String accessToken, HttpEntity<Map<String, Object>> requestEntity) { | 115 | private WxUserPhoneResponseDto fetchPhoneNumber(String accessToken, HttpEntity<Map<String, Object>> requestEntity) { | ... | ... |
| 1 | +package com.infoloop.tianting.store; | ||
| 2 | + | ||
| 3 | +import com.infoloop.tianting.constant.RedisKeyConstants; | ||
| 4 | +import io.lettuce.core.SetArgs; | ||
| 5 | +import io.lettuce.core.api.sync.RedisCommands; | ||
| 6 | + | ||
| 7 | +public class WxAccessTokenStore { | ||
| 8 | + | ||
| 9 | + private final RedisCommands<String, String> commands; | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + public WxAccessTokenStore(final RedisCommands<String, String> commands) { | ||
| 13 | + this.commands = commands; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + public void setAccessToken(final String accessToken, final int expire) { | ||
| 17 | + commands.set(RedisKeyConstants.WX_ACCESS_TOKEN, accessToken, new SetArgs().ex(expire)); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public String getAccessToken() { | ||
| 21 | + return commands.get((RedisKeyConstants.WX_ACCESS_TOKEN)); | ||
| 22 | + } | ||
| 23 | +} |
-
Please register or login to post a comment