WxMiniProgramHttpClient.java 6.58 KB
package com.infoloop.tianting.service.client;

import com.infoloop.tianting.model.dto.WxUserDto.WxUserOpenIdDto;
import com.infoloop.tianting.model.dto.WxUserDto.WxUserPhoneResponseDto;
import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto;
import com.infoloop.tianting.store.WxAccessTokenStore;
import com.infoloop.tianting.utils.JsonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

import static com.infoloop.tianting.constant.ConfigConstants.MINI_PROGRAM_APP_ID;
import static com.infoloop.tianting.constant.ConfigConstants.MINI_PROGRAM_APP_SECRET;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WxMiniProgramHttpClient {

    private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";
    private static final String PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={accessToken}";

    private final RestTemplate restTemplate;

    private final WxAccessTokenStore wxAccessTokenStore;

    @Value(MINI_PROGRAM_APP_ID)
    private String appId;

    @Value(MINI_PROGRAM_APP_SECRET)
    private String appSecret;

    public WxUserPhoneResponseDto getUserPhoneInfoByCode(String code) {
        try {
            String accessToken = fetchStableAccessToken();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            Map<String, Object> jsonDataMap = new HashMap<>();
            jsonDataMap.put("code", code);
            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(jsonDataMap, headers);
            return fetchPhoneNumber(accessToken, requestEntity);
        } catch (Exception e) {
            log.error("Error fetching user phone info", e);
            throw new IllegalArgumentException("获取用户手机号失败");
        }
    }

    private String fetchAccessToken() {
        log.info("Fetching WeChat access token...");
        final var code = wxAccessTokenStore.getAccessToken();
        if (StringUtils.isNotEmpty(code)) {
            log.info("Using cached access token: {}", code);
            return code;
        }
        ResponseEntity<String> tokenResponse = restTemplate.getForEntity(TOKEN_URL, String.class, appId, appSecret);
        if (!tokenResponse.getStatusCode().is2xxSuccessful() || tokenResponse.getBody() == null) {
            log.error("Failed to fetch access token: {}", tokenResponse);
            throw new IllegalArgumentException("获取 access_token 失败");
        }
        final var wxUserTokenDto = JsonUtil.readJsonAs(tokenResponse.getBody(), WxUserTokenDto.class);
        log.info("WeChat access token received: {}", wxUserTokenDto);
        final var accessToken = wxUserTokenDto.getAccess_token();
        wxAccessTokenStore.setAccessToken(accessToken, wxUserTokenDto.getExpires_in());
        return accessToken;
    }

    private final Object lock = new Object();

    public String fetchStableAccessToken() {
        log.info("Fetching WeChat Stable access token...");
        String code = wxAccessTokenStore.getAccessToken();
        if (StringUtils.isNotEmpty(code)) {
            log.info("Using cached Stable access token: {}", code);
            return code;
        }
        synchronized (lock) {
            code = wxAccessTokenStore.getAccessToken();
            if (StringUtils.isNotEmpty(code)) {
                log.info("Using cached Stable access token after lock: {}", code);
                return code;
            }
            String url = "https://api.weixin.qq.com/cgi-bin/stable_token";
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            Map<String, String> requestBody = new HashMap<>();
            requestBody.put("grant_type", "client_credential");
            requestBody.put("appid", appId);
            requestBody.put("secret", appSecret);
            HttpEntity<Map<String, String>> request = new HttpEntity<>(requestBody, headers);
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
                log.error("Failed to fetch Stable access token: {}", response);
                throw new IllegalArgumentException("获取 stable access_token 失败");
            }
            WxUserTokenDto tokenDto = JsonUtil.readJsonAs(response.getBody(), WxUserTokenDto.class);
            log.info("WeChat Stable access token received: {}", tokenDto);
            final var accessToken = tokenDto.getAccess_token();
            wxAccessTokenStore.setAccessToken(accessToken, tokenDto.getExpires_in());
            return accessToken;
        }
    }

    private WxUserPhoneResponseDto fetchPhoneNumber(String accessToken, HttpEntity<Map<String, Object>> requestEntity) {
        log.info("Fetching phone number...");
        ResponseEntity<WxUserPhoneResponseDto> phoneResponse = restTemplate.exchange(PHONE_URL, HttpMethod.POST, requestEntity, WxUserPhoneResponseDto.class, accessToken);
        if (!phoneResponse.getStatusCode().is2xxSuccessful() || phoneResponse.getBody() == null) {
            log.error("Failed to fetch phone number: {}", phoneResponse);
            throw new IllegalArgumentException("获取手机号失败");
        }
        log.info("WeChat phone number response: {}", phoneResponse.getBody());
        return phoneResponse.getBody();
    }

    public WxUserOpenIdDto getUserOpenIdByCode(String code) {
        try {
            final var url = "https://api.weixin.qq.com/sns/jscode2session" +
                    "?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";
            final var response = restTemplate.getForEntity(url, String.class);
            return JsonUtil.readJsonAs(response.getBody(), WxUserOpenIdDto.class);
        } catch (Exception e) {
            log.error("Error fetching user openid", e);
            throw new IllegalArgumentException("获取用户openid失败");
        }
    }

}