支付宝和微信登录与支付

This commit is contained in:
hanrenchun 2025-11-26 11:16:51 +08:00
parent 1caf3bce41
commit 644cbc1dcb
19 changed files with 1424 additions and 2 deletions

View File

@ -35,5 +35,17 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.40.523.ALL</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package com.ruoyi.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AlipayConfig {
@Value("${alipay.appId}")
public String appId;
@Value("${alipay.privateKey}")
public String privateKey;
@Value("${alipay.publicKey}")
public String alipayPublicKey;
@Value("${alipay.notifyUrl}")
public String notifyUrl;
@Value("${alipay.gateway}")
public String gateway;
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 设置连接超时时间毫秒
factory.setConnectTimeout(15000);
// 设置读取超时时间毫秒
factory.setReadTimeout(15000);
return factory;
}
}

View File

@ -0,0 +1,44 @@
package com.ruoyi.config;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WxPayConfiguration {
@Value("${wx.pay.appId}")
private String appId;
@Value("${wx.pay.mchId}")
private String mchId;
@Value("${wx.pay.mchKey}")
private String mchKey;
@Value("${wx.pay.keyPath}")
private String keyPath;
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(appId);
payConfig.setMchId(mchId);
payConfig.setMchKey(mchKey);
payConfig.setKeyPath(keyPath);
return payConfig;
}
@Bean
public WxPayService wxPayService(WxPayConfig wxPayConfig) {
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(wxPayConfig);
return wxPayService;
}
}

View File

@ -0,0 +1,84 @@
package com.ruoyi.database.controller;
import com.alipay.api.internal.util.AlipaySignature;
import com.ruoyi.config.AlipayConfig;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/alipay")
public class AlipayNotifyController {
private final AlipayConfig alipayConfig;
// 假设你有一个处理订单业务的服务
// private final OrderService orderService;
public AlipayNotifyController(AlipayConfig alipayConfig) {
this.alipayConfig = alipayConfig;
}
@PostMapping("/notify")
public String handleNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
// 1. 将异步通知的参数转换成一个Map
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
try {
// 2. 验证签名非常重要确保请求来自支付宝
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
alipayConfig.alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!signVerified) {
// 签名验证失败记录日志并拒绝
System.err.println("支付宝异步通知签名验证失败");
return "failure";
}
// 3. 签名验证通过处理业务逻辑
String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no"); // 你的商户订单号
String tradeNo = params.get("trade_no"); // 支付宝交易号
// 4. 判断交易状态
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISH".equals(tradeStatus)) {
// 支付成功
// 4.1 幂等性处理查询本地订单是否已处理避免重复通知导致业务重复执行[citation:5]
// if (orderService.isOrderPaid(outTradeNo)) {
// return "success";
// }
// 4.2 校验金额重要防止金额被篡改[citation:5]
// String totalAmount = params.get("total_amount");
// if (!orderService.verifyOrderAmount(outTradeNo, totalAmount)) {
// return "failure";
// }
// 4.3 更新本地订单状态为已支付
// orderService.updateOrderToPaid(outTradeNo, tradeNo);
System.out.println("订单支付成功: " + outTradeNo + ", 支付宝交易号: " + tradeNo);
}
// 5. 处理成功返回 'success' 字符串给支付宝支付宝将停止重复通知[citation:5]
return "success";
} catch (Exception e) {
System.err.println("处理支付宝异步通知异常: " + e.getMessage());
return "failure";
}
}
}

View File

