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

397 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.ruoyi.database.service;
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;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.database.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
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 javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
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;
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());
}
}
/**
* 第一步:使用授权码获取访问令牌
*/
private String getAccessToken(String authCode) {
try {
Map<String, String> params = buildBaseParams();
params.put("method", "alipay.system.oauth.token");
params.put("grant_type", "authorization_code");
params.put("code", authCode);
log.info("获取access_token请求参数: {}", params);
String sign = generateSign(params);
params.put("sign", sign);
String responseBody = callAlipayApiWithCharset(params);
log.info("获取access_token响应: {}", responseBody);
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_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));
}
throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息失败: 响应格式错误");
}
// 提取手机号
String mobile = (String) userResponse.get("mobile");
if (mobile == null || mobile.trim().isEmpty()) {
// 有些用户可能没有绑定手机号
log.warn("用户未绑定手机号或手机号为空");
throw new BusinessException("ALIPAY_PHONE_EMPTY", "用户未绑定手机号");
}
// 验证手机号格式
if (!validatePhoneNumber(mobile)) {
log.warn("获取到的手机号格式可能不正确: {}", mobile);
}
log.info("成功获取用户手机号: {}", mobile);
return mobile;
} catch (Exception e) {
log.error("获取用户信息异常", e);
throw new BusinessException("ALIPAY_USER_INFO_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());
}
}
}