|
|
@@ -0,0 +1,135 @@
|
|
|
+package cn.start.tz.module.member.service.member;
|
|
|
+
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
+import cn.hutool.crypto.digest.DigestUtil;
|
|
|
+import cn.start.tz.framework.common.pojo.CommonResult;
|
|
|
+import cn.start.tz.framework.common.util.json.JsonUtils;
|
|
|
+
|
|
|
+import cn.start.tz.module.member.service.member.vo.MemberUserCreateReqVO;
|
|
|
+import cn.start.tz.module.member.service.member.vo.MemberUserRespVO;
|
|
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import jakarta.annotation.Resource;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class MemberRemoteServiceImpl implements MemberRemoteService {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * nonce Redis Key 模板: member_remote_nonce:{appId}:{nonce}
|
|
|
+ */
|
|
|
+ private static final String NONCE_KEY_FORMAT = "member_remote_nonce:%s:%s";
|
|
|
+
|
|
|
+ private static final TypeReference<CommonResult<MemberUserRespVO>> USER_RESULT_TYPE =
|
|
|
+ new TypeReference<CommonResult<MemberUserRespVO>>() {};
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private StringRedisTemplate stringRedisTemplate;
|
|
|
+
|
|
|
+ @Value("${app-auth.base-url:http://localhost:48080}")
|
|
|
+ private String baseUrl;
|
|
|
+
|
|
|
+ @Value("${app-auth.app-id:xxxxxx}")
|
|
|
+ private String appId;
|
|
|
+
|
|
|
+ @Value("${app-auth.app-secret:yyyyyy}")
|
|
|
+ private String appSecret;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public MemberUserRespVO createUser(MemberUserCreateReqVO reqVO) {
|
|
|
+ long timestamp = System.currentTimeMillis();
|
|
|
+ String nonce = IdUtil.fastSimpleUUID();
|
|
|
+
|
|
|
+ // 请求体序列化
|
|
|
+ String bodyJson = JsonUtils.toJsonString(reqVO);
|
|
|
+
|
|
|
+ // 签名计算:第1步(查询参数为空)+ 第2步(请求体)+ 第3步(Header排序)+ appSecret
|
|
|
+ String signString = buildSignString("", bodyJson, timestamp, nonce);
|
|
|
+ String sign = DigestUtil.sha256Hex(signString);
|
|
|
+
|
|
|
+ // nonce 存 Redis 防重放
|
|
|
+ String nonceKey = String.format(NONCE_KEY_FORMAT, appId, nonce);
|
|
|
+ stringRedisTemplate.opsForValue().set(nonceKey, "1", Duration.ofSeconds(120));
|
|
|
+
|
|
|
+ HttpHeaders headers = buildSignHeaders(timestamp, nonce, sign);
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(bodyJson, headers);
|
|
|
+
|
|
|
+ String url = baseUrl + "/external-api/member/user/create";
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(
|
|
|
+ url, HttpMethod.POST, entity, String.class);
|
|
|
+
|
|
|
+ return parseResponse(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public MemberUserRespVO getUserByMobile(String mobile) {
|
|
|
+ long timestamp = System.currentTimeMillis();
|
|
|
+ String nonce = IdUtil.fastSimpleUUID();
|
|
|
+
|
|
|
+ // 查询参数按 key 字典序拼接: mobile={mobile}
|
|
|
+ String queryStr = "mobile=" + mobile;
|
|
|
+
|
|
|
+ // 签名计算:第1步(查询参数)+ 第2步(空)+ 第3步(Header排序)+ appSecret
|
|
|
+ String signString = buildSignString(queryStr, "", timestamp, nonce);
|
|
|
+ String sign = DigestUtil.sha256Hex(signString);
|
|
|
+
|
|
|
+ // nonce 存 Redis 防重放
|
|
|
+ String nonceKey = String.format(NONCE_KEY_FORMAT, appId, nonce);
|
|
|
+ stringRedisTemplate.opsForValue().set(nonceKey, "1", Duration.ofSeconds(120));
|
|
|
+
|
|
|
+ HttpHeaders headers = buildSignHeaders(timestamp, nonce, sign);
|
|
|
+ HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
|
+
|
|
|
+ String url = baseUrl + "/external-api/member/user/getByMobile?mobile=" + mobile;
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(
|
|
|
+ url, HttpMethod.GET, entity, String.class);
|
|
|
+
|
|
|
+ return parseResponse(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildSignString(String queryParams, String requestBody, long timestamp, String nonce) {
|
|
|
+ // 第1步 + 第2步 + 第3步(Header按key字典序: appId < nonce < timestamp) + appSecret
|
|
|
+ return queryParams
|
|
|
+ + requestBody
|
|
|
+ + "appId=" + appId + "&nonce=" + nonce + "×tamp=" + timestamp
|
|
|
+ + appSecret;
|
|
|
+ }
|
|
|
+
|
|
|
+ private HttpHeaders buildSignHeaders(long timestamp, String nonce, String sign) {
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.set("appId", appId);
|
|
|
+ headers.set("timestamp", String.valueOf(timestamp));
|
|
|
+ headers.set("nonce", nonce);
|
|
|
+ headers.set("sign", sign);
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ private MemberUserRespVO parseResponse(ResponseEntity<String> response) {
|
|
|
+ if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
|
|
+ CommonResult<MemberUserRespVO> result = JsonUtils.parseObject(
|
|
|
+ response.getBody(), USER_RESULT_TYPE);
|
|
|
+ if (result != null && result.isSuccess()) {
|
|
|
+ return result.getData();
|
|
|
+ }
|
|
|
+ log.warn("[parseResponse][接口返回失败: code={}, msg={}]",
|
|
|
+ result != null ? result.getCode() : null,
|
|
|
+ result != null ? result.getMsg() : null);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ log.warn("[parseResponse][HTTP状态异常: {}]", response.getStatusCode());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|