@ -0,0 +1,98 @@
package com.ruoyi.database.controller;
import com.ruoyi.database.domain.Order;
import com.ruoyi.database.service.OrderService;
import com.ruoyi.database.service.WechatMiniProgramPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@Autowired
private WechatMiniProgramPayService wechatPayService;
@Autowired
private OrderService orderService;
/**
* 创建支付订单
*/
@PostMapping("/create")
public Map<String, Object> createPayment(@RequestParam String openid,
@RequestParam String orderId,
@RequestParam Integer amount,
@RequestParam String description,
HttpServletRequest request) {
try {
System.out.println("收到创建支付订单请求: openid=" + openid +
", orderId=" + orderId + ", amount=" + amount);
// 1. 创建业务订单
orderService.createOrder(orderId, amount, description, openid);
// 2. 获取客户端IP
String clientIp = wechatPayService.getClientIpAddress(request);
// 3. 创建支付订单
Map<String, Object> result = wechatPayService.createJsapiOrder(
openid, orderId, amount, description, clientIp);
return result;
} catch (Exception e) {
System.err.println("创建支付订单异常: " + e.getMessage());
e.printStackTrace();
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "系统异常: " + e.getMessage());
return result;
}
}
/**
* 查询订单状态
*/
@GetMapping("/status/{orderId}")
public Map<String, Object> getOrderStatus(@PathVariable String orderId) {
try {
System.out.println("查询订单状态: " + orderId);
Order order = orderService.getOrder(orderId);
Map<String, Object> result = new HashMap<>();
if (order != null) {
result.put("success", true);
result.put("orderId", order.getOrderId());
result.put("status", order.getStatus().name());
result.put("amount", order.getAmount());
result.put("description", order.getDescription());
result.put("createTime", order.getCreateTime());
if (order.getPayTime() != null) {
result.put("payTime", order.getPayTime());
}
if (order.getTransactionId() != null) {
result.put("transactionId", order.getTransactionId());
}
} else {
result.put("success", false);
result.put("message", "订单不存在");
}
return result;
} catch (Exception e) {
System.err.println("查询订单状态异常: " + e.getMessage());
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "查询失败: " + e.getMessage());
return result;
}
}
}

View File

@ -0,0 +1,76 @@
package com.ruoyi.database.controller;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.service.WxPayService;
import com.ruoyi.database.service.OrderService;
import com.ruoyi.database.service.WechatMiniProgramPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/wxpay")
public class WxPayNotifyController {
@Autowired
private WechatMiniProgramPayService wechatPayService;
@Autowired
private OrderService orderService;
/**
* 微信支付结果通知回调
* 这个接口URL就是在创建订单时设置的 notifyUrl
*/
@PostMapping("/notify")
public String handleNotify(@RequestBody String xmlData) {
try {
System.out.println("========== 收到支付通知 ==========");
System.out.println("通知数据: " + xmlData);
// 1. 获取 wxPayService
WxPayService wxPayService = wechatPayService.getWxPayService();
// 2. 使用SDK解析并验证通知数据SDK会自动验证签名
WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
// 3. 获取关键业务参数
String orderId = notifyResult.getOutTradeNo();
String transactionId = notifyResult.getTransactionId();
Integer totalFee = notifyResult.getTotalFee();
String timeEnd = notifyResult.getTimeEnd();
System.out.println("解析通知数据: orderId=" + orderId +
", transactionId=" + transactionId + ", totalFee=" + totalFee);
// 4. 幂等性处理检查订单是否已处理
if (orderService.isOrderPaid(orderId)) {
System.out.println("订单已处理,直接返回成功");
return WxPayNotifyResponse.success("OK");
}
// 5. 校验订单金额重要防止资金损失
if (!orderService.verifyOrderAmount(orderId, totalFee)) {
System.out.println("订单金额校验失败");
return WxPayNotifyResponse.fail("订单金额不一致");
}
// 6. 处理业务逻辑更新订单状态等
orderService.updateOrderToPaid(orderId, transactionId);
System.out.println("支付通知处理成功");
System.out.println("========== 支付通知处理完成 ==========");
// 7. 返回成功响应必须返回success
return WxPayNotifyResponse.success("OK");
} catch (Exception e) {
System.err.println("支付通知处理失败: " + e.getMessage());
e.printStackTrace();
return WxPayNotifyResponse.fail("处理失败");
}
}
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.database.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class LoginRequest {
/**
* 登录方式: wechat / alipay
*/
@ApiModelProperty("登录方式: wechat 或 alipay")
@NotBlank(message = "登录方式不能为空")
private String loginType;
/**
* 微信或支付宝的授权code
*/
@ApiModelProperty("微信或支付宝的授权code")
@NotBlank(message = "授权code不能为空")
private String code;
}

View File

@ -0,0 +1,49 @@
package com.ruoyi.database.domain;
import com.ruoyi.database.enums.OrderStatus;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Order {
/**
* 商户订单号
*/
private String orderId;
/**
* 订单金额单位
*/
private Integer amount;
/**
* 订单描述
*/
private String description;
/**
* 用户openid
*/
private String openid;
/**
* 订单状态
*/
private OrderStatus status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 支付时间
*/
private LocalDateTime payTime;
/**
* 微信支付订单号
*/
private String transactionId;
}

View File

@ -0,0 +1,30 @@
package com.ruoyi.database.enums;
import lombok.Getter;
@Getter
public enum LoginType {
WECHAT("wechat", "微信登录"),
ALIPAY("alipay", "支付宝登录");
private final String code;
private final String description;
LoginType(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*/
public static LoginType getByCode(String code) {
for (LoginType type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new IllegalArgumentException("不支持的登录方式: " + code);
}
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.database.enums;
/**
* 订单状态枚举
*/
public enum OrderStatus {
PENDING, // 待支付
PAID, // 已支付
CANCELLED, // 已取消
REFUNDED // 已退款
}

View File

@ -0,0 +1,23 @@
package com.ruoyi.database.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final String code;
private final String message;
public BusinessException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(String message) {
super(message);
this.code = "500";
this.message = message;
}
}

View File

@ -0,0 +1,316 @@
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());
}
}
}

