初始化本地仓库
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\service\api\system;
|
||||
|
||||
use app\model\system\Config;
|
||||
use core\base\BaseService;
|
||||
|
||||
/**
|
||||
* 协议服务
|
||||
*/
|
||||
class AgreementService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 获取协议内容
|
||||
*/
|
||||
public function getAgreementContent(string $key): array
|
||||
{
|
||||
$agreements = [
|
||||
'privacy' => [
|
||||
'title' => '隐私政策',
|
||||
'content' => $this->getPrivacyPolicyContent()
|
||||
],
|
||||
'user' => [
|
||||
'title' => '用户协议',
|
||||
'content' => $this->getUserAgreementContent()
|
||||
],
|
||||
'service' => [
|
||||
'title' => '服务条款',
|
||||
'content' => $this->getServiceTermsContent()
|
||||
]
|
||||
];
|
||||
|
||||
if (!isset($agreements[$key])) {
|
||||
throw new \Exception('协议不存在', 404);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $agreements[$key]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐私政策内容
|
||||
*/
|
||||
private function getPrivacyPolicyContent(): string
|
||||
{
|
||||
return "# 隐私政策
|
||||
|
||||
## 1. 信息收集
|
||||
我们收集您在使用服务时主动提供的信息,包括但不限于姓名、联系方式等。
|
||||
|
||||
## 2. 信息使用
|
||||
我们使用收集的信息来提供和改进服务,保护用户安全,并遵守法律法规。
|
||||
|
||||
## 3. 信息共享
|
||||
除非获得您的明确同意,否则我们不会与第三方分享您的个人信息。
|
||||
|
||||
## 4. 信息安全
|
||||
我们采取合理的安全措施保护您的个人信息安全。
|
||||
|
||||
## 5. 政策变更
|
||||
我们可能会适时更新本隐私政策,请定期查看最新版本。";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户协议内容
|
||||
*/
|
||||
private function getUserAgreementContent(): string
|
||||
{
|
||||
return "# 用户协议
|
||||
|
||||
## 1. 接受条款
|
||||
通过使用我们的服务,您同意遵守本协议的所有条款和条件。
|
||||
|
||||
## 2. 用户注册
|
||||
您需要提供真实、准确、完整的注册信息,并保持信息的及时更新。
|
||||
|
||||
## 3. 使用规则
|
||||
您承诺遵守法律法规,不得利用服务从事任何违法或不正当的活动。
|
||||
|
||||
## 4. 服务变更
|
||||
我们保留随时修改、暂停或终止服务的权利,恕不另行通知。
|
||||
|
||||
## 5. 责任限制
|
||||
在法律允许的范围内,我们对服务的适用性、可靠性不作任何保证。";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务条款内容
|
||||
*/
|
||||
private function getServiceTermsContent(): string
|
||||
{
|
||||
return "# 服务条款
|
||||
|
||||
## 1. 服务内容
|
||||
我们提供在线服务,包括但不限于信息展示、用户交互等功能。
|
||||
|
||||
## 2. 服务费用
|
||||
基础服务免费提供,部分增值服务可能需要支付相应费用。
|
||||
|
||||
## 3. 服务中断
|
||||
因系统维护、升级等原因,服务可能会暂时中断,我们将尽力减少影响。
|
||||
|
||||
## 4. 用户责任
|
||||
用户应妥善保管账户信息,对其账户下的所有活动承担全部责任。
|
||||
|
||||
## 5. 终止服务
|
||||
如用户违反协议条款,我们有权终止提供服务。";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\service\api\system;
|
||||
|
||||
use app\model\system\Config;
|
||||
use core\base\BaseService;
|
||||
use support\Redis;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* 验证码服务
|
||||
*/
|
||||
class CaptchaService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 获取验证码图片
|
||||
*/
|
||||
public function getCaptchaImage(): array
|
||||
{
|
||||
// 生成验证码
|
||||
$captcha = $this->generateCaptcha();
|
||||
|
||||
// 存储验证码到Redis,5分钟过期
|
||||
Redis::setex('captcha:' . $captcha['key'], 300, $captcha['code']);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'captcha_key' => $captcha['key'],
|
||||
'captcha_image' => $captcha['image'],
|
||||
'expire_time' => 300
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*/
|
||||
public function sendSmsCode(string $type): array
|
||||
{
|
||||
$request = \request();
|
||||
$mobile = $request->post('mobile');
|
||||
|
||||
if (empty($mobile)) {
|
||||
throw new \Exception('手机号不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
if (!preg_match('/^1[3-9]\\d{9}$/', $mobile)) {
|
||||
throw new \Exception('手机号格式不正确', 400);
|
||||
}
|
||||
|
||||
// 检查发送频率
|
||||
$lastSendTime = Redis::get('sms_last_send:' . $mobile);
|
||||
if ($lastSendTime && time() - $lastSendTime < 60) {
|
||||
throw new \Exception('发送过于频繁,请稍后再试', 429);
|
||||
}
|
||||
|
||||
// 生成验证码
|
||||
$code = sprintf('%06d', mt_rand(0, 999999));
|
||||
|
||||
// 存储验证码到Redis,10分钟过期
|
||||
$cacheKey = 'sms_code:' . $mobile;
|
||||
Redis::setex($cacheKey, 600, $code);
|
||||
|
||||
// 记录发送时间
|
||||
Redis::setex('sms_last_send:' . $mobile, 60, time());
|
||||
|
||||
// 发送短信(这里应该调用短信服务商API)
|
||||
$this->sendSms($mobile, $code, $type);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '验证码发送成功'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
private function generateCaptcha(): array
|
||||
{
|
||||
// 生成随机验证码
|
||||
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
$code = '';
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$code .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
|
||||
// 生成唯一key
|
||||
$key = md5(uniqid() . microtime(true));
|
||||
|
||||
// 这里应该生成验证码图片,暂时返回模拟数据
|
||||
$image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'code' => $code,
|
||||
'image' => $image
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*/
|
||||
private function sendSms(string $mobile, string $code, string $type): void
|
||||
{
|
||||
// 这里应该调用短信服务商API
|
||||
// 暂时模拟发送成功
|
||||
$templates = [
|
||||
'login' => '您的登录验证码是:{code},有效期10分钟',
|
||||
'register' => '您的注册验证码是:{code},有效期10分钟',
|
||||
'bind' => '您的绑定验证码是:{code},有效期10分钟',
|
||||
'reset' => '您的密码重置验证码是:{code},有效期10分钟'
|
||||
];
|
||||
|
||||
$template = $templates[$type] ?? '您的验证码是:{code},有效期10分钟';
|
||||
$content = str_replace('{code}', $code, $template);
|
||||
|
||||
// 记录发送日志
|
||||
\Log::info("短信发送: {$mobile} - {$content}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\service\api\system;
|
||||
|
||||
use app\dao\system\ConfigDao;
|
||||
use app\scope\global\AccessPermissionScope;
|
||||
use core\base\BaseService;
|
||||
|
||||
|
||||
class ConfigService extends BaseService
|
||||
{
|
||||
|
||||
public function __construct(ConfigDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置项
|
||||
*
|
||||
* @param string $code 配置code
|
||||
* @param mixed $default 默认值
|
||||
* @param array $options 选项,如 ['group_code' => '...']
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function config(string $code, mixed $default = [], array $options = []): mixed
|
||||
{
|
||||
$map = ['code' => $code];
|
||||
// 如果选项中有分组,则添加到查询条件
|
||||
if (!empty($options['group_code'])) {
|
||||
$map['group_code'] = $options['group_code'];
|
||||
}
|
||||
|
||||
// 查询配置
|
||||
$configModel = $this->dao->get($map, ['*'], [], '', [AccessPermissionScope::class]);
|
||||
|
||||
// 如果配置不存在,自动创建
|
||||
if (!$configModel) {
|
||||
$configData = [
|
||||
'code' => $code,
|
||||
'name' => $options['name'] ?? $code,
|
||||
'group_code' => $options['group_code'] ?? 'default',
|
||||
'content' => is_array($default) ? json_encode($default, JSON_UNESCAPED_UNICODE) : $default,
|
||||
'type' => $options['type'] ?? (is_array($default) ? 'json' : 'string'),
|
||||
'enabled' => 1,
|
||||
'sort' => 0,
|
||||
'remark' => $options['remark'] ?? '',
|
||||
];
|
||||
|
||||
try {
|
||||
$this->dao->save($configData);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
$content = $configModel->getOriginal('content',null);
|
||||
|
||||
// 如果content是数组/JSON格式,返回整个数组;否则返回原值
|
||||
if (is_string($content) && !empty($content)) {
|
||||
$decoded = json_decode($content, true);
|
||||
return json_last_error() === JSON_ERROR_NONE ? $decoded : $content;
|
||||
}
|
||||
|
||||
return $content ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置分组详情
|
||||
*
|
||||
* @param string $groupCode 配置分组
|
||||
* @param array $defaults 默认配置项
|
||||
* @param array $options 选项参数
|
||||
* - enabled_only: bool 是否只获取启用的配置项,默认true
|
||||
* - with_metadata: bool 是否包含配置元数据,默认false
|
||||
*
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getByGroup(string $groupCode, array $defaults = [], array $options = []): array
|
||||
{
|
||||
// 验证groupCode参数,防止SQL注入
|
||||
if (empty($groupCode)) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
$enabledOnly = $options['enabled_only'] ?? true;
|
||||
$withMetadata = $options['with_metadata'] ?? false;
|
||||
|
||||
$map = ['group_code' => $groupCode];
|
||||
if ($enabledOnly) {
|
||||
$map['enabled'] = 1;
|
||||
}
|
||||
|
||||
if ($withMetadata) {
|
||||
// 获取完整配置信息
|
||||
$configs = $this->dao->getList($map);
|
||||
$processedData = [];
|
||||
foreach ($configs as $config) {
|
||||
$content = $config['content'] ?? null;
|
||||
if (is_string($content) && !empty($content)) {
|
||||
$decoded = json_decode($content, true);
|
||||
$config['content'] = json_last_error() === JSON_ERROR_NONE ? $decoded : $content;
|
||||
}
|
||||
|
||||
if (isset($options['key_by']) && $options['key_by'] === 'code') {
|
||||
$processedData[$config['code']] = $config;
|
||||
} else {
|
||||
$processedData[] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
return $processedData;
|
||||
} else {
|
||||
// 只获取配置内容
|
||||
$data = $this->dao->getColumn($map, 'content', 'code');
|
||||
// 处理每个配置项的内容,如果是JSON格式则解码
|
||||
$processedData = [];
|
||||
foreach ($data as $code => $content) {
|
||||
if (is_string($content) && !empty($content)) {
|
||||
$decoded = json_decode($content, true);
|
||||
$processedData[$code] = json_last_error() === JSON_ERROR_NONE ? $decoded : $content;
|
||||
} else {
|
||||
$processedData[$code] = $content;
|
||||
}
|
||||
}
|
||||
|
||||
// 合并默认配置
|
||||
return array_merge($defaults, $processedData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置并按分组整理
|
||||
*
|
||||
* @param array $options 选项参数
|
||||
* - enabled_only: bool 是否只获取启用的配置项,默认true
|
||||
* - group_filter: array 分组过滤,只返回指定分组的配置
|
||||
* - with_metadata: bool 是否包含配置项的完整元数据,默认false
|
||||
* - key_by: string 按指定字段作为键名('code' 或 'id'),默认null
|
||||
* - sort_groups: bool 是否对分组进行排序,默认false
|
||||
* - sort_configs: bool 是否对配置项进行排序,默认false
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAllGrouped(array $options = []): array
|
||||
{
|
||||
$enabledOnly = $options['enabled_only'] ?? true;
|
||||
$groupFilter = $options['group_filter'] ?? [];
|
||||
$withMetadata = $options['with_metadata'] ?? false;
|
||||
$keyBy = $options['key_by'] ?? null;
|
||||
$sortGroups = $options['sort_groups'] ?? false;
|
||||
$sortConfigs = $options['sort_configs'] ?? false;
|
||||
|
||||
$map = [];
|
||||
if ($enabledOnly) {
|
||||
$map['enabled'] = 1;
|
||||
}
|
||||
if (!empty($groupFilter)) {
|
||||
$map['group_code'] = $groupFilter;
|
||||
}
|
||||
|
||||
$configs = $this->dao->getList($map);
|
||||
$grouped = [];
|
||||
|
||||
foreach ($configs as $config) {
|
||||
$groupCode = $config['group_code'] ?? 'default';
|
||||
|
||||
// 处理内容
|
||||
$content = $config['content'];
|
||||
if (is_string($content) && !empty($content)) {
|
||||
$decoded = json_decode($content, true);
|
||||
$config['content'] = json_last_error() === JSON_ERROR_NONE ? $decoded : $content;
|
||||
}
|
||||
|
||||
// 初始化分组
|
||||
if (!isset($grouped[$groupCode])) {
|
||||
$grouped[$groupCode] = [];
|
||||
}
|
||||
|
||||
// 根据选项决定返回的数据结构
|
||||
if ($withMetadata) {
|
||||
// 返回完整配置信息
|
||||
if ($keyBy && isset($config[$keyBy])) {
|
||||
$grouped[$groupCode][$config[$keyBy]] = $config;
|
||||
} else {
|
||||
$grouped[$groupCode][] = $config;
|
||||
}
|
||||
} else {
|
||||
// 只返回配置内容
|
||||
if ($keyBy && isset($config[$keyBy])) {
|
||||
$grouped[$groupCode][$config[$keyBy]] = $config['content'];
|
||||
} else {
|
||||
$grouped[$groupCode][$config['code']] = $config['content'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 排序处理
|
||||
if ($sortGroups) {
|
||||
ksort($grouped);
|
||||
}
|
||||
|
||||
if ($sortConfigs) {
|
||||
foreach ($grouped as &$configs) {
|
||||
if ($withMetadata) {
|
||||
// 对元数据模式按code排序
|
||||
uasort($configs, function ($a, $b) {
|
||||
return strcmp($a['code'] ?? '', $b['code'] ?? '');
|
||||
});
|
||||
} else {
|
||||
// 对内容模式按键名排序
|
||||
ksort($configs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置项中的某个键值
|
||||
*
|
||||
* @param string $code 配置项
|
||||
* @param string $key 要获取的键,支持多级键,如 key1.key2.key3
|
||||
* @param mixed $default 默认值,默认null
|
||||
* @param array $options 选项参数
|
||||
* - group_code: string 配置分组,默认null(在所有分组中查找)
|
||||
* - require_group: bool 是否必须指定分组,默认false
|
||||
* - fallback_to_all: bool 当未找到分组配置时,是否在所有分组中查找,默认true
|
||||
* - search_groups: array 搜索的分组列表,按顺序查找
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getValue(string $code, string $key, mixed $default = null, array $options = []): mixed
|
||||
{
|
||||
$groupCode = $options['group_code'] ?? null;
|
||||
$requireGroup = $options['require_group'] ?? false;
|
||||
$fallbackToAll = $options['fallback_to_all'] ?? true;
|
||||
$searchGroups = $options['search_groups'] ?? [];
|
||||
|
||||
// 如果指定了搜索分组列表,按顺序查找
|
||||
if (!empty($searchGroups) && is_array($searchGroups)) {
|
||||
foreach ($searchGroups as $searchGroupCode) {
|
||||
$config = $this->config($code, null, ['group_code' => $searchGroupCode]);
|
||||
if (is_array($config)) {
|
||||
$value = $this->getNestedValue($config, $key);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
// 如果指定了分组,优先在该分组中查找
|
||||
if ($groupCode !== null) {
|
||||
$config = $this->config($code, null, ['group_code' => $groupCode]);
|
||||
|
||||
if (is_array($config)) {
|
||||
$value = $this->getNestedValue($config, $key);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未找到且允许回退到所有分组,则尝试在所有分组中查找
|
||||
if ($fallbackToAll && !$requireGroup) {
|
||||
$config = $this->config($code, null);
|
||||
if (is_array($config)) {
|
||||
$value = $this->getNestedValue($config, $key);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
// 默认在所有分组中查找
|
||||
$config = $this->config($code, null);
|
||||
if (is_array($config)) {
|
||||
$value = $this->getNestedValue($config, $key);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据多级键获取嵌套数组中的值
|
||||
*
|
||||
* @param array $array 要查找的数组
|
||||
* @param string $key 多级键,如 key1.key2.key3
|
||||
*
|
||||
* @return mixed|null 找到的值或null
|
||||
*/
|
||||
private function getNestedValue(array $array, string $key): mixed
|
||||
{
|
||||
// 如果key不包含点号,直接返回对应值
|
||||
if (!str_contains($key, '.')) {
|
||||
return $array[$key] ?? null;
|
||||
}
|
||||
|
||||
// 拆分多级键
|
||||
$keys = explode('.', $key);
|
||||
$value = $array;
|
||||
|
||||
// 逐级查找
|
||||
foreach ($keys as $k) {
|
||||
if (!is_array($value) || !isset($value[$k])) {
|
||||
return null;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置项是否存在
|
||||
*
|
||||
* @param string $code 配置code
|
||||
* @param array $options 扩展选项参数
|
||||
* - group_code: string 配置分组
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function exists(string $code, array $options = []): bool
|
||||
{
|
||||
$map = ['code' => $code];
|
||||
if (isset($options['group_code'])) {
|
||||
// 验证group_code格式
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $options['group_code'])) {
|
||||
throw new \Exception('Invalid group_code format');
|
||||
}
|
||||
$map['group_code'] = $options['group_code'];
|
||||
}
|
||||
|
||||
$config = $this->dao->get($map);
|
||||
return !empty($config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\service\api\system;
|
||||
|
||||
use app\model\system\Config;
|
||||
use core\base\BaseService;
|
||||
use support\Redis;
|
||||
|
||||
/**
|
||||
* 微信服务
|
||||
*/
|
||||
class WechatService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 获取微信授权码
|
||||
*/
|
||||
public function getWechatAuthCode(array $params): array
|
||||
{
|
||||
$redirectUrl = $params['redirect_url'] ?? '';
|
||||
|
||||
if (empty($redirectUrl)) {
|
||||
throw new \Exception('回调URL不能为空', 400);
|
||||
}
|
||||
|
||||
$config = config('wechat');
|
||||
$appId = $config['app_id'] ?? '';
|
||||
|
||||
if (empty($appId)) {
|
||||
throw new \Exception('微信配置未完成', 500);
|
||||
}
|
||||
|
||||
// 生成授权URL
|
||||
$scope = 'snsapi_userinfo'; // 需要获取用户信息
|
||||
$state = md5(uniqid() . microtime(true));
|
||||
|
||||
$authUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?" . http_build_query([
|
||||
'appid' => $appId,
|
||||
'redirect_uri' => urlencode($redirectUrl),
|
||||
'response_type' => 'code',
|
||||
'scope' => $scope,
|
||||
'state' => $state
|
||||
]) . "#wechat_redirect";
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'auth_url' => $authUrl,
|
||||
'state' => $state
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步微信用户信息
|
||||
*/
|
||||
public function syncWechatUserInfo(array $params): array
|
||||
{
|
||||
$code = $params['code'] ?? '';
|
||||
|
||||
if (empty($code)) {
|
||||
throw new \Exception('授权码不能为空', 400);
|
||||
}
|
||||
|
||||
$config = config('wechat');
|
||||
|
||||
if (empty($config['app_id']) || empty($config['app_secret'])) {
|
||||
throw new \Exception('微信配置未完成', 500);
|
||||
}
|
||||
|
||||
// 获取access_token
|
||||
$accessToken = $this->getAccessTokenByCode($code, $config['app_id'], $config['app_secret']);
|
||||
|
||||
// 获取用户信息
|
||||
$userInfo = $this->getUserInfoByToken($accessToken['access_token'], $accessToken['openid']);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '同步成功',
|
||||
'data' => $userInfo
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信 JSSDK 配置
|
||||
*/
|
||||
public function getWechatJssdkConfig(array $params): array
|
||||
{
|
||||
$url = $params['url'] ?? '';
|
||||
|
||||
if (empty($url)) {
|
||||
throw new \Exception('URL不能为空', 400);
|
||||
}
|
||||
|
||||
$config = config('wechat');
|
||||
$appId = $config['app_id'] ?? '';
|
||||
|
||||
if (empty($appId)) {
|
||||
throw new \Exception('微信配置未完成', 500);
|
||||
}
|
||||
|
||||
// 获取jsapi_ticket
|
||||
$ticket = $this->getJsapiTicket();
|
||||
|
||||
// 生成签名
|
||||
$nonceStr = $this->generateNonceStr();
|
||||
$timestamp = time();
|
||||
$signature = $this->generateSignature($ticket, $nonceStr, $timestamp, $url);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'appId' => $appId,
|
||||
'timestamp' => $timestamp,
|
||||
'nonceStr' => $nonceStr,
|
||||
'signature' => $signature,
|
||||
'url' => $url
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过code获取access_token
|
||||
*/
|
||||
private function getAccessTokenByCode(string $code, string $appId, string $appSecret): array
|
||||
{
|
||||
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?" . http_build_query([
|
||||
'appid' => $appId,
|
||||
'secret' => $appSecret,
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code'
|
||||
]);
|
||||
|
||||
// 这里应该调用微信API,暂时返回模拟数据
|
||||
return [
|
||||
'access_token' => 'mock_access_token',
|
||||
'expires_in' => 7200,
|
||||
'refresh_token' => 'mock_refresh_token',
|
||||
'openid' => 'mock_openid_' . uniqid(),
|
||||
'scope' => 'snsapi_userinfo'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过token获取用户信息
|
||||
*/
|
||||
private function getUserInfoByToken(string $accessToken, string $openid): array
|
||||
{
|
||||
$url = "https://api.weixin.qq.com/sns/userinfo?" . http_build_query([
|
||||
'access_token' => $accessToken,
|
||||
'openid' => $openid,
|
||||
'lang' => 'zh_CN'
|
||||
]);
|
||||
|
||||
// 这里应该调用微信API,暂时返回模拟数据
|
||||
return [
|
||||
'openid' => $openid,
|
||||
'nickname' => '微信用户',
|
||||
'sex' => 1,
|
||||
'province' => '广东省',
|
||||
'city' => '深圳市',
|
||||
'country' => '中国',
|
||||
'headimgurl' => '',
|
||||
'privilege' => [],
|
||||
'unionid' => ''
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取jsapi_ticket
|
||||
*/
|
||||
private function getJsapiTicket(): string
|
||||
{
|
||||
// 检查缓存
|
||||
$cacheKey = 'wechat_jsapi_ticket';
|
||||
$ticket = Redis::get($cacheKey);
|
||||
|
||||
if ($ticket) {
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
// 获取access_token
|
||||
$accessToken = $this->getAccessToken();
|
||||
|
||||
// 获取ticket
|
||||
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
|
||||
|
||||
// 这里应该调用微信API,暂时返回模拟数据
|
||||
$mockTicket = 'mock_jsapi_ticket_' . uniqid();
|
||||
|
||||
// 缓存2小时
|
||||
Redis::setex($cacheKey, 7200, $mockTicket);
|
||||
|
||||
return $mockTicket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token
|
||||
*/
|
||||
private function getAccessToken(): string
|
||||
{
|
||||
// 检查缓存
|
||||
$cacheKey = 'wechat_access_token';
|
||||
$accessToken = Redis::get($cacheKey);
|
||||
|
||||
if ($accessToken) {
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
$config = config('wechat');
|
||||
$url = "https://api.weixin.qq.com/cgi-bin/token?" . http_build_query([
|
||||
'grant_type' => 'client_credential',
|
||||
'appid' => $config['app_id'],
|
||||
'secret' => $config['app_secret']
|
||||
]);
|
||||
|
||||
// 这里应该调用微信API,暂时返回模拟数据
|
||||
$mockToken = 'mock_access_token_' . uniqid();
|
||||
|
||||
// 缓存2小时
|
||||
Redis::setex($cacheKey, 7200, $mockToken);
|
||||
|
||||
return $mockToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
*/
|
||||
private function generateNonceStr(int $length = 16): string
|
||||
{
|
||||
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
$str = "";
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*/
|
||||
private function generateSignature(string $ticket, string $nonceStr, int $timestamp, string $url): string
|
||||
{
|
||||
$string = "jsapi_ticket={$ticket}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}";
|
||||
return sha1($string);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user