add
This commit is contained in:
94
app/utils/AliUtils.php
Normal file
94
app/utils/AliUtils.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace app\utils;
|
||||
|
||||
use app\exception\SysException;
|
||||
use think\facade\Log;
|
||||
|
||||
class AliUtils
|
||||
{
|
||||
public static function verify($name, $idCard, $accountNo, $bankPreMobile)
|
||||
{
|
||||
Log::info("阿里云四要素认证请求参数: {$name}, {$idCard}, {$accountNo}, {$bankPreMobile}");
|
||||
|
||||
// 参数校验
|
||||
if (empty($name)) {
|
||||
throw new SysException("持卡人姓名不能为空");
|
||||
}
|
||||
if (empty($idCard)) {
|
||||
throw new SysException("身份证号码不能为空");
|
||||
}
|
||||
if (empty($accountNo)) {
|
||||
throw new SysException("银行卡卡号不能为空");
|
||||
}
|
||||
if (empty($bankPreMobile)) {
|
||||
throw new SysException("银行预留手机号码不能为空");
|
||||
}
|
||||
if (!preg_match('/^[\x{4e00}-\x{9fa5}]{2,}$/u', $name)) {
|
||||
throw new SysException("持卡人姓名不合法");
|
||||
}
|
||||
if (!self::isValidIdCard($idCard)) {
|
||||
throw new SysException("身份证号码不合法");
|
||||
}
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $bankPreMobile)) {
|
||||
throw new SysException("银行预留手机号码格式不正确");
|
||||
}
|
||||
|
||||
$appcode = 'f98606b602564d209f37fc02b0bd590c';
|
||||
$headers = [
|
||||
"Authorization: APPCODE {$appcode}",
|
||||
"Content-Type: application/x-www-form-urlencoded; charset=UTF-8"
|
||||
];
|
||||
|
||||
$url = 'https://bkvip.market.alicloudapi.com/v3/bcheck';
|
||||
$body = http_build_query([
|
||||
'accountNo' => $accountNo,
|
||||
'name' => $name,
|
||||
'idCardCode' => $idCard,
|
||||
'bankPreMobile' => $bankPreMobile
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
Log::info("阿里云四要素: {$idCard}, {$name}, {$accountNo}, {$bankPreMobile}, 结果: {$response}");
|
||||
|
||||
$ret = json_decode($response, true);
|
||||
if (!isset($ret['error_code']) || !isset($ret['result'])) {
|
||||
throw new SysException("阿里云接口返回格式错误".$response);
|
||||
}
|
||||
|
||||
$errorCode = $ret['error_code'];
|
||||
$result = $ret['result'];
|
||||
$respCode = $result['respCode'] ?? '';
|
||||
|
||||
if ($errorCode == 0 && $respCode === "0") {
|
||||
// 验证通过
|
||||
} else {
|
||||
throw new SysException($result['respMsg'] ?? '认证失败');
|
||||
}
|
||||
|
||||
$bankName = $result['bancardInfor']['bankName'] ?? null;
|
||||
|
||||
return [
|
||||
'bankName' => $bankName,
|
||||
'respJson' => $response
|
||||
];
|
||||
}
|
||||
|
||||
private static function isValidIdCard($idCard)
|
||||
{
|
||||
// 简单校验身份证号
|
||||
return preg_match('/^\d{17}[\dXx]$/', $idCard);
|
||||
}
|
||||
}
|
||||
86
app/utils/JwtUtils.php
Normal file
86
app/utils/JwtUtils.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace app\utils;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
class JwtUtils
|
||||
{
|
||||
private string $secret = "f4e2e52034348f86b67cde581c0f9eb5";
|
||||
private int $expire = 604800; // 单位:秒
|
||||
private string $header = "token";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->secret = base64_decode($this->secret);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成 JWT Token
|
||||
*/
|
||||
public function generateToken($userId, string $type): string
|
||||
{
|
||||
$now = time();
|
||||
$payload = [
|
||||
'sub' => (string)$userId,
|
||||
'type' => $type,
|
||||
'iat' => $now,
|
||||
'exp' => $now + $this->expire
|
||||
];
|
||||
|
||||
return JWT::encode($payload, $this->secret, 'HS512');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 token 中解析出 Claims
|
||||
*/
|
||||
public function getClaimByToken(string $token): ?object
|
||||
{
|
||||
try {
|
||||
return JWT::decode($token, new Key($this->secret, 'HS512'));
|
||||
} catch (\Exception $e) {
|
||||
error_log('Token 验证失败: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 token 是否过期(通过传入的 exp 字段)
|
||||
*/
|
||||
public function isTokenExpired(int $exp): bool
|
||||
{
|
||||
return $exp < time();
|
||||
}
|
||||
|
||||
// Getter/Setter
|
||||
public function getSecret(): string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function getExpire(): int
|
||||
{
|
||||
return $this->expire;
|
||||
}
|
||||
|
||||
public function getHeader(): string
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function setSecret(string $secret): void
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
public function setExpire(int $expire): void
|
||||
{
|
||||
$this->expire = $expire;
|
||||
}
|
||||
|
||||
public function setHeader(string $header): void
|
||||
{
|
||||
$this->header = $header;
|
||||
}
|
||||
}
|
||||
230
app/utils/RedisUtils.php
Normal file
230
app/utils/RedisUtils.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace app\utils;
|
||||
|
||||
use app\exception\SysException;
|
||||
use support\think\Cache;
|
||||
|
||||
class RedisUtils
|
||||
{
|
||||
const CAN_CREATE_ORDER = 'can_create_order:';
|
||||
const CREATE_ORDER_LIMIT = 'createOrder:';
|
||||
const CAN_CASH = 'cash:canCash:';
|
||||
|
||||
public static function checkCanCreateOrder($userId)
|
||||
{
|
||||
$val = cache(self::CAN_CREATE_ORDER . $userId);
|
||||
return empty($val);
|
||||
}
|
||||
|
||||
public static function setUserCanCreateOrder($userId, $seconds)
|
||||
{
|
||||
cache(self::CAN_CREATE_ORDER . $userId, 1, $seconds);
|
||||
}
|
||||
|
||||
public static function setCreateOrderFlagAndCheckLimit($userId, $orderId): bool
|
||||
{
|
||||
// 构造 key
|
||||
$key = "createOrder:{$userId}:{$orderId}";
|
||||
|
||||
// 设置 Redis key,值为 orderId,过期时间 60 秒
|
||||
Cache::store('redis')->set($key, (string)$orderId, 60);
|
||||
|
||||
// 获取 Redis 原生实例
|
||||
$redis = Cache::store('redis')->handler();
|
||||
|
||||
// 使用 scan 非阻塞统计 key 数量
|
||||
$pattern = "createOrder:{$userId}:*";
|
||||
$iterator = null;
|
||||
$count = 0;
|
||||
|
||||
while ($keys = $redis->scan($iterator, $pattern, 100)) {
|
||||
$count += count($keys);
|
||||
}
|
||||
|
||||
// 超过 22 返回 true
|
||||
return $count > 22;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的“按周统计”计数器(自动按课程 ID、业务类型区分)
|
||||
*
|
||||
* @param string $prefix 前缀标识,如 'course:week_play:'
|
||||
* @param int|string $id 主体 ID(如课程 ID)
|
||||
* @param int $expireSeconds 默认过期时间(秒),默认 7 天
|
||||
* @return int 返回当前统计值
|
||||
*/
|
||||
public static function incrWeekCounter(string $prefix, $id, int $expireSeconds = 604800): int
|
||||
{
|
||||
$key = $prefix . date('oW') . ':' . $id; // oW 为当前“ISO 年 + 周数”,确保每周一个 key
|
||||
$count = Cache::store('redis')->inc($key);
|
||||
|
||||
// 第一次设置时设定过期时间
|
||||
if ($count === 1) {
|
||||
Cache::store('redis')->expire($key, $expireSeconds);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
||||
public static function getconfig()
|
||||
{
|
||||
return Cache::getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定“周统计”计数值
|
||||
*
|
||||
* @param string $prefix 前缀
|
||||
* @param int|string $id 主体 ID
|
||||
* @return int
|
||||
*/
|
||||
public static function getWeekCounter(string $prefix, $id): int
|
||||
{
|
||||
$key = $prefix . date('oW') . ':' . $id;
|
||||
return (int)Cache::store('redis')->get($key, 0);
|
||||
}
|
||||
|
||||
public static function isCanCash($userId)
|
||||
{
|
||||
$val = cache(self::CAN_CASH . $userId);
|
||||
return !empty($val);
|
||||
}
|
||||
|
||||
public static function checkRealCount(int $userId)
|
||||
{
|
||||
$key = 'updateAuthCertInfo:' . $userId;
|
||||
$val = cache($key);
|
||||
if (!empty($val)) {
|
||||
throw new SysException("实名修改失败: 每月可修改次数已用完,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
public static function setRealCount(int $userId)
|
||||
{
|
||||
$key = 'updateAuthCertInfo:' . $userId;
|
||||
cache($key, 2628000);
|
||||
}
|
||||
|
||||
private static function getFreeWatchKey($userId, $permanently = false)
|
||||
{
|
||||
if ($permanently) {
|
||||
return "free:watch:" . $userId;
|
||||
}
|
||||
|
||||
return "free:watch:" . date('Y-m-d') . ':' . $userId;
|
||||
}
|
||||
|
||||
public static function isExpiredSet($key)
|
||||
{
|
||||
$redis = Cache::store('redis')->handler();
|
||||
|
||||
// 获取 key 的剩余过期时间(单位:秒)
|
||||
$ttl = $redis->ttl($key);
|
||||
|
||||
if ($ttl == -1) {
|
||||
return false;
|
||||
}
|
||||
return $ttl !== -2;
|
||||
}
|
||||
|
||||
public static function getFreeWatchTimeIsExpire($userId)
|
||||
{
|
||||
$freeWatchKey = self::getFreeWatchKey($userId, true);
|
||||
$permanentlyFreeWatch = cache($freeWatchKey);
|
||||
|
||||
$watchKey = self::getFreeWatchKey($userId, false);
|
||||
$payFreeWatchInfo = cache($watchKey);
|
||||
|
||||
$expireTime = -1;
|
||||
$jsonObject = null;
|
||||
|
||||
if (!empty($payFreeWatchInfo)) {
|
||||
$jsonObject = json_decode($payFreeWatchInfo, true);
|
||||
$expireTime = isset($jsonObject['expireTime']) ? $jsonObject['expireTime'] : -1;
|
||||
}
|
||||
|
||||
$now = time(); // 当前时间戳
|
||||
|
||||
if ((!empty($permanentlyFreeWatch) && RedisUtils::isExpiredSet($freeWatchKey)) ||
|
||||
(!empty($permanentlyFreeWatch) && $now >= $expireTime)) {
|
||||
|
||||
if (empty($permanentlyFreeWatch)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!RedisUtils::isExpiredSet($freeWatchKey)) {
|
||||
cache($freeWatchKey, intval($permanentlyFreeWatch));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (empty($payFreeWatchInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$second = isset($jsonObject['second']) ? intval($jsonObject['second']) : 0;
|
||||
|
||||
if ($expireTime == -1) {
|
||||
$jsonObject['expireTime'] = $now + $second;
|
||||
|
||||
$tomorrowZero = strtotime(date('Y-m-d', strtotime('+1 day')));
|
||||
$expire = $tomorrowZero - $now;
|
||||
|
||||
cache($watchKey, json_encode($jsonObject), $expire);
|
||||
return false;
|
||||
} else {
|
||||
return $now > $expireTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function setFreeWatchTime($userId, $second, $isPermanently = false)
|
||||
{
|
||||
$now = time();
|
||||
$tomorrow = strtotime(date('Y-m-d', strtotime('+1 day')));
|
||||
$freeWatchKey = self::getFreeWatchKey($userId, $isPermanently);
|
||||
|
||||
if ($isPermanently) {
|
||||
$data = cache($freeWatchKey);
|
||||
if (empty($data)) {
|
||||
// 永久的,但不设置过期时间(-1)
|
||||
cache($freeWatchKey, $second);
|
||||
} else {
|
||||
$expire = Cache::store('redis')->handler()->ttl($freeWatchKey);
|
||||
if ($expire === -1 || $expire === false || $expire === null) {
|
||||
$expire = -1;
|
||||
} else {
|
||||
$expire += intval($second);
|
||||
}
|
||||
|
||||
$newValue = intval($data) + intval($second);
|
||||
if ($expire > 0) {
|
||||
cache($freeWatchKey, $newValue, $expire);
|
||||
} else {
|
||||
cache($freeWatchKey, $newValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 非永久,临时有效期到明天零点
|
||||
$expire = $tomorrow - $now;
|
||||
|
||||
$jsonObject = [
|
||||
'expireTime' => -1,
|
||||
'second' => intval($second)
|
||||
];
|
||||
|
||||
// 如果不存在才设置
|
||||
if (!Cache::store('redis')->has($freeWatchKey)) {
|
||||
cache($freeWatchKey, json_encode($jsonObject), $expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function setCanCashFlag(mixed $userId, int $param)
|
||||
{
|
||||
cache("cash:canCash:".$userId, $param,300);
|
||||
}
|
||||
|
||||
}
|
||||
192
app/utils/WuYouPayUtils.php
Normal file
192
app/utils/WuYouPayUtils.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace app\utils;
|
||||
|
||||
use app\api\model\CommonInfo;
|
||||
use app\exception\SysException;
|
||||
use think\facade\Log;
|
||||
|
||||
class WuYouPayUtils
|
||||
{
|
||||
|
||||
private static string $mchId;
|
||||
private static string $payUrl;
|
||||
private static string $notifyUrl;
|
||||
private static string $h5BaseUrl;
|
||||
private static string $secret;
|
||||
private static string $extractUrl;
|
||||
private static string $extractNotifyUrl;
|
||||
private static string $queryUrl;
|
||||
|
||||
private static function boot(): void
|
||||
{
|
||||
static $booted = false;
|
||||
if ($booted) {
|
||||
return;
|
||||
}
|
||||
$booted = true;
|
||||
|
||||
self::$mchId = config('wuyou.merchant_id', '');
|
||||
self::$payUrl = config('wuyou.url', '');
|
||||
self::$notifyUrl = config('wuyou.notify_url', '');
|
||||
self::$h5BaseUrl = config('wuyou.h5_base_url', '');
|
||||
self::$secret = config('wuyou.secret', '');
|
||||
self::$extractUrl = config('wuyou.extract_url', '');
|
||||
self::$extractNotifyUrl = config('wuyou.extract_notify_url', '');
|
||||
self::$queryUrl = config('wuyou.query_url', '');
|
||||
|
||||
if (hasEmpty(self::$mchId, self::$payUrl, self::$notifyUrl, self::$h5BaseUrl, self::$secret)) {
|
||||
throw new SysException('缺少必要参数');
|
||||
}
|
||||
}
|
||||
|
||||
public static function getParamsSign(array $params): string
|
||||
{
|
||||
self::boot();
|
||||
ksort($params);
|
||||
|
||||
$signStr = '';
|
||||
foreach ($params as $key => $value) {
|
||||
$signStr .= $key . '=' . $value . '&';
|
||||
}
|
||||
|
||||
$signStr .= 'key=' . self::$secret;
|
||||
|
||||
return strtoupper(md5($signStr));
|
||||
}
|
||||
|
||||
private static function getBaseParams()
|
||||
{
|
||||
self::boot();
|
||||
return [
|
||||
'mch_id' => self::$mchId,
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function payOrderTest($orderNo, $userId, $amount, $userAgent, $allId, $payType) {
|
||||
return http_post('192.168.1.21:8080/api/delay', [
|
||||
'callbacks' => 'CODE_SUCCESS',
|
||||
'order_sn' => uuid(),
|
||||
'out_trade_no' => "{$orderNo}-{$userId}",
|
||||
'pay_time' => time(),
|
||||
'sign' => '',
|
||||
'total' => $amount
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
public static function payOrder($orderNo, $userId, $amount, $userAgent, $allId, $payType)
|
||||
{
|
||||
$payConfig = (new CommonInfo())->getByCode(926)['value'] ?? '0';
|
||||
if ($payConfig !== '1') {
|
||||
throw new SysException('暂无支付渠道');
|
||||
}
|
||||
|
||||
$params = self::getBaseParams();
|
||||
$params['type'] = '6001';
|
||||
$params['is_code'] = '1';
|
||||
$params['out_trade_no'] = "{$orderNo}-{$userId}";
|
||||
$params['total'] = $amount;
|
||||
$params['notify_url'] = self::$notifyUrl;
|
||||
|
||||
$params['sign'] = self::getParamsSign($params);
|
||||
if ($payType === 'h5') {
|
||||
$params['return_url'] = self::$h5BaseUrl . $allId;
|
||||
}
|
||||
|
||||
|
||||
return self::requestPost(self::$payUrl, $params, $userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起 POST 请求
|
||||
* @param string $url 请求地址
|
||||
* @param array $params 请求参数
|
||||
* @param string $userAgent User-Agent
|
||||
* @return array|null
|
||||
*/
|
||||
private static function requestPost(string $url, array $params, string $userAgent): ?array
|
||||
{
|
||||
Log::info("[支付]请求地址:{$url},请求参数:".json_encode($params).",User-Agent:{$userAgent}");
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($params),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'User-Agent: ' . $userAgent,
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
// 'Content-Type: application/json',
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
Log::info("[支付]返回结果:{$response}");
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
public static function queryExtractOrder($outOrderNo, $userId, $isUser, $amount)
|
||||
{
|
||||
$params = self::getBaseParams();
|
||||
$params['out_trade_no'] = sprintf('%s-%s:%s', $outOrderNo, $userId, $isUser ? 'us' : 'dl');
|
||||
$params['total'] = $amount;
|
||||
$params['sign'] = self::getParamsSign($params);
|
||||
|
||||
return self::requestPost(self::$extractUrl, $params, 'WuYouPay');
|
||||
}
|
||||
|
||||
public static function extractOrder($isAliPay, $outOrderNo, $userId, $amount, $isUser, $account, $userName, $bankName, $province, $city, $bankBranch)
|
||||
{
|
||||
|
||||
$params = self::getBaseParams();
|
||||
|
||||
$params['out_trade_no'] = sprintf('%s-%s:%s', $outOrderNo, $userId, $isUser ? 'us' : 'dl');
|
||||
$params['total'] = $amount;
|
||||
$params['bank_card'] = $account;
|
||||
$params['bank_account_name'] = $userName;
|
||||
|
||||
if ($isAliPay) {
|
||||
$params['bank_name'] = '1';
|
||||
$params['bank_branch'] = '1';
|
||||
$params['province'] = '1';
|
||||
$params['city'] = '1';
|
||||
} else {
|
||||
$params['bank_name'] = $bankName;
|
||||
$params['bank_branch'] = !empty($bankBranch) ? $bankBranch : '1';
|
||||
$params['province'] = !empty($province) ? $province : '1';
|
||||
$params['city'] = !empty($city) ? $city : '1';
|
||||
}
|
||||
|
||||
$params['notify_url'] = self::$extractNotifyUrl;
|
||||
|
||||
$params['sign'] = self::getParamsSign($params);
|
||||
|
||||
$params['business_type'] = 0;
|
||||
if ($isAliPay) {
|
||||
$params['business_attr'] = 'alipay';
|
||||
} else {
|
||||
$params['business_attr'] = 'unionpay';
|
||||
}
|
||||
|
||||
// 发送请求(POST)
|
||||
return self::requestPost(self::$extractUrl, $params, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36');
|
||||
|
||||
}
|
||||
|
||||
public static function queryOrder($trade_no, $user_id, string $amount, string $string)
|
||||
{
|
||||
$params = self::getBaseParams();
|
||||
$params['out_trade_no'] = "{$trade_no}-{$user_id}";
|
||||
$params['total'] = $amount;
|
||||
$params['sign'] = self::getParamsSign($params);
|
||||
|
||||
return self::requestPost(self::$queryUrl, $params, 'WuYouPay');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user