View File

@ -0,0 +1,73 @@
package com.ruoyi.database.service;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.ruoyi.config.AlipayConfig;
import org.springframework.stereotype.Service;
@Service
public class AlipayService {
private final AlipayConfig alipayConfig;
public AlipayService(AlipayConfig alipayConfig) {
this.alipayConfig = alipayConfig;
}
/**
* 创建支付宝小程序支付订单
*
* @param orderId 商户订单号
* @param amount 支付金额单位
* @param subject 订单标题
* @param buyerId 支付宝用户ID相当于OpenID
* @return 支付订单字符串用于小程序前端调起支付
*/
public String createOrder(String orderId, String amount, String subject, String buyerId) {
try {
// 1. 创建AlipayClient实例
AlipayClient alipayClient = new DefaultAlipayClient(
alipayConfig.gateway,
alipayConfig.appId,
alipayConfig.privateKey,
"json",
"UTF-8",
alipayConfig.alipayPublicKey,
"RSA2"
);
// 2. 创建API请求对象设置请求参数
AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
request.setNotifyUrl(alipayConfig.notifyUrl);
// 3. 组装业务参数
String bizContent = "{" +
"\"out_trade_no\":\"" + orderId + "\"," +
"\"total_amount\":\"" + amount + "\"," +
"\"subject\":\"" + subject + "\"," +
"\"buyer_id\":\"" + buyerId + "\"," +
"\"product_code\":\"JSAPI_PAY\"" + // 小程序场景固定值
"}";
request.setBizContent(bizContent);
// 4. 调用支付宝接口
AlipayTradeCreateResponse response = alipayClient.execute(request);
// 5. 处理响应
if (response.isSuccess()) {
System.out.println("支付宝预下单成功,交易号: " + response.getTradeNo());
// 返回给前端的订单字符串用于调起支付
return response.getOutTradeNo();
// 注意在实际开发中你可能需要返回一个包含更多信息的对象或重新构造一个支付参数字符串
// 具体返回格式需与前端调用 my.tradePay 时所需的参数保持一致
} else {
System.err.println("支付宝预下单失败: " + response.getMsg() + " - " + response.getSubMsg());
throw new RuntimeException("支付宝预下单失败");
}
} catch (Exception e) {
throw new RuntimeException("调用支付宝接口异常", e);
}
}
}

