Files
douba_shop/app/services/other/PrivacyPhoneServices.php
Karlcarlo 359e9eef48 refactor(privacy-phone): 将隐私手机配置从数据库迁移至环境变量
修改 PrivacyPhoneServices 从读取数据库配置改为读取 .env 环境变量,提升配置灵活性与安全性。
更新 SQL 迁移文件,移除向系统配置表插入配置的语句,并添加配置方式说明。
2026-04-01 01:26:30 +08:00

330 lines
9.0 KiB
PHP
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.
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\services\other;
use app\services\BaseServices;
use think\facade\Log;
/**
* 隐私小号服务类 (AX模式)
* Class PrivacyPhoneServices
* @package app\services\other
*/
class PrivacyPhoneServices extends BaseServices
{
/**
* API地址
*/
private string $apiUrl = 'https://api.mixcom.cn';
/**
* 获取AppKey
* @return string
*/
public function getAppKey(): string
{
return env('PRIVACY_PHONE.APPKEY', '');
}
/**
* 获取AppSecret
* @return string
*/
public function getAppSecret(): string
{
return env('PRIVACY_PHONE.APPSECRET', '');
}
/**
* 检查功能是否开启
* @return bool
*/
public function isEnabled(): bool
{
return (bool)env('PRIVACY_PHONE.ENABLE', false);
}
/**
* 生成签名
* @param array $params
* @return string
*/
private function generateSign(array $params): string
{
$secret = $this->getAppSecret();
if (empty($secret)) {
throw new \Exception('隐私小号AppSecret未配置');
}
$params = array_change_key_case($params, CASE_LOWER);
foreach ($params as $key => $val) {
if ($key == 'c' || $key == 'a' || $key == 'sign') {
unset($params[$key]);
}
if ($val === '') {
unset($params[$key]);
}
}
ksort($params);
$data = '';
foreach ($params as $key => $val) {
$data .= $val;
}
$data .= $secret;
return strtolower(sha1($data . sha1($secret)));
}
/**
* 发送POST请求
* @param string $url
* @param array $params
* @return array
*/
private function post(string $url, array $params): array
{
$params['time'] = time();
$params['appkey'] = $this->getAppKey();
$params['sign'] = $this->generateSign($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
$result = [
'http_code' => $httpCode,
'response' => $response,
'error' => $error,
'decoded' => json_decode($response, true) ?? []
];
Log::info('隐私小号API请求', [
'url' => $url,
'params' => $params,
'result' => $result
]);
return $result;
}
/**
* 获取可用AX小号
* @param string $regionCode 区号(可选)
* @return array
*/
public function getAxNumber(string $regionCode = ''): array
{
if (!$this->isEnabled()) {
return [];
}
$params = [];
if ($regionCode) {
$params['region_code'] = $regionCode;
}
$url = $this->apiUrl . '/virtual/getaxnumber';
$result = $this->post($url, $params);
if ($result['http_code'] !== 200) {
throw new \Exception('获取隐私小号失败:HTTP ' . $result['http_code']);
}
$data = $result['decoded'];
if (empty($data['success']) || $data['code'] != 200 || empty($data['data'])) {
throw new \Exception('获取隐私小号失败:' . ($data['msg'] ?? '未知错误'));
}
return $data['data'];
}
/**
* 获取第一个可用小号
* @return string|null
*/
public function getFirstAxNumber(): ?string
{
$numbers = $this->getAxNumber();
return $numbers[0]['number'] ?? null;
}
/**
* AX绑定
* @param string $number 隐私小号
* @param string $aParty A号码(用户手机号)
* @param array $options 可选参数
* @return array
*/
public function axBind(string $number, string $aParty, array $options = []): array
{
if (!$this->isEnabled()) {
throw new \Exception('隐私小号功能未开启');
}
$params = array_merge([
'number' => $number,
'aparty' => $aParty,
'recording' => 0,
'recordhinttone' => '',
'display_call' => 0,
'callerhinttone' => '',
'calleehinttone' => '',
'enddate' => 0,
], $options);
$url = $this->apiUrl . '/virtual/axBind';
$result = $this->post($url, $params);
if ($result['http_code'] !== 200) {
throw new \Exception('AX绑定失败:HTTP ' . $result['http_code']);
}
$data = $result['decoded'];
if (empty($data['success']) || $data['code'] != 200) {
throw new \Exception('AX绑定失败:' . ($data['msg'] ?? '未知错误'));
}
return $data;
}
/**
* AX解绑
* @param string $number 隐私小号
* @param string $aParty A号码(用户手机号)
* @return array
*/
public function unAxBind(string $number, string $aParty): array
{
if (!$this->isEnabled()) {
throw new \Exception('隐私小号功能未开启');
}
$params = [
'number' => $number,
'aparty' => $aParty,
];
$url = $this->apiUrl . '/virtual/unAxBind';
$result = $this->post($url, $params);
if ($result['http_code'] !== 200) {
throw new \Exception('AX解绑失败:HTTP ' . $result['http_code']);
}
$data = $result['decoded'];
if (empty($data['success']) || $data['code'] != 200) {
throw new \Exception('AX解绑失败:' . ($data['msg'] ?? '未知错误'));
}
return $data;
}
/**
* 订单创建时绑定隐私手机号
* @param string $realPhone 用户真实手机号
* @return array ['user_phone' => 隐私号, 'bind_id' => 真实手机号, 'status' => 0]
*/
public function bindForOrder(string $realPhone): array
{
if (!$this->isEnabled()) {
return [];
}
$result = [
'user_phone' => '',
'bind_id' => '',
'status' => 0
];
try {
$number = $this->getFirstAxNumber();
if (!$number) {
Log::error('获取隐私小号失败:无可用号码');
return $result;
}
$bindResult = $this->axBind($number, $realPhone);
if (!empty($bindResult['success']) && $bindResult['code'] == 200) {
$result = [
'user_phone' => $number,
'bind_id' => $realPhone,
'status' => 1
];
Log::info('隐私小号绑定成功', [
'privacy_phone' => $number,
'real_phone' => $realPhone
]);
}
} catch (\Exception $e) {
Log::error('隐私小号绑定异常', [
'real_phone' => $realPhone,
'error' => $e->getMessage()
]);
}
return $result;
}
/**
* 订单完成时解绑隐私手机号
* @param string $privacyPhone 隐私手机号(user_phone)
* @param string $realPhone 真实手机号(private_bind_id)
* @return bool
*/
public function unbindForOrder(string $privacyPhone, string $realPhone): bool
{
if (!$this->isEnabled()) {
return false;
}
if (empty($privacyPhone)) {
return false;
}
try {
$result = $this->unAxBind($privacyPhone, $realPhone);
if (!empty($result['success']) && $result['code'] == 200) {
Log::info('隐私小号解绑成功', [
'privacy_phone' => $privacyPhone,
'real_phone' => $realPhone
]);
return true;
}
} catch (\Exception $e) {
Log::error('隐私小号解绑异常', [
'privacy_phone' => $privacyPhone,
'real_phone' => $realPhone,
'error' => $e->getMessage()
]);
}
return false;
}
}