wisdomPark/gather-app/src/main/java/com/ruoyi/database/service/AlipayPhoneService.java

317 lines
12 KiB
Java
Raw Normal View History

2025-11-26 11:16:51 +08:00
package com.ruoyi.database.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.database.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class AlipayPhoneService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
@Value("${alipay.appId}")
private String appId;
@Value("${alipay.privateKey}")
private String privateKey;
@Value("${alipay.gateway}")
private String gateway;
/**
* 通过授权码获取用户手机号
*/
public String getPhoneNumber(String authCode) {
try {
// 1. 构建请求参数
Map<String, String> params = buildBaseParams();
params.put("method", "alipay.system.oauth.token");
params.put("grant_type", "authorization_code");
params.put("code", authCode);
log.info("请求参数构建完成: {}", params);
// 2. 生成签名
String sign = generateSign(params);
params.put("sign", sign);
log.info("签名生成完成");
// 3. 调用支付宝接口 - 使用修复后的方法
String responseBody = callAlipayApiWithCharset(params);
log.info("支付宝手机号接口响应: {}", responseBody);
// 4. 解析响应
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
Map<String, Object> tokenResponse = (Map<String, Object>) responseMap.get("alipay_system_oauth_token_response");
if (tokenResponse == null) {
// 检查错误响应
Map<String, Object> errorResponse = (Map<String, Object>) responseMap.get("error_response");
if (errorResponse != null) {
String subCode = (String) errorResponse.get("sub_code");
String subMsg = (String) errorResponse.get("sub_msg");
throw new BusinessException("ALIPAY_PHONE_ERROR",
String.format("支付宝接口错误[%s]: %s", subCode, subMsg));
}
throw new BusinessException("ALIPAY_PHONE_ERROR", "支付宝接口响应格式错误");
}
// 5. 提取手机号
String mobile = (String) tokenResponse.get("mobile");
if (mobile == null || mobile.trim().isEmpty()) {
throw new BusinessException("ALIPAY_PHONE_ERROR", "获取手机号失败: 手机号为空");
}
// 6. 验证手机号格式
if (!validatePhoneNumber(mobile)) {
log.warn("获取到的手机号格式可能不正确: {}", mobile);
}
log.info("成功获取用户手机号: {}", mobile);
return mobile;
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("获取支付宝手机号异常", e);
throw new BusinessException("ALIPAY_PHONE_ERROR", "获取支付宝手机号异常: " + e.getMessage());
}
}
/**
* 修复后的API调用方法 - 正确处理charset参数
*/
private String callAlipayApiWithCharset(Map<String, String> params) {
try {
// 1. 将charset参数提取到URL查询字符串中
String charset = params.get("charset");
params.remove("charset"); // 从业务参数中移除
// 2. 构建表单数据
String formData = params.entrySet().stream()
.map(entry -> {
try {
// 使用指定的charset进行编码
String encodedValue = URLEncoder.encode(entry.getValue(), charset);
return entry.getKey() + "=" + encodedValue;
} catch (Exception e) {
log.error("参数编码失败: key={}, value={}", entry.getKey(), entry.getValue(), e);
return entry.getKey() + "=" + entry.getValue();
}
})
.collect(Collectors.joining("&"));
// 3. 构建完整的URL包含charset参数
String url = gateway + "?charset=" + charset;
log.debug("支付宝API请求URL: {}", url);
log.debug("支付宝API请求表单数据: {}", formData);
// 4. 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("User-Agent", "Alipay Client");
HttpEntity<String> request = new HttpEntity<>(formData, headers);
// 5. 调用接口
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
return response.getBody();
} catch (Exception e) {
log.error("调用支付宝API异常", e);
throw new BusinessException("ALIPAY_API_ERROR", "调用支付宝API异常: " + e.getMessage());
}
}
/**
* 修复签名生成方法
*/
private String generateSign(Map<String, String> params) {
try {
// 1. 参数排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 2. 拼接待签名字符串排除sign参数
String signContent = keys.stream()
.filter(key -> !"sign".equals(key) && params.get(key) != null && !params.get(key).isEmpty())
.map(key -> {
try {
// 使用UTF-8编码进行签名即使charset是其他值
String value = params.get(key);
return key + "=" + value;
} catch (Exception e) {
return key + "=" + params.get(key);
}
})
.collect(Collectors.joining("&"));
log.debug("待签名字符串: {}", signContent);
// 3. 使用RSA2签名
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(getPrivateKey());
signature.update(signContent.getBytes(StandardCharsets.UTF_8)); // 使用UTF-8编码签名
byte[] signed = signature.sign();
// 4. Base64编码
String sign = Base64.getEncoder().encodeToString(signed);
log.debug("生成签名: {}", sign);
return sign;
} catch (Exception e) {
log.error("生成支付宝签名异常", e);
throw new BusinessException("ALIPAY_SIGN_ERROR", "生成签名失败: " + e.getMessage());
}
}
/**
* 构建基础参数 - 确保charset正确
*/
private Map<String, String> buildBaseParams() {
Map<String, String> params = new HashMap<>();
params.put("app_id", appId);
params.put("charset", "UTF-8"); // 明确指定UTF-8
params.put("sign_type", "RSA2");
params.put("timestamp", getCurrentTimestamp());
params.put("version", "1.0");
params.put("format", "json");
return params;
}
/**
* 获取当前时间戳
*/
private String getCurrentTimestamp() {
return java.time.LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
}
/**
* 获取私钥
*/
private PrivateKey getPrivateKey() {
try {
String privateKeyContent = privateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
throw new BusinessException("ALIPAY_KEY_ERROR", "加载私钥失败: " + e.getMessage());
}
}
/**
* 调用支付宝API
*/
private String callAlipayApi(Map<String, String> params) {
try {
// 构建表单数据
String formData = params.entrySet().stream()
.map(entry -> {
try {
return entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name());
} catch (Exception e) {
return entry.getKey() + "=" + entry.getValue();
}
})
.collect(Collectors.joining("&"));
log.debug("支付宝API请求表单数据: {}", formData);
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("User-Agent", "Alipay Client");
HttpEntity<String> request = new HttpEntity<>(formData, headers);
// 调用接口
ResponseEntity<String> response = restTemplate.postForEntity(gateway, request, String.class);
return response.getBody();
} catch (Exception e) {
log.error("调用支付宝API异常", e);
throw new BusinessException("ALIPAY_API_ERROR", "调用支付宝API异常: " + e.getMessage());
}
}
/**
* 验证支付宝响应签名简化版实际需要完整实现
*/
private boolean verifySign(Map<String, Object> responseMap) {
try {
// 这里需要实现完整的支付宝响应签名验证
// 包括提取sign、排序参数、使用支付宝公钥验证等
// 生产环境必须实现此方法
log.warn("支付宝响应签名验证需要完整实现");
return true; // 测试环境暂时返回true
} catch (Exception e) {
log.error("验证支付宝签名异常", e);
return false;
}
}
/**
* 验证手机号格式
*/
public boolean validatePhoneNumber(String phoneNumber) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
return false;
}
// 简单的手机号格式验证
return phoneNumber.matches("^1[3-9]\\d{9}$");
}
/**
* 获取支付宝用户ID (如果需要)
*/
public String getAlipayUserId(String authCode) {
try {
String phoneNumber = getPhoneNumber(authCode);
// 在实际业务中你可能需要根据手机号关联用户ID
// 这里返回手机号作为示例实际应该返回支付宝用户ID
return phoneNumber;
} catch (Exception e) {
log.error("获取支付宝用户ID异常", e);
throw new BusinessException("ALIPAY_USER_ID_ERROR", "获取支付宝用户ID异常: " + e.getMessage());
}
}
}