View File

@ -0,0 +1,88 @@
package com.ruoyi.database.service;
import com.ruoyi.database.domain.Order;
import com.ruoyi.database.enums.OrderStatus;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class OrderService {
/**
* 使用内存Map存储订单生产环境请用数据库
*/
private static final Map<String, Order> orderCache = new ConcurrentHashMap<>();
/**
* 创建订单
*/
public Order createOrder(String orderId, Integer amount, String description, String openid) {
Order order = new Order();
order.setOrderId(orderId);
order.setAmount(amount);
order.setDescription(description);
order.setOpenid(openid);
order.setStatus(OrderStatus.PENDING);
order.setCreateTime(LocalDateTime.now());
orderCache.put(orderId, order);
System.out.println("创建订单成功: " + orderId + ", 金额: " + amount + "");
return order;
}
/**
* 检查订单是否已支付幂等性处理
*/
public boolean isOrderPaid(String orderId) {
Order order = orderCache.get(orderId);
boolean isPaid = order != null && order.getStatus() == OrderStatus.PAID;
if (isPaid) {
System.out.println("订单已支付: " + orderId);
}
return isPaid;
}
/**
* 验证订单金额
*/
public boolean verifyOrderAmount(String orderId, Integer totalFee) {
Order order = orderCache.get(orderId);
boolean isValid = order != null && order.getAmount().equals(totalFee);
if (!isValid) {
System.out.println("订单金额验证失败: " + orderId +
", 期望: " + (order != null ? order.getAmount() : "null") +
", 实际: " + totalFee);
}
return isValid;
}
/**
* 更新订单状态为已支付
*/
public void updateOrderToPaid(String orderId, String transactionId) {
Order order = orderCache.get(orderId);
if (order != null) {
order.setStatus(OrderStatus.PAID);
order.setTransactionId(transactionId);
order.setPayTime(LocalDateTime.now());
orderCache.put(orderId, order);
System.out.println("更新订单状态为已支付: " + orderId +
", 微信订单号: " + transactionId);
// 这里可以添加其他业务逻辑如发送消息更新库存等
} else {
System.out.println("订单不存在: " + orderId);
}
}
/**
* 获取订单信息
*/
public Order getOrder(String orderId) {
return orderCache.get(orderId);
}
}

View File

