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

397 lines
15 KiB
Java
Raw Normal View History

2025-11-26 11:16:51 +08:00
package com.ruoyi.database.service;
2025-11-28 14:35:30 +08:00
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipayEncrypt;
import com.alipay.api.internal.util.AlipaySignature;
2025-11-26 11:16:51 +08:00
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.database.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
2025-11-28 14:35:30 +08:00
import org.checkerframework.checker.units.qual.A;
2025-11-26 11:16:51 +08:00
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;
2025-11-28 14:35:30 +08:00
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
2025-11-26 11:16:51 +08:00
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;
2025-11-28 14:35:30 +08:00
private final String AES_STR = "LoKBapEj35+bRnC/Rrhe8g==";
public String getPhoneNumber(String authCode) {
try {
// 1. 使用auth_code获取access_token
// String accessToken = getAccessToken(authCode);
System.out.println("authCode:" + authCode);
String charset = "UTF-8";
String encryptType = "AES";
//判断是否为加密内容
boolean isDataEncrypted = !authCode.startsWith("{");
//3. 解密
String plainData = null;
if (isDataEncrypted) {
try {
plainData = AlipayEncrypt.decryptContent(authCode, encryptType, AES_STR, charset);
System.out.println("解密:" + plainData);
} catch (AlipayApiException e) {
//解密异常, 记录日志
throw new Exception("解密异常");
}
} else {
plainData = authCode;
}
// 2. 使用access_token获取用户信息包含手机号
Map<String, Object> responseMap = objectMapper.readValue(plainData, Map.class);
return responseMap.get("mobile").toString();
} catch (Exception e) {
log.error("获取支付宝手机号异常", e);
throw new BusinessException("ALIPAY_PHONE_ERROR", "获取支付宝手机号异常: " + e.getMessage());
}
}
2025-11-26 11:16:51 +08:00
/**
2025-11-28 14:35:30 +08:00
* 第一步使用授权码获取访问令牌
2025-11-26 11:16:51 +08:00
*/
2025-11-28 14:35:30 +08:00
private String getAccessToken(String authCode) {
2025-11-26 11:16:51 +08:00
try {
Map<String, String> params = buildBaseParams();
params.put("method", "alipay.system.oauth.token");
params.put("grant_type", "authorization_code");
params.put("code", authCode);
2025-11-28 14:35:30 +08:00
log.info("获取access_token请求参数: {}", params);
2025-11-26 11:16:51 +08:00
String sign = generateSign(params);
params.put("sign", sign);
String responseBody = callAlipayApiWithCharset(params);
2025-11-28 14:35:30 +08:00
log.info("获取access_token响应: {}", responseBody);
2025-11-26 11:16:51 +08:00
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");
2025-11-28 14:35:30 +08:00
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR",
String.format("获取access_token失败[%s]: %s", subCode, subMsg));
}
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: 响应格式错误");
}
String accessToken = (String) tokenResponse.get("access_token");
if (accessToken == null || accessToken.trim().isEmpty()) {
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: access_token为空");
}
log.info("成功获取access_token: {}", accessToken);
return accessToken;
} catch (Exception e) {
log.error("获取access_token异常", e);
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token异常: " + e.getMessage());
}
}
/**
* 第二步使用access_token获取用户信息包含手机号
*/
private String getUserPhoneNumber(String accessToken) {
try {
Map<String, String> params = buildBaseParams();
params.put("method", "alipay.user.info.share");
params.put("auth_token", accessToken);
log.info("获取用户信息请求参数: {}", params);
String sign = generateSign(params);
params.put("sign", sign);
String responseBody = callAlipayApiWithCharset(params);
log.info("获取用户信息响应: {}", responseBody);
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
Map<String, Object> userResponse = (Map<String, Object>) responseMap.get("alipay_user_info_share_response");
if (userResponse == 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_USER_INFO_ERROR",
String.format("获取用户信息失败[%s]: %s", subCode, subMsg));
2025-11-26 11:16:51 +08:00
}
2025-11-28 14:35:30 +08:00
throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息失败: 响应格式错误");
2025-11-26 11:16:51 +08:00
}
2025-11-28 14:35:30 +08:00
// 提取手机号
String mobile = (String) userResponse.get("mobile");
2025-11-26 11:16:51 +08:00
if (mobile == null || mobile.trim().isEmpty()) {
2025-11-28 14:35:30 +08:00
// 有些用户可能没有绑定手机号
log.warn("用户未绑定手机号或手机号为空");
throw new BusinessException("ALIPAY_PHONE_EMPTY", "用户未绑定手机号");
2025-11-26 11:16:51 +08:00
}
2025-11-28 14:35:30 +08:00
// 验证手机号格式
2025-11-26 11:16:51 +08:00
if (!validatePhoneNumber(mobile)) {
log.warn("获取到的手机号格式可能不正确: {}", mobile);
}
log.info("成功获取用户手机号: {}", mobile);
return mobile;
} catch (Exception e) {
2025-11-28 14:35:30 +08:00
log.error("获取用户信息异常", e);
throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息异常: " + e.getMessage());
2025-11-26 11:16:51 +08:00
}
}
/**
* 修复后的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());
}
}
}