FastMovieAI 二开基线 v1.0 (完整源码)

This commit is contained in:
李建琦
2026-08-26 18:41:02 +08:00
parent fad7690c0c
commit d54584f70d
888 changed files with 105571 additions and 1 deletions
@@ -0,0 +1,46 @@
<?php
namespace plugin\control\expose\helper;
use app\expose\helper\Menus;
class Control extends Menus
{
/**
* 构造函数
*
* @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);
if ($plugin_name == 'control') {
continue;
}
$class = 'plugin\\' . $plugin_name . '\\api\\Control';
if (!class_exists($class)) {
continue;
}
$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);
}
}
@@ -0,0 +1,382 @@
<?php
namespace plugin\control\expose\helper;
use app\expose\helper\Config;
use app\model\Uploads as ModelUploads;
use app\model\UploadsClassify;
use Exception;
use GuzzleHttp\Client;
use Shopwwi\WebmanFilesystem\Storage;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Webman\Http\UploadFile;
class Uploads
{
/**
* 获取文件URL
* @param int $channels_uid 渠道用户ID
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function url(int $channels_uid, string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::url($channels_uid, $value);
}
return $data;
}
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return $path;
}
$config = new Config('filesystem', '', $channels_uid);
$Storage = new Storage($config->toArray());
return $Storage->adapter($model->channels)->url($model->path);
}
/**
* 获取文件在本地存储的完整路径
* @param int $channels_uid 渠道用户ID
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function local(int $channels_uid, string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::local($channels_uid, $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, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return $path;
}
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
$has = $filesystem->has($model->path);
if ($has) {
return self::downloadTemp(self::url($channels_uid, $model->path));
}
return $path;
}
/**
* 获取文件路径
* @param string|array|null $url URL地址
* @return string|array
*/
public static function path(int $channels_uid, string|array|null $url)
{
if (empty($url)) {
return '';
}
if (is_array($url)) {
$data = [];
if (count($url) === 1) {
return self::path($channels_uid, current($url));
}
foreach ($url as $value) {
$data[] = self::path($channels_uid, $value);
}
return $data;
} else {
if (filter_var($url, FILTER_SANITIZE_URL) === false) {
throw new Exception('URL地址不合法');
}
$parseUrl = parse_url($url);
return ltrim($parseUrl['path'], '/');
}
}
public static function getClassify(int $channels_uid, string $dir_name, string $title, $channels = null)
{
if (!$channels) {
$config = new Config('filesystem', '', $channels_uid);
$channels = $config->default;
}
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels, 'channels_uid' => $channels_uid])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->channels_uid = $channels_uid;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
return $UploadsClassify;
}
/**
* 保存文件
* @param int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @param string $title 文件分类标题
* @return array
*/
public static function save(int $channels_uid, string $path, $channels = null, $dir_name = 'uploads/save', $title = '本地保存')
{
$config = new Config('filesystem', '', $channels_uid);
if (!$channels) {
$channels = $config->default;
}
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels, 'channels_uid' => $channels_uid])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->channels_uid = $channels_uid;
$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));
$Storage = new Storage($config->toArray());
$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->channels_uid = $channels_uid;
$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 mixed
*/
public static function download(int $channels_uid, string $url, UploadsClassify $UploadsClassify)
{
$dir_name = $UploadsClassify->dir_name;
$config = new Config('filesystem', '', $channels_uid);
$channels = $UploadsClassify->channels;
$date_path = date('Ymd');
$client = new Client([
'timeout' => 600,
'verify' => false,
'proxy' => false
]);
$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);
$Storage = new Storage($config->toArray());
$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->channels_uid = $channels_uid;
$Uploads->save();
return $result;
}
/**
* 下载文件到临时文件
* @param string $url 文件URL
* @return string 临时文件路径
*/
public static function downloadTemp(string $url, ?string $fileName = null)
{
if (!$fileName) {
$fileName = uniqid();
}
$urlPath = parse_url($url, PHP_URL_PATH);
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
$temp = runtime_path('temp/' . $fileName . '.' . $ext);
if (file_exists($temp)) {
return $temp;
}
$client = new Client([
'proxy' => null,
'verify' => false,
'timeout' => 600
]);
$response = $client->get($url);
$body = $response->getBody();
$file = $body->getContents();
file_put_contents($temp, $file);
return $temp;
}
/**
* 上传文件
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @return array
*/
public static function upload(int $channels_uid, string|UploadFile $path, $channels = null, $dir_name = 'uploads/save')
{
$date_path = date('Ymd');
//单文件上传
if (is_string($path)) {
$originName = basename($path);
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
} else {
$originName = $path->getUploadName();
$file = $path;
}
$config = new Config('filesystem', '', $channels_uid);
if (!$channels) {
$channels = $config->default;
}
$Storage = new Storage($config->toArray());
$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->channels_uid = $channels_uid;
$Uploads->save();
return [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
/**
* 删除文件
* @param int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @return bool
*/
public static function delete(int $channels_uid, string $path)
{
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return true;
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
$filesystem->delete($model->path);
$model->delete();
return true;
}
/**
* 重命名文件
* @param int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function rename(int $channels_uid, string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return false;
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
$filesystem->move($model->path, $newName);
$model->path = $newName;
$model->save();
return true;
}
/**
* 判断文件是否存在
* @param int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @return bool
*/
public static function has(int $channels_uid, string $path)
{
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return false;
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
return $filesystem->has($model->path);
}
/**
* 复制文件
* @param int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function copy(int $channels_uid, string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return false;
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
$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 int $channels_uid 渠道用户ID
* @param string $path 文件路径
* @param bool $recursive 是否递归
* @return array
*/
public static function listContents(int $channels_uid, string $path, $recursive = false)
{
$model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find();
if (!$model) {
return false;
}
$config = new Config('filesystem', '', $channels_uid);
$filesystem = FilesystemFactory::get($model->channels, $config->toArray());
return $filesystem->listContents($path, $recursive);
}
}
@@ -0,0 +1,80 @@
<?php
namespace plugin\control\expose\helper;
use app\expose\enum\State;
use app\expose\helper\ConfigGroup;
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(4, 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', 'control');
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,129 @@
<?php
namespace plugin\control\expose\helper;
use app\expose\helper\Config;
use GuzzleHttp\Client;
use support\Redis;
use support\Request;
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(Request $request)
{
$config = new Config('wechat_official_account', 'control');
$access_token = Redis::get('wechat_official_account_access_token_' . $request->channels_uid);
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_' . $request->channels_uid, $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($request);
$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)
{
$request = request();
$access_token = self::getAccessToken($request);
$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'];
}
}