@ -0,0 +1,178 @@
package com.ruoyi.database.service;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.service.WxPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.util.*;
@Service
public class WechatMiniProgramPayService {
@Autowired
private WxPayService wxPayService;
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
/**
* 创建小程序支付订单
*/
public Map<String, Object> createJsapiOrder(String openid, String orderId, Integer amount,
String description, String clientIp) {
try {
System.out.println("开始创建支付订单: " + orderId + ", openid: " + openid + ", 金额: " + amount + "");
// 构建支付请求
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(orderId) // 商户订单号
.totalFee(amount) // 金额
.body(description) // 商品描述
.tradeType("JSAPI") // 交易类型小程序支付
.openid(openid) // 用户openid
.spbillCreateIp(clientIp) // 终端IP
.notifyUrl(notifyUrl) // 支付结果通知地址
.build();
System.out.println("调用微信支付统一下单API...");
// 调用微信支付API
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);
// 生成小程序支付参数
Map<String, String> paymentParams = createPaymentParams(result);
System.out.println("支付订单创建成功: " + orderId + ", prepay_id: " + result.getPrepayId());
// 返回结果
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("paymentParams", paymentParams);
response.put("prepayId", result.getPrepayId());
return response;
} catch (Exception e) {
System.err.println("创建支付订单失败: " + orderId + ", 错误: " + e.getMessage());
e.printStackTrace();
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "创建支付订单失败: " + e.getMessage());
return response;
}
}
/**
* 生成小程序支付参数
*/
private Map<String, String> createPaymentParams(WxPayUnifiedOrderResult result) {
Map<String, String> paymentParams = new HashMap<>();
// 小程序支付必需的参数
paymentParams.put("appId", result.getAppid());
paymentParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
paymentParams.put("nonceStr", result.getNonceStr());
paymentParams.put("package", "prepay_id=" + result.getPrepayId());
paymentParams.put("signType", "MD5");
// 生成签名
String sign = generateSign(paymentParams);
paymentParams.put("paySign", sign);
System.out.println("生成支付参数: " + paymentParams);
return paymentParams;
}
/**
* 生成支付签名
*/
private String generateSign(Map<String, String> params) {
try {
// 按照参数名ASCII字典序排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 拼接字符串
StringBuilder sb = new StringBuilder();
for (String key : keys) {
if ("sign".equals(key) || key.isEmpty() || params.get(key) == null) {
continue;
}
sb.append(key).append("=").append(params.get(key)).append("&");
}
// 加上API密钥
sb.append("key=").append(wxPayService.getConfig().getMchKey());
String signStr = sb.toString();
System.out.println("签名原串: " + signStr);
// MD5加密并转大写
String sign = md5(signStr).toUpperCase();
System.out.println("生成签名: " + sign);
return sign;
} catch (Exception e) {
throw new RuntimeException("生成签名失败", e);
}
}
/**
* MD5加密
*/
private String md5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
}
/**
* 获取客户端IP地址
*/
public String getClientIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多级代理取第一个IP
if (ip != null && ip.contains(",")) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
// 本地开发环境处理
if ("0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip)) {
ip = "123.123.123.123"; // 替换为你的服务器公网IP
}
System.out.println("获取到客户端IP: " + ip);
return ip;
}
/**
* 获取WxPayService实例
*/
public WxPayService getWxPayService() {
return wxPayService;
}
}

View File

