feat: 集成隐私小号功能保护用户手机号

新增隐私小号服务类,在订单创建时自动绑定隐私手机号,收货时自动解绑
在快递发货和收货流程中使用隐私小号替代真实手机号,保护用户隐私
添加数据库字段存储绑定关系,提供系统配置开关和测试脚本
This commit is contained in:
2026-04-01 00:43:51 +08:00
parent 03a6ba7961
commit 42d98407de
14 changed files with 1722 additions and 6 deletions

View File

@@ -0,0 +1,228 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace Tests\Other\PrivacyPhone;
use PHPUnit\Framework\TestCase;
/**
* 米糠云隐私小号接口测试
*/
class MikangyunApiTest extends TestCase
{
private string $appkey = '0f2494a0e258c85575d1e2aa68e07687';
private string $appsecret = ''; // TODO: 请填写完整的AppSecret
private string $serverRoot = 'http://api.mixcom.cn';
/**
* 生成签名
* @param array $params
* @return string
*/
private function generateSign(array $params): string
{
$params = array_filter($params, function ($v, $k) {
return $v !== '' && $k !== 'sign';
}, ARRAY_FILTER_USE_BOTH);
ksort($params);
$string = '';
foreach ($params as $v) {
$string .= $v;
}
return strtolower(sha1($string . sha1($this->appsecret)));
}
/**
* 发送POST请求
* @param string $url
* @param array $params
* @return array
*/
private function post(string $url, array $params): array
{
$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, http_build_query($params));
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'http_code' => $httpCode,
'response' => $response,
'error' => $error,
'decoded' => json_decode($response, true) ?? []
];
}
/**
* 测试获取AXB可用小号
*/
public function testGetAxbNumber()
{
echo "\n========== 测试获取AXB可用小号 ==========\n";
if (empty($this->appsecret)) {
$this->markTestSkipped('请先填写完整的AppSecret');
}
$time = time();
$params = [
'appkey' => $this->appkey,
'time' => $time,
];
$params['sign'] = $this->generateSign($params);
$url = $this->serverRoot . '/virtual/getAxbNumber';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = $this->post($url, $params);
echo "HTTP状态码: {$result['http_code']}\n";
echo "响应内容: {$result['response']}\n";
if ($result['error']) {
echo "curl错误: {$result['error']}\n";
}
$this->assertEquals(200, $result['http_code'], 'HTTP状态码应为200');
$data = $result['decoded'];
$this->assertArrayHasKey('success', $data, '响应应包含success字段');
$this->assertArrayHasKey('code', $data, '响应应包含code字段');
$this->assertArrayHasKey('data', $data, '响应应包含data字段');
if ($data['success'] && !empty($data['data'])) {
echo "\n获取成功!可用小号列表:\n";
foreach ($data['data'] as $item) {
echo " - 号码: {$item['number']}, 区号: {$item['region_code']}, 绑定数: {$item['bindcount']}\n";
}
}
return $data;
}
/**
* 测试AXB绑定
* @depends testGetAxbNumber
*/
public function testAxbBind($axbData)
{
echo "\n========== 测试AXB绑定 ==========\n";
if (empty($this->appsecret)) {
$this->markTestSkipped('请先填写完整的AppSecret');
}
if (empty($axbData['data']) || empty($axbData['data'][0]['number'])) {
$this->markTestSkipped('无可用小号,无法测试绑定');
}
// TODO: 请填写测试用的A号码(用户手机号)和B号码(商家/快递手机号)
$aParty = ''; // TODO: 填写A号码
$bParty = ''; // TODO: 填写B号码
if (empty($aParty) || empty($bParty)) {
$this->markTestSkipped('请先填写A号码和B号码进行测试');
}
$number = $axbData['data'][0]['number'];
$time = time();
$params = [
'appkey' => $this->appkey,
'time' => $time,
'number' => $number,
'aparty' => $aParty,
'bparty' => $bParty,
];
$params['sign'] = $this->generateSign($params);
$url = $this->serverRoot . '/virtual/axbBind';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = $this->post($url, $params);
echo "HTTP状态码: {$result['http_code']}\n";
echo "响应内容: {$result['response']}\n";
$this->assertEquals(200, $result['http_code'], 'HTTP状态码应为200');
$data = $result['decoded'];
$this->assertTrue($data['success'] ?? false, '绑定应成功');
$this->assertEquals(200, $data['code'] ?? 0, '响应码应为200');
echo "\n绑定成功!\n";
echo "隐私小号: $number\n";
echo "A号码: $aParty\n";
echo "B号码: $bParty\n";
return [
'number' => $number,
'aparty' => $aParty,
'bparty' => $bParty,
];
}
/**
* 测试AXB解绑
* @depends testAxbBind
*/
public function testUnAxbBind($bindData)
{
echo "\n========== 测试AXB解绑 ==========\n";
if (empty($this->appsecret)) {
$this->markTestSkipped('请先填写完整的AppSecret');
}
$time = time();
$params = [
'appkey' => $this->appkey,
'time' => $time,
'number' => $bindData['number'],
'aparty' => $bindData['aparty'],
'bparty' => $bindData['bparty'],
];
$params['sign'] = $this->generateSign($params);
$url = $this->serverRoot . '/virtual/unAxbBind';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = $this->post($url, $params);
echo "HTTP状态码: {$result['http_code']}\n";
echo "响应内容: {$result['response']}\n";
$this->assertEquals(200, $result['http_code'], 'HTTP状态码应为200');
$data = $result['decoded'];
$this->assertTrue($data['success'] ?? false, '解绑应成功');
echo "\n解绑成功!\n";
}
}

View File

