FastMovieAI AI短剧创作平台 二开基线

- 源码: xhadmincn/FastMovieAI (Apache 2.0)
- 二开: Docker部署, Swoole改Select, route.php修复, 补update/VERSION
- vendor/示例资源已gitignore
This commit is contained in:
李建琦
2026-08-26 18:37:36 +08:00
commit eae151fd57
888 changed files with 105571 additions and 0 deletions
@@ -0,0 +1,109 @@
<?php
namespace app\expose\helper;
use Exception;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
/**
* 图像验证码助手类
* Class Captcha
* @package app\helper
*
* @method static Response captcha()
* @method static array captchaCode()
* @method static bool check(string $captcha)
*
*/
class Captcha
{
/**
* 检验图像验证码
* @param string $captcha
* @return boolean
*/
public static function check(string $captcha, ?string $token = null): bool
{
$request = request();
if ($token) {
$request->sessionId($token);
}
$captchaData = $request->session()->get('captcha');
if (!$captchaData) {
throw new Exception('图像验证码不存在');
}
if ($captchaData['expire'] < time()) {
throw new Exception('图像验证码已过期');
}
// 对比session中的captcha值
if (strtolower($captcha) !== $captchaData['captcha']) {
throw new Exception('图像验证码不正确');
}
return true;
}
public static function create()
{
$request = request();
$bg = $request->get('bg');
$length = $request->get('length', 4);
if ($length < 4) {
$length = 4;
}
if ($length > 6) {
$length = 6;
}
$defaultBg = '255,255,255';
if ($bg) {
$defaultBg = $bg;
}
$width = (int)$request->get('w', 150);
$height = (int)$request->get('h', 40);
// 初始化验证码类
$builder = new CaptchaBuilder((int)$length);
$bgArr = explode(',', $defaultBg);
$builder->setBackgroundColor($bgArr[0], $bgArr[1], $bgArr[2]);
$builder->setDistortion(false);
$builder->setInterpolation(false);
$builder->setTextColor(255 - $bgArr[0], 255 - $bgArr[1], 255 - $bgArr[2]);
// 生成验证码
$builder->build($width, $height);
return $builder;
}
/**
* 生成图像验证码
* @return Response
*/
public static function captcha(): Response
{
$request = request();
$builder = self::create();
$request->session()->set('captcha', [
'captcha' => strtolower($builder->getPhrase()),
'expire' => time() + 60 * 5
]);
$img_content = $builder->inline();
return response($img_content, 200);
}
/**
* 图像验证码
*/
public static function captchaCode(): array
{
$request = request();
$builder = self::create();
$request->session()->set('captcha', [
'captcha' => strtolower($builder->getPhrase()),
'expire' => time() + 60 * 5
]);
$img_content = $builder->inline();
return ['base64' => $img_content, 'token' => $request->sessionId()];
}
}
@@ -0,0 +1,142 @@
<?php
namespace app\expose\helper;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\SubmitEvent;
use app\model\Config as ModelConfig;
use app\expose\utils\DataModel;
class Config extends DataModel
{
protected $configData = [];
protected $groupData = [];
protected $data = [];
protected $group = '';
protected $channels_uid = null;
/**
* 获取配置
*
* @param string $group 配置分组,settings目录下的文件名
* @param string|null $plugin 如若要获取全局配置,请传入''
*/
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
{
$request = request();
if ($plugin === null) {
$plugin = $request->plugin;
}
if ($request && $request->channels_uid && $channels_uid === null) {
$this->channels_uid = $request->channels_uid;
} elseif ($channels_uid) {
$this->channels_uid = $channels_uid;
}
$this->group = $plugin ? $plugin . '.' . $group : $group;
$this->configData = config('settings');
$this->groupData = $this->configData[$this->group] ?? [];
$this->builder();
}
/**
* 获取当前应用指定配置,如需获取全局配置请new Config('group','');
*
* @param string $group 配置分组,settings目录下的文件名
* @param string|null $field 获取自定字段配置
* @param mixed $default 默认数据
* @return array|mixed
*/
public static function get(string $group, string|null $field = null, mixed $default = null)
{
$self = new self($group);
if ($field) {
return isset($self->{$field}) ? $self->{$field} : $default;
}
return $self->toArray();
}
public function builder()
{
$d = [];
$request = request();
$domain = '';
$host = '';
if ($request) {
$domain = $request->header('x-forwarded-proto') . '://' . $request->host();
$host = $request->host();
}
$replaceData = [
'{DOMAIN}' => $domain,
'{HOST}' => $host,
'{CHANNELS_UID}' => $this->channels_uid
];
if (!empty($this->groupData)) {
foreach ($this->groupData as $key => $item) {
$has = false;
if (is_string($item['value']) && strpos($item['value'], '{DOMAIN}') !== false && $domain) {
$has = true;
} elseif (is_string($item['value']) && strpos($item['value'], '{HOST}') !== false && $host) {
$has = true;
} elseif (is_string($item['value']) && strpos($item['value'], '{CHANNELS_UID}') !== false && $this->channels_uid) {
$has = true;
}
if ($has) {
$d[$item['field']] = str_replace(array_keys($replaceData), array_values($replaceData), $item['value']);
} else {
$d[$item['field']] = $item['value'];
}
}
}
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
if ($ConfigModel) {
foreach ($ConfigModel->value as $field => $value) {
$has = false;
if (is_string($value) && strpos($value, '{DOMAIN}') !== false && $domain) {
$has = true;
} elseif (is_string($value) && strpos($value, '{HOST}') !== false && $host) {
$has = true;
} elseif (is_string($value) && strpos($value, '{CHANNELS_UID}') !== false && $this->channels_uid) {
$has = true;
}
if ($has) {
$d[$field] = str_replace(array_keys($replaceData), array_values($replaceData), $value);
} else {
$d[$field] = $value;
}
}
}
$this->data = $d;
}
public function getGroupData()
{
return $this->groupData;
}
public function getGrop()
{
return $this->group;
}
public static function set($group, $field, $value)
{
$self = new self($group);
$self->{$field} = $value;
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
if (!$ConfigModel) {
$ConfigModel = new ModelConfig;
$ConfigModel->group = $group;
$ConfigModel->channels_uid = $self->channels_uid;
}
$ConfigModel->value = $self->toArray();
$ConfigModel->save();
}
public static function formBuilder($group, $plugin = null, $channels_uid = null)
{
$self = new self($group, $plugin, $channels_uid);
$builder = new FormBuilder(null, null, [
'submitEvent' => SubmitEvent::SILENT
]);
$builder->setTranslations();
$groupData = $self->getGroupData();
foreach ($groupData as $key => $item) {
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
}
$builder->setData($self->toArray());
return $builder;
}
}
@@ -0,0 +1,110 @@
<?php
namespace app\expose\helper;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\SubmitEvent;
use app\model\Config as ModelConfig;
use app\expose\utils\DataModel;
class ConfigGroup extends DataModel
{
protected $configData = [];
protected $groupData = [];
protected $data = [];
protected $group = '';
protected $channels_uid = null;
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
{
$request = request();
if ($plugin === null) {
$plugin = $request->plugin;
}
if ($request && $request->channels_uid && $channels_uid === null) {
$this->channels_uid = $request->channels_uid;
} elseif ($channels_uid) {
$this->channels_uid = $channels_uid;
}
$this->group = $plugin ? $plugin . '.' . $group : $group;
$this->configData = config('settings-tabs');
$this->groupData = $this->configData[$this->group] ?? [];
$this->builder();
}
public function builder()
{
$d = [];
if (!empty($this->groupData)) {
foreach ($this->groupData as $key => $item) {
if ($key != 'group') {
$d[$item['field']] = $item['value'];
} else {
foreach ($item as $group) {
$d[$group['name']] = [];
foreach ($group['children'] as $child) {
$d[$group['name']][$child['field']] = $child['value'];
}
}
}
}
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
if ($ConfigModel) {
foreach ($ConfigModel->value as $field => $value) {
$d[$field] = $value;
}
}
}
$this->data = $d;
}
public function getGroupData()
{
return $this->groupData;
}
public function getGrop()
{
return $this->group;
}
public static function get($group, $field = null, $default = null)
{
$self = new self($group);
if ($field) {
return isset($self->{$field}) ? $self->{$field} : $default;
}
return $self->toArray();
}
public static function set($group, $field, $value)
{
$self = new self($group);
$self->{$field} = $value;
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
if (!$ConfigModel) {
$ConfigModel = new ModelConfig;
$ConfigModel->group = $group;
$ConfigModel->channels_uid = $self->channels_uid;
}
$ConfigModel->value = $self->toArray();
$ConfigModel->save();
}
public static function formBuilder($group, $plugin = null, $channels_uid = null)
{
$self = new self($group, $plugin, $channels_uid);
$builder = new FormBuilder(null, null, [
'translations' => true,
'submitEvent' => SubmitEvent::SILENT
]);
$groupData = $self->getGroupData();
foreach ($groupData as $key => $item) {
if ($key != 'group') {
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
}
}
foreach ($groupData['group'] as $key => $item) {
$subBuilder = new FormBuilder($item['name'], $item['title']);
foreach ($item['children'] as $child) {
$subBuilder->add($child['field'], $child['title'], $child['component'], $child['value'], $child['extra']);
}
$builder->addGroupForm($subBuilder);
}
$builder->setData($self->toArray());
return $builder;
}
}
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace app\expose\helper;
use app\expose\utils\DataModel;
class Menus extends DataModel
{
public $data = [];
public $itemData = [
# 菜单标题
"title" => "Title",
# 菜单图标,element-plus中的Icon名称,如:ElementPlus
"icon" => "",
# 路由路径
"path" => "",
# 使用组件,table:表格列表,form:表单,images:图片列表
"component" => "default",
# 请求方式,GET,POST,PUT,DELETE
"methods" => ["GET"],
# 是否显示,0:隐藏,1:显示
"show" => 1,
# 排序,正序
"sort" => 9999,
# 携带地址栏参数
"query" => [],
# 携带post参数
"params" => [],
# 路由meta
"meta" => [
"login" => true,
],
# 子级和父级参数一致
"children" => []
];
/**
* 构造函数
*
* @param object $Install 需实现 getMenus 方法的类返回菜单数据
*/
public function __construct($Install)
{
$request = request();
$lang = null;
if ($request && $request->lang) {
$lang = $request->lang;
}
$data = $Install->getMenus();
if ($lang) {
$this->translateChildren($data, $request->app,$lang);
}
foreach (glob(base_path('plugin/*')) as $path) {
$plugin_name = basename($path);
$class = 'plugin\\' . $plugin_name . '\\api\\Install';
$plugin = new $class;
$menus = $plugin->getMenus();
if ($menus) {
if ($lang&&$plugin->getPlugin()) {
$request->plugin = $plugin_name;
$this->translateChildren($menus, $request->app,$lang);
}
$data[] = $menus;
}
}
$this->builder($data);
}
public function translateChildren(&$data, $domain,$lang)
{
if (is_array($data) && isset($data[0])) {
foreach ($data as $key => &$item) {
if (isset($item['title'])) {
$data[$key]['title'] = trans($item['title'], [], $domain, $lang);
}
if (!empty($item['children'])) {
$this->translateChildren($item['children'],$domain, $lang);
}
}
} else {
if (isset($data['title'])) {
$data['title'] = trans($data['title'], [], $domain, $lang);
}
if (!empty($data['children'])) {
$this->translateChildren($data['children'],$domain, $lang);
}
}
}
/**
* 递归合并
*
* @param array $data
* @return $data
*/
public function mergeMenus(array $data): array
{
$result = [];
foreach ($data as $key => $item) {
$temp = array_merge($this->itemData, $item);
if (!empty($temp['children'])) {
$temp['children'] = $this->mergeMenus($temp['children']);
}
$result[] = $temp;
}
return $result;
}
/**
* 构建菜单
*
* @param array $data
* @return Menus
*/
public function builder(array $data)
{
$this->data = $this->mergeMenus($data);
return $this;
}
}
@@ -0,0 +1,188 @@
<?php
namespace app\expose\helper;
use app\expose\enum\PaymentChannels;
use app\expose\enum\Platform;
use app\expose\enum\State;
use app\expose\helper\Uploads as HelperUploads;
use app\model\PaymentConfig;
use app\model\PaymentTemplate;
use Exception;
use plugin\control\expose\helper\Uploads;
use Yansongda\Pay\Pay;
use support\Log;
class Payment
{
public static function get($platform, $channels, int|null $channels_uid = null)
{
$where = [];
$where[] = ['platform', '=', $platform];
$where[] = ['channels', '=', $channels];
if ($channels_uid) {
$where[] = ['channels_uid', '=', $channels_uid];
}
$PaymentConfig = PaymentConfig::where($where)->find();
if (!$PaymentConfig) {
throw new Exception('支付配置不存在');
}
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id, 'channels_uid' => $channels_uid])->find();
if (!$PaymentTemplate) {
throw new Exception('支付模板不存在');
}
return $PaymentTemplate->value;
}
public static function platform(int|null $channels_uid = null)
{
$platform = request()->platform;
$where = [];
$where[] = ['platform', '=', $platform];
$where[] = ['state', '=', State::YES['value']];
if ($channels_uid) {
$where[] = ['channels_uid', '=', $channels_uid];
}
$PaymentConfig = PaymentConfig::where($where)->select();
$data = [];
foreach ($PaymentConfig as $item) {
$enum = PaymentChannels::get($item->channels);
$temp = [
'id' => $item->id,
'label' => $enum['label'],
'enum' => $enum,
];
if ($item->is_default == State::YES['value']) {
$temp['default'] = 1;
}
$data[] = $temp;
}
return $data;
}
public static function getIntegralName()
{
$integral = '积分';
try {
$platform = request()->platform;
if (!$platform) {
$platform = Platform::PC['value'];
}
$Payment = self::get($platform, PaymentChannels::INTEGRAL['value']);
if (isset($Payment['display_name'])) {
$integral = $Payment['display_name'];
}
} catch (\Throwable $th) {
}
return $integral;
}
public static function createPayment($model, $id)
{
$PaymentConfig = PaymentConfig::where(['id' => $id])->find();
if (!$PaymentConfig) {
throw new Exception('支付配置不存在');
}
switch ($PaymentConfig->channels) {
case PaymentChannels::WXPAY['value']:
return self::wxPay($model, $PaymentConfig);
case PaymentChannels::ALIPAY['value']:
break;
case PaymentChannels::INTEGRAL['value']:
break;
case PaymentChannels::BALANCE['value']:
break;
}
throw new \Exception('未知支付方式');
}
public static function wxPay($model, $PaymentConfig)
{
switch ($PaymentConfig->platform) {
case Platform::PC['value']:
return self::wxPayPc($model, $PaymentConfig);
case Platform::H5['value']:
return self::wxPayH5($model, $PaymentConfig);
case Platform::WECHAT_OFFICIAL_ACCOUNT['value']:
return self::wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig);
case Platform::APP['value']:
return self::wxPayApp($model, $PaymentConfig);
case Platform::WECHAT_MINIAPP['value']:
return self::wxPayMiniapp($model, $PaymentConfig);
}
}
public static function wxPayPc($model, $PaymentConfig)
{
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id])->find();
if (!$PaymentTemplate) {
throw new Exception('支付模板不存在');
}
$Payment = $PaymentTemplate->value;
$notify_url = $Payment['notify_url'];
# 判断是否为“/”结尾
if (substr($notify_url, -1) == '/') {
$notify_url = substr($notify_url, 0, -1);
}
$notify_url .= '/notify/wechat/' . $model->plugin . '/' . $PaymentTemplate->id;
$mch_secret_cert = null;
if ($PaymentTemplate->channels_uid) {
$mch_secret_cert = Uploads::downloadTemp($Payment['ssl_key'], 'mch_secret_cert_' . $PaymentTemplate->id);
} else {
$mch_secret_cert = base_path(HelperUploads::path($Payment['ssl_key']));
}
$mch_public_cert_path = null;
if ($PaymentTemplate->channels_uid) {
$mch_public_cert_path = Uploads::downloadTemp($Payment['ssl_cert'], 'mch_public_cert_path_' . $PaymentTemplate->id);
} else {
$mch_public_cert_path = base_path(HelperUploads::path($Payment['ssl_cert']));
}
$config = [
'wechat' => [
'default' => [
// 必填-商户号
'mch_id' => $Payment['mch_id'],
// 选填-v2商户私钥
'mch_secret_key_v2' => '',
// 必填-v3商户秘钥
'mch_secret_key' => $Payment['mch_key'],
// 必填-商户私钥 字符串或路径
'mch_secret_cert' => $mch_secret_cert,
// 必填-商户公钥证书路径
'mch_public_cert_path' => $mch_public_cert_path,
// 必填
'notify_url' => $notify_url,
// 选填-公众号 的 app_id
'mp_app_id' => $Payment['appid'],
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
]
],
'logger' => [
'enable' => true,
'file' => runtime_path('logs/wechat-pay-' . date('Y-m-d') . '.log'),
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [
'timeout' => 5.0,
'connect_timeout' => 5.0,
],
];
$order = [
'out_trade_no' => $model->trade,
'amount' => [
'total' => getenv('DEV') === 'true' ? 1 : $model->price * 100,
],
'description' => $model->title,
'time_expire' => date('c', strtotime($model->expire_time)),
];
Log::info('wxPayPc', ['order' => $order, 'config' => $config]);
$Pay = Pay::wechat($config)->scan($order);
return [
'plugin' => $model->plugin,
'trade' => $model->trade,
'qrcode' => $Pay->code_url,
];
}
public static function wxPayH5($model, $PaymentConfig) {}
public static function wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig) {}
public static function wxPayApp($model, $PaymentConfig) {}
public static function wxPayMiniapp($model, $PaymentConfig) {}
}
@@ -0,0 +1,368 @@
<?php
namespace app\expose\helper;
use app\expose\enum\Filesystem;
use app\model\Uploads as ModelUploads;
use app\model\UploadsClassify;
use Exception;
use GuzzleHttp\Client;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Webman\Http\UploadFile;
class Uploads
{
/**
* 获取文件URL
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function url(string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::url($value);
}
return $data;
}
# 判断path是否以[?]开头,如果是则获取对应的key
if (strpos($path, '[') === 0) {
$key = substr($path, 1, strpos($path, ']') - 1);
$model = new \stdClass;
$model->channels = $key;
$model->path = substr($path, strpos($path, ']') + 1);
}else{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return $path;
}
}
return Storage::adapter($model->channels)->url($model->path);
}
/**
* 获取文件在本地存储的完整路径
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function local(string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::local($value);
}
return $data;
}
if (strpos($path, '[') === 0) {
$key = substr($path, 1, strpos($path, ']') - 1);
$model = new \stdClass;
$model->channels = $key;
$model->path = substr($path, strpos($path, ']') + 1);
}else{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return $path;
}
}
$filesystem = FilesystemFactory::get($model->channels);
$has = $filesystem->has($model->path);
if ($has) {
if (in_array($model->channels, [Filesystem::LOCAL['value'], Filesystem::PUBLIC['value']])) {
$config = config('plugin.shopwwi.filesystem.app.storage.' . $model->channels);
return $config['root'] . $model->path;
} else {
return self::downloadTemp(Storage::adapter($model->channels)->url($model->path));
}
}
return $path;
}
/**
* 获取文件路径
* @param string|array|null $url URL地址
* @return string|array
*/
public static function path(string|array|null $url)
{
if (empty($url)) {
return '';
}
if (is_array($url)) {
$data = [];
if (count($url) === 1) {
return self::path(current($url));
}
$config = config('plugin.shopwwi.filesystem.app.storage');
$urls = [];
foreach ($config as $key => $value) {
if (empty($value['url'])) {
continue;
}
$urls[$key] = $value['url'];
}
foreach ($url as $value) {
if (filter_var($value, FILTER_SANITIZE_URL) === false) {
throw new Exception('URL地址不合法');
}
$parseUrl = parse_url($value);
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
$key = array_search($domain, $urls);
if ($key) {
$data[] = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
} else {
$data[] = ltrim($parseUrl['path'], '/');
}
}
return $data;
} else {
if (filter_var($url, FILTER_SANITIZE_URL) === false) {
throw new Exception('URL地址不合法');
}
$parseUrl = parse_url($url);
$config = config('plugin.shopwwi.filesystem.app.storage');
$urls = [];
foreach ($config as $key => $value) {
if (empty($value['url'])) {
continue;
}
$urls[$key] = $value['url'];
}
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
$key = array_search($domain, $urls);
if ($key) {
$data = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
} else {
$data = ltrim($parseUrl['path'], '/');
}
return $data;
}
}
/**
* 保存文件
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @param string $title 文件分类标题
* @return array
*/
public static function save(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save', $title = '本地保存')
{
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$date_path = date('Ymd');
$originName = basename($path);
//单文件上传
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
$Uploads = new ModelUploads;
$Uploads->classify_id = $UploadsClassify->id;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
/**
* 远程下载文件
* @param string $url 文件URL
* @param string $channels 文件存储通道
* @return string
*/
public static function download(string $url, $channels = Filesystem::PUBLIC['value'])
{
$dir_name = 'uploads/remote';
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'is_system' => 1])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = '远程下载';
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$date_path = date('Ymd');
$client = new Client();
$response = $client->get($url);
$body = $response->getBody();
$file = $body->getContents();
$urlPath = parse_url($url, PHP_URL_PATH);
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
$fileName = uniqid() . '.' . $ext;
$temp = tempnam(sys_get_temp_dir(), '') . $fileName;
file_put_contents($temp, $file);
$file = new UploadFile($temp, $temp, $response->getHeaderLine('Content-Type'), 0);
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
\unlink($temp);
$Uploads = new ModelUploads;
$Uploads->classify_id = $UploadsClassify->id;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return $result->file_name;
}
/**
* 下载文件到临时文件
* @param string $url 文件URL
* @return string 临时文件路径
*/
public static function downloadTemp(string $url)
{
$client = new Client();
$response = $client->get($url);
$body = $response->getBody();
$file = $body->getContents();
$urlPath = parse_url($url, PHP_URL_PATH);
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
$fileName = uniqid() . '.' . $ext;
$temp = runtime_path('temp/' . $fileName);
file_put_contents($temp, $file);
return $temp;
}
/**
* 上传文件
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @return array
*/
public static function upload(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save')
{
$date_path = date('Ymd');
$originName = basename($path);
//单文件上传
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
$Uploads = new ModelUploads;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
/**
* 删除文件
* @param string $path 文件路径
* @return bool
*/
public static function delete(string $path)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return true;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->delete($model->path);
$model->delete();
return true;
}
/**
* 重命名文件
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function rename(string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->move($model->path, $newName);
$model->path = $newName;
$model->save();
return true;
}
/**
* 判断文件是否存在
* @param string $path 文件路径
* @return bool
*/
public static function has(string $path)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
return $filesystem->has($model->path);
}
/**
* 复制文件
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function copy(string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->copy($model->path, $newName);
$model = new ModelUploads;
$model->classify_id = $model->classify_id;
$model->filename = $model->filename;
$model->path = $newName;
$model->ext = $model->ext;
$model->mime = $model->mime;
$model->size = $model->size;
$model->path = $newName;
$model->save();
return true;
}
/**
* 获取文件列表
* @param string $path 文件路径
* @param bool $recursive 是否递归
* @return array
*/
public static function listContents(string $path, $recursive = false)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
return $filesystem->listContents($path, $recursive);
}
}
@@ -0,0 +1,79 @@
<?php
namespace app\expose\helper;
use app\expose\enum\State;
use app\expose\template\email\Vcode as EmailVcode;
use app\expose\template\sms\Vcode as SmsVcode;
use app\expose\utils\Email;
use app\expose\utils\Sms;
use app\expose\utils\Str;
use support\Log;
class Vcode
{
public static function check($username, $vcode, $scene, $token = null)
{
$request = request();
if (!empty($token)) {
$request->sessionId($token);
}
$vcodeData = $request->session()->get('vcode:' . $scene . ':' . $username);
if (empty($vcodeData)) {
throw new \Exception('验证码不存在');
}
if ($vcodeData['vcode'] != $vcode) {
throw new \Exception('验证码错误');
}
if ($vcodeData['expire'] < time()) {
throw new \Exception('验证码已过期');
}
return true;
}
public static function send($username, $scene, $token = null)
{
$request = request();
if (!empty($token)) {
$request->sessionId($token);
}
$vcode = Str::random(6, 1);
if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
$Email = new Email;
$Email->toemail = $username;
$Email->setTemplate(EmailVcode::class);
$Email->setData(['vcode' => $vcode]);
$Email->send();
} else {
$request->session()->set('vcode:' . $scene . ':' . $username, [
'vcode' => $vcode,
'expire' => time() + 60 * 5
]);
$config = new ConfigGroup('sms', '');
foreach ($config['channels'] as $channel) {
$item = $config[$channel];
if ($item['enable'] == State::NO['value']) {
continue;
}
try {
if (empty($item['vcode_template_' . $scene])) {
continue;
}
$SmsVcode = new SmsVcode();
$SmsVcode->channel = $channel;
$SmsVcode->config = $item;
$SmsVcode->template_code = $item['vcode_template_' . $scene];
$Sms = new Sms;
$Sms->mobile = $username;
$Sms->setTemplate($SmsVcode);
$Sms->setData(['code' => $vcode]);
$Sms->send();
return true;
} catch (\Throwable $th) {
Log::error("发送短信失败:" . $th->getMessage(), $th->getTrace());
continue;
}
}
throw new \Exception('发送失败');
}
}
}
@@ -0,0 +1,126 @@
<?php
namespace app\expose\helper;
use GuzzleHttp\Client;
use support\Redis;
class Wechat
{
/**
* QR_SCENE
* 临时的整型参数值
* @var string
*/
const QR_SCENE = 'QR_SCENE';
/**
* QR_STR_SCENE
* 临时的字符串参数值
* @var string
*/
const QR_STR_SCENE = 'QR_STR_SCENE';
/**
* QR_LIMIT_SCENE
* 永久的整型参数值
* @var string
*/
const QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
/**
* QR_LIMIT_STR_SCENE
* 永久的字符串参数值
* @var string
*/
const QR_LIMIT_STR_SCENE = 'QR_LIMIT_STR_SCENE';
public static function getAccessToken()
{
$config = new Config('wechat_official_account', '');
$access_token = Redis::get('wechat_official_account_access_token');
if ($access_token) {
return $access_token;
}
if (!$config['state']) {
throw new \Exception('请先开启公众号');
}
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$config['app_id']}&secret={$config['app_secret']}";
try {
$client = new Client();
$response = $client->get($url);
$data = json_decode($response->getBody()->getContents(), true);
if (isset($data['errcode'])) {
throw new \Exception($data['errmsg']);
}
Redis::set('wechat_official_account_access_token', $data['access_token'], 'EX', $data['expires_in']);
return $data['access_token'];
} catch (\Throwable $th) {
throw $th;
}
}
/**
* 创建公众号二维码
*
* @param string $action_name QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
* @param int $expire 二维码有效时间,以秒为单位。最大不超过2592000(即30天)
* @param array $eventData 回调事件数据
* @param Class $eventData[0] 回调事件类
* @param string $eventData[1] 回调事件方法
* @param array $eventData[2] 回调事件参数,会在回调事件方法中$data.params原样传入
* @return array $data
* @return string $data['url'] 二维码图片地址
* @return string $data['id'] 二维码id,在回调事件中$data['EventKey']
* @return int $data['expire_seconds'] 二维码有效时间
* @throws \Exception
*/
public static function createQrCode($action_name, $expire, $eventData)
{
$request = request();
$id = $request->sessionId();
if(count($eventData)<2){
throw new \Exception('回调事件数据不完整');
}
$access_token = self::getAccessToken();
$url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $access_token;
$client = new Client();
$data = [
'expire_seconds' => $expire,
'action_name' => $action_name,
'action_info' => [
'scene' => []
]
];
if (in_array($action_name, [self::QR_SCENE, self::QR_LIMIT_SCENE])) {
$data['action_info']['scene'] = ['scene_id' => $id];
} else {
$data['action_info']['scene'] = ['scene_str' => $id];
}
Redis::set($id, json_encode($eventData, JSON_UNESCAPED_UNICODE), 'EX', $expire + 60);
$response = $client->post($url, [
'json' => $data
]);
$res = json_decode($response->getBody()->getContents(), true);
if (isset($res['errcode'])) {
throw new \Exception($res['errmsg']);
}
$res['id'] = $id;
return $res;
}
/**
* 发送模板消息
*
* @param mixed $params 模板消息参数
* @return string
*/
public static function sendTemplate($params)
{
$access_token = self::getAccessToken();
$url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token;
$client = new Client([
'content_type' => 'application/json',
]);
$response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]);
$data = json_decode($response->getBody()->getContents(), true);
if (empty($data['msgid'])) {
throw new \Exception("[{$data['errcode']}]" . $data['errmsg']);
}
return $data['msgid'];
}
}