@ -0,0 +1,191 @@
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.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class WechatPhoneService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
@Value("${wechat.appId}")
private String appId;
@Value("${wechat.appSecret}")
private String appSecret;
@Value("${wechat.getPhoneNumberUrl}")
private String getPhoneNumberUrl;
@Value("${wechat.accessTokenUrl}")
private String accessTokenUrl;
/**
* 获取微信访问令牌
*/
public String getAccessToken() {
try {
String url = String.format("%s?grant_type=client_credential&appid=%s&secret=%s",
accessTokenUrl, appId, appSecret);
log.info("请求微信access_token, URL: {}", url);
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
String responseBody = response.getBody();
log.info("微信access_token响应: {}", responseBody);
// 解析响应
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
String accessToken = (String) result.get("access_token");
Integer errcode = (Integer) result.get("errcode");
if (accessToken != null) {
log.info("成功获取微信access_token");
return accessToken;
} else if (errcode != null) {
String errmsg = (String) result.get("errmsg");
log.error("获取微信access_token失败: {} - {}", errcode, errmsg);
throw new BusinessException("WECHAT_ACCESS_TOKEN_ERROR",
String.format("获取微信访问令牌失败[%s]: %s", errcode, errmsg));
} else {
throw new BusinessException("WECHAT_ACCESS_TOKEN_ERROR", "获取微信访问令牌失败: 响应格式错误");
}
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("获取微信access_token异常", e);
throw new BusinessException("WECHAT_ACCESS_TOKEN_ERROR", "获取微信访问令牌异常: " + e.getMessage());
}
}
/**
* 通过code获取用户手机号
*/
public String getPhoneNumber(String code) {
try {
// 1. 获取access_token
String accessToken = getAccessToken();
// 2. 构建请求参数
Map<String, String> requestBody = new HashMap<>();
requestBody.put("code", code);
String url = getPhoneNumberUrl + "?access_token=" + accessToken;
log.info("请求微信手机号接口, URL: {}", url);
log.debug("请求参数: code={}", code);
// 3. 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
// 4. 调用微信接口
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
String responseBody = response.getBody();
log.info("微信手机号接口响应: {}", responseBody);
// 5. 解析响应
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
Integer errcode = (Integer) result.get("errcode");
String errmsg = (String) result.get("errmsg");
if (errcode != null && errcode != 0) {
log.error("获取微信手机号失败: {} - {}", errcode, errmsg);
throw new BusinessException("WECHAT_PHONE_ERROR",
String.format("获取手机号失败[%s]: %s", errcode, errmsg));
}
// 6. 提取手机号信息
Map<String, Object> phoneInfo = (Map<String, Object>) result.get("phone_info");
if (phoneInfo == null) {
throw new BusinessException("WECHAT_PHONE_ERROR", "获取手机号失败: 响应中缺少phone_info");
}
String phoneNumber = (String) phoneInfo.get("phoneNumber");
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
throw new BusinessException("WECHAT_PHONE_ERROR", "获取手机号失败: 手机号为空");
}
// 7. 验证手机号格式
if (!validatePhoneNumber(phoneNumber)) {
log.warn("获取到的手机号格式可能不正确: {}", phoneNumber);
// 这里可以根据业务需求决定是否抛出异常
}
log.info("成功获取用户手机号: {}", phoneNumber);
return phoneNumber;
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("获取微信手机号异常", e);
throw new BusinessException("WECHAT_PHONE_ERROR", "获取微信手机号异常: " + e.getMessage());
}
}
/**
* 验证手机号格式
*/
public boolean validatePhoneNumber(String phoneNumber) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
return false;
}
// 简单的手机号格式验证
return phoneNumber.matches("^1[3-9]\\d{9}$");
}
/**
* 获取微信用户openid (如果需要)
*/
public String getOpenId(String jsCode) {
try {
String url = String.format(
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
appId, appSecret, jsCode
);
log.info("请求微信openid, URL: {}", url.replace(jsCode, "***"));
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
String responseBody = response.getBody();
log.info("微信openid响应: {}", responseBody);
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
String openid = (String) result.get("openid");
Integer errcode = (Integer) result.get("errcode");
if (openid != null) {
log.info("成功获取微信openid");
return openid;
} else if (errcode != null) {
String errmsg = (String) result.get("errmsg");
throw new BusinessException("WECHAT_OPENID_ERROR",
String.format("获取openid失败[%s]: %s", errcode, errmsg));
} else {
throw new BusinessException("WECHAT_OPENID_ERROR", "获取openid失败: 响应格式错误");
}
} catch (Exception e) {
log.error("获取微信openid异常", e);
throw new BusinessException("WECHAT_OPENID_ERROR", "获取微信openid异常: " + e.getMessage());
}
}
}

View File