@@ -0,0 +1,230 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
/**
* 米糠云隐私小号接口测试脚本
*
* 使用方法:
* 1. 填写下方配置信息
* 2. 命令行执行: php tests/Other/PrivacyPhone/test_api.php
*/
// 配置信息
$appkey = '0f2494a0e258c85575d1e2aa68e07687';
$appsecret = 'SZWgOa'; // 完整AppSecret
$aParty = '13693687880'; // A号码 - 用户手机号
$serverRoot = 'https://api.mixcom.cn'; // 注意:SDK中使用的是https
/**
* 生成签名 - 参照SDK实现
*/
function generateSign(array $params, string $secret): string
{
// key转换为小写
$params = array_change_key_case($params, CASE_LOWER);
// 排除不需要加密的参数 (key为c、a或sign)
foreach ($params as $key => $val) {
if ($key == 'c' || $key == 'a' || $key == 'sign') {
unset($params[$key]);
}
if ($val === '') {
unset($params[$key]);
}
}
// 对key进行排序, 按名称升序
ksort($params);
// 将数据连接,进行加密
$data = '';
foreach ($params as $key => $val) {
$data .= $val;
}
$data .= $secret;
$sign = strtolower(sha1($data . sha1($secret)));
echo "\n[签名调试]\n";
echo "key小写转换后: " . json_encode(array_keys($params)) . "\n";
echo "排除后参数: " . json_encode($params) . "\n";
echo "参与签名数据拼接: $data\n";
echo "sha1(secret): " . sha1($secret) . "\n";
echo "最终签名: $sign\n";
return $sign;
}
/**
* 发送POST请求
*/
function post(string $url, array $params): array
{
$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);
return [
'http_code' => $httpCode,
'response' => $response,
'error' => $error,
'decoded' => json_decode($response, true) ?? []
];
}
/**
* 打印结果
*/
function printResult(string $title, array $result): void
{
echo "\n========== $title ==========\n";
echo "HTTP状态码: {$result['http_code']}\n";
echo "响应内容: {$result['response']}\n";
if ($result['error']) {
echo "curl错误: {$result['error']}\n";
}
echo "\n";
}
// ========== 开始测试 ==========
echo "米糠云隐私小号接口测试\n";
echo "========================\n";
echo "时间: " . date('Y-m-d H:i:s') . "\n";
echo "AppKey: $appkey\n";
echo "AppSecret: " . substr($appsecret, 0, 6) . "***\n";
echo "A号码: $aParty\n";
echo "API地址: $serverRoot\n";
// 检查配置
if (empty($appsecret) || empty($aParty)) {
echo "\n❌ 错误: 请填写完整的AppSecret和A号码\n";
exit(1);
}
// 测试1: 获取AXB可用小号
echo "\n[测试1] 获取AXB可用小号\n";
$params = [
'appkey' => $appkey,
'time' => time(),
];
$params['sign'] = generateSign($params, $appsecret);
$url = $serverRoot . '/virtual/getaxbnumber';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = post($url, $params);
printResult('获取AXB可用小号响应', $result);
if ($result['http_code'] !== 200) {
echo "❌ HTTP请求失败\n";
exit(1);
}
$data = $result['decoded'];
if (empty($data['success'])) {
echo "❌ 接口返回失败\n";
echo "错误码: " . ($data['code'] ?? 'N/A') . "\n";
echo "错误信息: " . ($data['msg'] ?? 'N/A') . "\n";
exit(1);
}
if (empty($data['data'])) {
echo "❌ 没有可用的隐私小号\n";
exit(1);
}
echo "✅ 获取成功!可用小号列表:\n";
foreach ($data['data'] as $index => $item) {
echo " [" . ($index + 1) . "] 号码: {$item['number']}, 区号: {$item['region_code']}, 绑定数: {$item['bindcount']}\n";
}
$availableNumber = $data['data'][0]['number'];
$bParty = $availableNumber; // 使用获取到的第一个可用号作为B号码进行测试
// 测试2: AXB绑定
echo "\n[测试2] AXB绑定\n";
echo "隐私小号(X): $availableNumber\n";
echo "A号码(用户): $aParty\n";
echo "B号码(商家): $bParty\n";
$params = [
'appkey' => $appkey,
'time' => time(),
'number' => $availableNumber,
'aparty' => $aParty,
'bparty' => $bParty,
];
$params['sign'] = generateSign($params, $appsecret);
$url = $serverRoot . '/virtual/axbBind';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = post($url, $params);
printResult('AXB绑定响应', $result);
$data = $result['decoded'];
if (!empty($data['success']) && $data['code'] == 200) {
echo "✅ 绑定成功!\n";
// 测试3: AXB解绑
echo "\n[测试3] AXB解绑\n";
$params = [
'appkey' => $appkey,
'time' => time(),
'number' => $availableNumber,
'aparty' => $aParty,
'bparty' => $bParty,
];
$params['sign'] = generateSign($params, $appsecret);
$url = $serverRoot . '/virtual/unAxbBind';
echo "请求URL: $url\n";
echo "请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
$result = post($url, $params);
printResult('AXB解绑响应', $result);
$data = $result['decoded'];
if (!empty($data['success']) && $data['code'] == 200) {
echo "✅ 解绑成功!\n";
} else {
echo "❌ 解绑失败\n";
echo "错误信息: " . ($data['msg'] ?? 'N/A') . "\n";
}
} else {
echo "❌ 绑定失败\n";
echo "错误码: " . ($data['code'] ?? 'N/A') . "\n";
echo "错误信息: " . ($data['msg'] ?? 'N/A') . "\n";
}
echo "\n========== 测试完成 ==========\n";