@ -10,7 +10,11 @@ import com.ruoyi.common.core.domain.model.LoginBodyByPhone;
import com.ruoyi.common.core.domain.model.LoginUserByPhone;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.database.domain.BaseCustomerInfo;
import com.ruoyi.database.domain.LoginRequest;
import com.ruoyi.database.enums.LoginType;
import com.ruoyi.database.service.AlipayPhoneService;
import com.ruoyi.database.service.BaseCustomerInfoService;
import com.ruoyi.database.service.WechatPhoneService;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.system.service.ISysMenuService;
@ -47,6 +51,12 @@ public class SysLoginController {
@Autowired
private BaseCustomerInfoService baseCustomerInfoService;
@Autowired
private WechatPhoneService wechatPhoneService;
@Autowired
private AlipayPhoneService alipayPhoneService;
/**
* 登录方法
*
@ -80,7 +90,13 @@ public class SysLoginController {
@ApiOperation("根据手机号登录(没有的用户会创建)")
@PostMapping("/loginByPhone")
public AjaxResult loginByPhone(@Valid @RequestBody LoginBodyByPhone loginBodyByPhone) {
public AjaxResult loginByPhone(@Valid @RequestBody LoginRequest request) {
validateLoginRequest(request);
String phoneNumber = getPhoneNumberByLoginType(request);
LoginBodyByPhone loginBodyByPhone = new LoginBodyByPhone();
loginBodyByPhone.setPhone(phoneNumber);
AjaxResult ajax = AjaxResult.success();
// 生成令牌
BaseCustomerInfo one = baseCustomerInfoService.lambdaQuery()
@ -97,6 +113,41 @@ public class SysLoginController {
ajax.put(Constants.TOKEN, token);
return ajax;
}
private void validateLoginRequest(LoginRequest request) {
if (request.getLoginType() == null || request.getLoginType().trim().isEmpty()) {
throw new IllegalArgumentException("登录方式不能为空");
}
if (request.getCode() == null || request.getCode().trim().isEmpty()) {
throw new IllegalArgumentException("授权code不能为空");
}
// 验证登录方式是否支持
try {
LoginType.getByCode(request.getLoginType());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("不支持的登录方式: " + request.getLoginType());
}
}
/**
* 根据登录方式获取手机号
*/
private String getPhoneNumberByLoginType(LoginRequest request) {
LoginType loginType = LoginType.getByCode(request.getLoginType());
switch (loginType) {
case WECHAT:
return wechatPhoneService.getPhoneNumber(request.getCode());
case ALIPAY:
return alipayPhoneService.getPhoneNumber(request.getCode());
default:
throw new IllegalArgumentException("不支持的登录方式: " + loginType);
}
}
/**
* 获取用户信息
*

View File

@ -18,7 +18,7 @@ ruoyi:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 9188
port: 9669
servlet:
# 应用的访问路径
context-path: /wisdomPark/
@ -131,3 +131,28 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 微信支付配置
wx:
pay:
appId: wxea120f6f0e955dc2 # 你的小程序AppID
mchId: 1251484401 # 你的商户号
mchKey: QloRc8RZcKpOVb4hKPFJKRG9stmqSfxD # 你的API密钥(32位)
keyPath: classpath:cert/apiclient_cert.p12 # 证书路径
notifyUrl: https://tingche.csckl.com/api/wxpay/notify
alipay:
appId: 2021006113615353
privateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7yP787s/Qr36GMnfAb/Blj9NR9voGYfGT/AsC0BGKE+NcymBhA55i27I+QfiXSsb/f/kz1ByMtZxoYHwz7Ha/la5FPps3SgwQNp+CnMIo0DpiP+3i5eVWb+Tffh8C0uL/LzfZKNxaGya68hkG9TitHf7XvBt+Z0Fdy2zKNZZn3xAhr0F6lEmZ645jp8cetDvqpemv95FCJ/2oTB5QDjy65+beOdeES1uvx0HopA+MJyMlJ08K/gopCfNYRqgOnmcDFb6kixUcajdAn8+pjaDpX0RIHnxmOmbcgDUOZS8pakfzbtc/NaSZa25FxXVer5LHR8dNIPKyZKslOHV+MvCtAgMBAAECggEAc/rfKh7hMXXCgmXXOn6ojkG3dc9BewGxMAIiVtlli89exKelwSV4BHzGaNdcDy07HQDQTGd8PpfIg1rcO0GX0hpDaTcSrKJKMxVZjVT+QjDhbl8nsNTmwW0YgcSmqJrRYmSqbLBk8C4GzL3MIVUBbUKybFIc9QQXBjQm+/j+7SV0Kr9hKTA/q4hk4gguem8Cg2+z752uM31eEExD71UlauTvAyj9C4kuhYOnSThpz1UeWZxhOl9n0ij/guEVXhMjlnYf9fvE/gV6yxuTxKWa4NWhTpqDHv6hwm0kjvL3spfTRorAfM7yeXmFYe1grVs5v4GVGPRQLP3/e71CMhGLTQKBgQDgcngshP07uht37yoXbX3bl4E15QU5FKQ7AhU+ZTCTJrmHIJFyBew7oEq34FnM4HPut8V/6BT3PfQJoAgLfNprieuXDiLa/ngdlmBmh5ywITnwC9aCLpwtJjN+bCU7mraQkd7X7ZTo0uiiHYGLTbBWcVAZ/0/zy32C0tsJVe5WWwKBgQDWLx0Z4FMAq4B4pdQfAD3DgqVaxBZAD6Pd4pFXCYBqFgw/RRYE44Gp1KqyfdP1f/JwoFdF/UtOWMe+RUdoU86Iqm2657/PWMSDjCPuYJ2KlIV5cqReXR/XycyIg64Gz+4wGjO788SzGG7oV9V++VZeZtVC2GKkxLlNIeha3SPTlwKBgQCQAAEoFv7dlg7PiOtJcp8gBvkIWfJa5+piTXjRulxK3LqFfpiyPiiNLM8jX9Zdgtf+nXsiEAV7hw0OK9VdgXIlMqyrEo+8Ty1DVlptDa6yte6VGxM6HKtosCyzfLD6p81fvabqhGxYYBCPCj014dcz7qlEtLBYKwNQrvWG7NHoJwKBgEcGxqq/FidhuC/KJibFqLW+DGGmJw7FYP0C1lm7n+cOOq98lIm4fFY6XQTl7zU5YcdxlJ90GNX5YPu686woJpRxmAPkOFplQH2zKnMxfOzoxb55gwJ79URU+kvHBx3hNEalWcSZWQBfOOA5yGXb/4U8qroJEeU8C4sFlI0VSn0vAoGBANOfx8geSo22AuqQKmX27cE4aw8YSPAbiGmjMb4K6wDVeiH2CjO1/boG0D+0F8CO9JDR4llIB8YixgkwFlhuVMk1jouTkOqH+TLE1gqZC5iE7lMCnL1K/OsOH+zGoQ4dP6MRBfcMEG8HyCZw5Uo180fHSwPYLSushxbOG3sHaffJ
apppublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAokc3c5rqr8+KIbzmnBciiSCsn8iNth5DYAiVfIFmDZ+aEdcBY8rft/5n9UQGFdMgOGFZpyZIhsbTXU0RUKDiVNtTUw7rTfDHY2NyoBhQJgH319bmay1p9+wOUkvy6OmJuz0r6lCP3ajJdkWNgbPARDAAZg+L8KJxLwXz44l90IUBtDbqX7AF+pOXlQLKeDsH1sIANSGNnS4ocy4Vk9doRJb0SpX+XHaEz/IA4gDC8S8uFG4S4lI+MIpq05ef1K7k6PlgcGNxP2m8SyMelL6FOgcCSofqBdj6OUzERVquig7/p0LJA3gdveqzRkmuOVF8SsuKpw8ulsLrfNTGtCWtXQIDAQAB
publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhHGI9i9ewOf1lKHkLy8rQS2eLgYwheyzhrfi0fX1BDB7CWKoKu4kqDT52CYP6438K7NvANzh+aSaXvi1vZEAf2ociyjozKypj2qKynL6nW/sLyXWDbCU2u41WJv9iDvm8l/AF6qcanoKSSzZPUFsXHD+ZDsGj/3EyLF+FUN7vQw1Bj9BmYnushjTL/0KQWQNbrZeSHtEPYkhiVGKRD63ZETQPqcTU4vOncmNSb879Z40dAHxob2qUzh7743hk8PnTMkDpacfolMTeeRdDrdsLgmaLypyWtKXe8DASKXE92YnW5Yq8Vkb3aZiS+u903WWNfatOoOGIn0XyuA6T5OCbwIDAQAB
notifyUrl: https://tingche.csckl.com/alipay/notify
gateway: https://openapi.alipay.com/gateway.do # 生产环境
wechat:
appId: wxea120f6f0e955dc2
appSecret: 0c08946e824179e42b1d9c27ce8f8a03
# 获取手机号接口
getPhoneNumberUrl: https://api.weixin.qq.com/wxa/business/getuserphonenumber
# 获取access_token接口
accessTokenUrl: https://api.weixin.qq.com/cgi-bin/token