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
+10
View File
@@ -0,0 +1,10 @@
# LoongAdmin Plugin
#### 使用方法
```binsh
git clone https://github.com/RenLoong/control.git
```
克隆仓库至plugin目录,访问:http://你的域名/control/
+39
View File
@@ -0,0 +1,39 @@
{
"title": "渠道",
"icon": "ElementPlus",
"path": "/app/control/admin",
"sort": 2,
"children": [
{
"title": "User List",
"icon": "User",
"path": "/app/control/admin/User/index",
"component": "table",
"children": [
{
"title": "Create User",
"path": "/app/control/admin/User/create",
"show": false,
"component": "form"
},
{
"title": "Edit User",
"show": false,
"path": "/app/control/admin/User/update",
"component": "form"
},
{
"title": "Update Status",
"show": false,
"path": "/app/control/admin/User/indexUpdateState",
"component": ""
}
]
},
{
"title": "系统设置",
"path": "/app/control/admin/settings/state",
"component": "form"
}
]
}
@@ -0,0 +1,15 @@
<?php
namespace plugin\control\api;
use plugin\control\expose\api\Control as ApiControl;
class Control
{
use ApiControl;
public function __construct()
{
$this->path = __DIR__;
$this->plugin = 'control';
}
}
@@ -0,0 +1,15 @@
<?php
namespace plugin\control\api;
use app\expose\api\Install as ApiInstall;
class Install
{
use ApiInstall;
public function __construct()
{
$this->path = __DIR__;
$this->plugin = 'control';
}
}
@@ -0,0 +1,20 @@
<?php
namespace plugin\control\app\admin\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\Filesystem;
use app\expose\enum\SubmitEvent;
use app\expose\trait\Config;
use support\Request;
class SettingsController extends Basic
{
use Config;
public function state(Request $request)
{
return $this->builder();
}
}
@@ -0,0 +1,426 @@
<?php
namespace plugin\control\app\admin\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\Action;
use app\expose\enum\State;
use plugin\control\app\model\PluginChannelsUser;
use support\Request;
class UserController extends Basic
{
public function __construct()
{
$this->model = new PluginChannelsUser;
}
public function indexGetTable(Request $request)
{
$builder = new TableBuilder;
$builder->addAction('操作', [
'width' => '100px',
'fixed' => 'right'
]);
$builder->addTableAction('编辑', [
'model' => Action::DIALOG['value'],
'path' => '/app/control/admin/User/update',
'props' => [
'title' => '编辑《ID{id}》渠道用户'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'primary',
'size' => 'small'
]
]
]);
$builder->addHeader();
$builder->addHeaderAction('创建渠道用户', [
'model' => Action::DIALOG['value'],
'path' => '/app/control/admin/User/create',
'props' => [
'title' => '创建渠道用户'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'success'
]
]
]);
$formBuilder = new FormBuilder(null, null, [
'inline' => true
]);
$formBuilder->add('nickname', '昵称', 'input', '', [
'props' => [
'placeholder' => '昵称搜索',
'clearable' => true
]
]);
$formBuilder->add('username', '账号', 'input', '', [
'props' => [
'placeholder' => '账号搜索',
'clearable' => true
]
]);
$formBuilder->add('mobile', '手机号', 'input', '', [
'props' => [
'placeholder' => '手机号搜索',
'clearable' => true
]
]);
$formBuilder->add('email', '邮箱', 'input', '', [
'props' => [
'placeholder' => '邮箱搜索',
'clearable' => true
]
]);
$formBuilder->add('uid', 'UID', 'input', '', [
'props' => [
'placeholder' => 'UID搜索',
'clearable' => true
]
]);
$builder->addScreen($formBuilder);
$builder->add('id', 'ID', [
'props' => [
'width' => '100px'
]
]);
$builder->add('userinfo', '渠道用户', [
'component' => [
'name' => 'table-userinfo',
'props' => [
'nickname' => 'nickname',
'avatar' => 'headimg',
'info' => 'username',
'nicknameTags' => [
[
'field' => 'new_text',
'props' => [
'type' => 'success',
'size' => 'small'
]
],
[
'field' => 'week_text',
'props' => [
'type' => 'warning',
'size' => 'small'
]
],
[
'field' => 'month_text',
'props' => [
'type' => 'info',
'size' => 'small'
]
]
]
]
],
'props' => [
'minWidth' => '300px'
]
]);
$builder->add('contact', '联系方式', [
'props' => [
'width' => '280px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'mobile',
'label' => '手机号'
],
[
'field' => 'email',
'label' => '邮箱'
]
]
]
]
]);
$builder->add('state', '状态', [
'component' => [
'name' => 'switch',
'api' => '/app/control/admin/User/indexUpdateState',
'props' => [
'active-value' => State::YES['value'],
'inactive-value' => State::NO['value']
]
],
'props' => [
'width' => '100px'
]
]);
$builder->add('online_time', '活动', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'activation_time',
'label' => '激活'
],
[
'field' => 'login_time',
'label' => '登录'
],
[
'component' => 'tag',
'field' => 'login_ip',
'label' => '登录IP',
'props' => [
'size' => 'small',
]
]
]
]
]
]);
$builder->add('create_time', '时间', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'create_time',
'label' => '创建'
],
[
'field' => 'update_time',
'label' => '更新'
]
]
]
]
]);
$builder = $builder->builder();
return $this->resData($builder);
}
public function query(Request $request)
{
$query = $request->post('query');
if (empty($query)) {
return $this->resData([]);
}
$where = [];
$where[] = ['nickname|username|mobile|email', 'like', "%{$query}%"];
return $this->resData(PluginChannelsUser::options($where));
}
public function index(Request $request)
{
$limit = $request->get('limit', 10);
$where = [];
$username = $request->get('username');
if ($username) {
$where[] = ['username', 'like', "%{$username}%"];
}
$mobile = $request->get('mobile');
if ($mobile) {
$where[] = ['mobile', 'like', "%{$mobile}%"];
}
$email = $request->get('email');
if ($email) {
$where[] = ['email', 'like', "%{$email}%"];
}
$puid = $request->get('puid');
if ($puid) {
$where[] = ['puid', '=', $puid];
}
$uid = $request->get('uid');
if ($uid) {
$where[] = ['id', '=', $uid];
}
$list = PluginChannelsUser::where($where)
->order('id desc')->paginate($limit)->each(function ($item) {
// 三天以内创建的
$create_time = strtotime($item->create_time);
$create_time = time() - $create_time;
if ($create_time < 3 * 24 * 60 * 60) {
$item->new_text = '新渠道用户';
} elseif ($create_time < 7 * 24 * 60 * 60) {
$item->week_text = '一周内';
} elseif ($create_time < 30 * 24 * 60 * 60) {
$item->month_text = '30天内';
}
});
return $this->resData($list);
}
public function create(Request $request)
{
if ($request->method() === 'POST') {
$data = $request->post();
try {
$insterData = [];
if (empty($data['username']) && empty($data['mobile'])) {
throw new \Exception('渠道用户名、手机号至少填写一项');
}
if (!empty($data['username'])) {
$Find = PluginChannelsUser::where(['username' => $data['username']])->find();
if ($Find) {
throw new \Exception('渠道用户名已存在');
}
$insterData['username'] = $data['username'];
}
if (!empty($data['mobile'])) {
$Find = PluginChannelsUser::where(['mobile' => $data['mobile']])->find();
if ($Find) {
throw new \Exception('手机号已存在');
}
$insterData['mobile'] = $data['mobile'];
}
if (!empty($data['email'])) {
$insterData['email'] = $data['email'];
}
if (!empty($data['password'])) {
$insterData['password'] = $data['password'];
}
if (!empty($data['activation_time'])) {
$insterData['activation_time'] = $data['activation_time'];
}
if (!empty($data['nickname'])) {
$insterData['nickname'] = $data['nickname'];
}
if (!empty($data['headimg'])) {
$insterData['headimg'] = $data['headimg'];
}
$model = new PluginChannelsUser();
$model->save($insterData);
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->success('创建成功');
}
$builder = $this->getFormBuilder();
$Component = new ComponentBuilder;
$builder->add('activation_time', '激活时间', 'date-picker', null, [
'prompt' => [
$Component->add('text', ['default' => '不选择时间则不激活,由渠道用户前台登录后自动激活'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '选择激活时间',
'type' => 'datetime',
'format' => 'YYYY-MM-DD HH:mm:ss',
'value-format' => 'YYYY-MM-DD HH:mm:ss'
]
]);
return $this->resData($builder);
}
public function update(Request $request)
{
if ($request->method() === 'POST') {
$data = $request->post();
try {
$user = PluginChannelsUser::where(['id' => $data['id']])->find();
if (!$user) {
throw new \Exception('渠道用户不存在');
}
if (!empty($data['password'])) {
$user->password = $data['password'];
}
if (!empty($data['username'])) {
$Find = PluginChannelsUser::where(['username' => $data['username']])->find();
if ($Find && $Find->id != $data['id']) {
throw new \Exception('渠道用户名已存在');
}
$user->username = $data['username'];
}
if (!empty($data['mobile'])) {
$Find = PluginChannelsUser::where(['mobile' => $data['mobile']])->find();
if ($Find && $Find->id != $data['id']) {
throw new \Exception('手机号已存在');
}
$user->mobile = $data['mobile'];
}
if (!empty($data['email'])) {
$user->email = $data['email'];
}
if (!empty($data['nickname'])) {
$user->nickname = $data['nickname'];
}
if (!empty($data['headimg'])) {
$user->headimg = $data['headimg'];
}
$user->save();
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->success('更新成功');
}
$id = $request->get('id');
$User = PluginChannelsUser::where(['id' => $id])->withoutField('password')->find();
$builder = $this->getFormBuilder();
$builder->setData($User->toArray());
return $this->resData($builder);
}
private function getFormBuilder()
{
$builder = new FormBuilder(null, null, [
'labelPosition' => 'right',
'label-width' => "200px",
'class' => 'w-80 mx-auto',
'size' => 'large',
]);
$Component = new ComponentBuilder;
$builder->add('nickname', '昵称', 'input', '', [
'required' => true,
'maxlength' => 30,
'show-word-limit' => true
]);
$builder->add('username', '账号', 'input', '', [
'prompt' => [
$Component->add('text', ['default' => '支持字母、数字、下划线,长度不超过30个字符'], ['type' => 'info', 'size' => 'small'])->builder(),
$Component->add('text', ['default' => '可用于账号登录'], ['type' => 'success', 'size' => 'small'])->builder()
],
'props' => [
'maxlength' => 30,
'show-word-limit' => true
]
]);
$builder->add('mobile', '手机号', 'input', '', [
'prompt' => [
$Component->add('text', ['default' => '中国大陆11位手机号'], ['type' => 'info', 'size' => 'small'])->builder(),
$Component->add('text', ['default' => '可用于账号,短信验证码登录'], ['type' => 'success', 'size' => 'small'])->builder()
],
'props' => [
'maxlength' => 11,
'show-word-limit' => true
]
]);
$builder->add('email', '邮箱', 'input', '', [
'prompt' => [
$Component->add('text', ['default' => '可用于账号,邮箱验证码登录'], ['type' => 'success', 'size' => 'small'])->builder()
],
'props' => [
'maxlength' => 100,
'show-word-limit' => true
]
]);
$builder->add('password', '密码', 'input', '', [
'prompt' => [
$Component->add('text', ['default' => '不修改密码请留空'], ['type' => 'info', 'size' => 'small'])->builder()
],
'props' => [
'placeholder' => '不修改密码请留空',
'maxlength' => 30,
'show-word-limit' => true
]
]);
return $builder;
}
}
@@ -0,0 +1,113 @@
<?php
namespace plugin\control\app\api\controller;
use app\Basic;
use app\expose\helper\Captcha;
use app\expose\helper\Config;
use plugin\control\expose\helper\Vcode;
use plugin\shortplay\utils\enum\ActorAge;
use plugin\shortplay\utils\enum\ActorGender;
use plugin\shortplay\utils\enum\ActorSpeciesType;
use plugin\shortplay\utils\enum\ActorStatus;
use plugin\shortplay\utils\enum\StyleClassify;
use plugin\shortplay\utils\enum\VoiceEmotion;
use plugin\shortplay\utils\enum\VoiceLanguage;
use plugin\user\app\model\PluginUser;
use plugin\user\utils\enum\VcodeScene;
use support\Request;
class PublicController extends Basic
{
protected $notNeedLoginAll = true;
public function config(Request $request)
{
$config = new Config('basic', 'control');
$register = new Config('register', 'user', $request->channels_uid);
$config->enum = [
'actor_species_type' => ActorSpeciesType::getOptions(),
'actor_gender' => ActorGender::getOptions(),
'actor_age' => ActorAge::getOptions(),
'actor_status' => ActorStatus::getOptions(),
'style_classify' => StyleClassify::getOptions(),
'voice_emotion' => VoiceEmotion::getOptions(),
'voice_language' => VoiceLanguage::getOptions(),
];
$pushConfig = new Config('push', 'notification', 0);
if ($pushConfig->state) {
$config->push = [
'url' => $pushConfig->wss_url,
'app_key' => config('plugin.webman.push.app.app_key'),
'auth' => config('plugin.webman.push.app.auth'),
];
}
$config->register = $register->toArray();
return $this->resData($config);
}
public function getSmsVcode(Request $request)
{
$scene = $request->post('scene');
if (empty($scene)) {
return $this->fail('参数错误');
}
$token = $request->post('token');
if ($token) {
$request->sessionId($token);
}
try {
$captcha_state = 1;
if ($captcha_state) {
$captcha = $request->post('captcha');
if (empty($captcha)) {
return $this->fail('请输入验证码');
}
Captcha::check($captcha, $token);
}
$username = $request->post('username');
switch ($scene) {
case VcodeScene::LOGIN['value']:
$UserModel = PluginUser::where(['mobile' => $username])->find();
if (!$UserModel) {
throw new \Exception('手机号未注册');
}
Vcode::send($username, VcodeScene::LOGIN['value'], $token);
break;
case VcodeScene::SIGNUP['value']:
Vcode::send($username, VcodeScene::SIGNUP['value'], $token);
break;
case VcodeScene::BIND_MOBILE['value']:
if (!$request->uid) {
throw new \Exception('请先登录');
}
// $UserModel = PluginUser::where(['mobile' => $username])->find();
// if ($UserModel && $UserModel->id != $request->uid) {
// throw new \Exception('手机号已存在');
// }
Vcode::send($username, VcodeScene::BIND_MOBILE['value'], $token);
break;
case VcodeScene::BIND_EMAIL['value']:
if (!$request->uid) {
throw new \Exception('请先登录');
}
$UserModel = PluginUser::where(['email' => $username])->find();
if ($UserModel && $UserModel->id != $request->uid) {
throw new \Exception('邮箱已存在');
}
Vcode::send($username, VcodeScene::BIND_EMAIL['value'], $token);
break;
case VcodeScene::SET_PASSWORD['value']:
if (!$request->uid) {
throw new \Exception('请先登录');
}
$mobile = PluginUser::where(['id' => $request->uid])->value('mobile');
Vcode::send($mobile, VcodeScene::SET_PASSWORD['value'], $token);
break;
default:
throw new \Exception('场景错误');
}
} catch (\Throwable $th) {
return $this->fail($th->getMessage());
}
return $this->success();
}
}
@@ -0,0 +1,39 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\helper\Captcha;
use support\Request;
use support\Response;
/**
* 图像验证码
* Class CaptchaController
* @package app\api\controller
*/
class CaptchaController extends Basic
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $notNeedLogin = [
'captcha', 'captchaCode'
];
/**
* 获取图像验证码
* @param Request $request
* @return Response
*/
public function captcha(Request $request)
{
return Captcha::captcha();
}
public function captchaCode()
{
return $this->resData( Captcha::captchaCode());
}
}
@@ -0,0 +1,289 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\Action;
use app\expose\enum\State;
use app\expose\utils\Rsa;
use app\expose\utils\Str;
use GuzzleHttp\Client;
use plugin\control\app\model\PluginChannelsDomain;
use support\Redis;
use support\Request;
class DomainController extends Basic
{
protected $notNeedLogin = ['downloadFile'];
public function __construct()
{
$this->model = new PluginChannelsDomain();
}
public function indexGetTable(Request $request)
{
$builder = new TableBuilder;
$builder->addAction('操作', [
'width' => '100px',
'fixed' => 'right'
]);
$builder->addTableAction('删除', [
'model' => Action::COMFIRM['value'],
'path' => '/app/control/control/Domain/delete',
'props' => [
'message' => '确定要删除《{domain}》域名吗?'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'danger',
'size' => 'small'
]
]
]);
$builder->addHeader();
$builder->addHeaderAction('绑定域名', [
'model' => Action::DIALOG['value'],
'path' => '/app/control/control/Domain/create',
'props' => [
'title' => '绑定域名'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'success'
]
]
]);
$formBuilder = new FormBuilder(null, null, [
'inline' => true
]);
$formBuilder->add('domain', '域名', 'input', '', [
'props' => [
'placeholder' => '域名搜索',
'clearable' => true
]
]);
$builder->addScreen($formBuilder);
$builder->add('id', 'ID', [
'props' => [
'width' => '100px'
]
]);
$builder->add('domain', '域名', [
'props' => [
'minWidth' => '200px'
]
]);
$builder->add('state', '状态', [
'component' => [
'name' => 'switch',
'api' => '/app/control/control/Domain/indexUpdateState',
'props' => [
'active-value' => State::YES['value'],
'inactive-value' => State::NO['value']
]
],
'props' => [
'width' => '100px'
]
]);
$builder->add('remarks', '备注', [
'props' => [
'width' => '300px'
]
]);
$builder->add('create_time', '时间', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'create_time',
'label' => '创建'
],
[
'field' => 'update_time',
'label' => '更新'
]
]
]
]
]);
$builder = $builder->builder();
return $this->resData($builder);
}
public function index(Request $request)
{
$limit = $request->get('limit', 10);
$where = [];
$domain = $request->get('domain');
if ($domain) {
$where[] = ['domain', 'like', "%{$domain}%"];
}
$where[] = ['channels_uid', '=', $request->channels_uid];
$list = PluginChannelsDomain::where($where)
->order('id desc')->paginate($limit)->each(function ($item) {});
return $this->resData($list);
}
/**
* 更新状态字段
* @method POST
*/
public function indexUpdateState(Request $request)
{
$id = $request->post('id');
$field = $request->post('field');
$value = $request->post('value');
$model = $this->model->where(['id' => $id])->find();
if (!$model) {
return $this->fail('数据不存在');
}
$model->{$field} = $value;
if ($model->save()) {
if ($field == 'state' && $value == State::YES['value']) {
Redis::hSet('domain_map', $model->domain, $model->channels_uid);
} else if ($field == 'state' && $value == State::NO['value']) {
Redis::hDel('domain_map', $model->domain);
}
return $this->success();
}
return $this->fail('操作失败');
}
public function create(Request $request)
{
$file_name = md5('domain-verify-' . $request->channels_uid) . '.txt';
$file = runtime_path('temp/') . $file_name;
if ($request->method() === 'POST') {
$data = $request->post();
try {
$content = file_get_contents($file);
if ($data['verify_type'] === 'file') {
$remote_content = file_get_contents('http://' . $data['domain'] . '/' . $file_name);
if ($content !== $remote_content) {
throw new \Exception('验证失败');
}
} else {
// 获取域名解析记录
$this->verifyDomainRecord($file_name . '.' . $data['domain'], $content);
}
} catch (\Throwable $th) {
return $this->fail('验证失败');
}
try {
$insterData = [];
if (empty($data['domain'])) {
throw new \Exception('请填写域名');
}
if (!empty($data['domain'])) {
$Find = PluginChannelsDomain::where(['domain' => $data['domain']])->find();
if ($Find) {
throw new \Exception('域名已存在');
}
$insterData['domain'] = $data['domain'];
}
if (!empty($data['remarks'])) {
$insterData['remarks'] = $data['remarks'];
}
$insterData['channels_uid'] = $request->channels_uid;
$insterData['state'] = State::YES['value'];
$model = new PluginChannelsDomain();
$model->save($insterData);
Redis::hSet('domain_map', $model->domain, $model->channels_uid);
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->success('创建成功');
}
$builder = new FormBuilder(null, null, [
'labelPosition' => 'right',
'label-width' => "200px",
'class' => 'w-80 mx-auto',
'size' => 'large',
]);
$Component = new ComponentBuilder;
$builder->add('verify_type', '验证方式', 'radio', 'file', [
'required' => true,
'options' => [
[
'label' => '文件',
'value' => 'file'
],
[
'label' => 'DNS',
'value' => 'dns'
]
],
'subProps' => [
'border' => true
]
]);
if (!is_dir(runtime_path('temp'))) {
mkdir(runtime_path('temp'), 0755, true);
}
if (!file_exists($file)) {
$content = Str::random(32);
file_put_contents($file, $content);
} else {
$content = file_get_contents($file);
}
$builder->add('domain', '域名(HOST)', 'input', '', [
'required' => true,
'prompt' => [
$Component->add('link', ['default' => '点击下载验证文件'], ['href' => '/app/control/control/Domain/downloadFile?file_name=' . $file_name, 'type' => 'primary', 'size' => 'small', 'target' => '_blank', 'underline' => 'never'])
->add('text', ['default' => "文件验证:将文件 {$file_name} 放置域名根目录下,确保域名+文件名能访问到文件内容:{$content}"], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => "DNS验证:添加一条域名解析记录,记录类型为TXT,记录值为:{$content}"], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入不带协议的域名(HOST),如:www.example.com',
'clearable' => true
]
]);
$builder->add('remarks', '备注', 'input', '', [
'props' => [
'clearable' => true
]
]);
return $this->resData($builder);
}
public function downloadFile(Request $request)
{
$file_name = $request->get('file_name');
$file = runtime_path('temp/') . $file_name;
if (file_exists($file)) {
return response()->download($file, $file_name);
}
return $this->fail('文件不存在');
}
public function verifyDomainRecord($domain, $content)
{
$res = dns_get_record($domain, DNS_ALL);
foreach ($res as $key => $value) {
if ($value['type'] === 'TXT') {
if ($value['txt'] === $content) {
return true;
}
}
}
throw new \Exception('验证失败');
}
public function delete(Request $request)
{
$id = $request->post('id');
$model = $this->model->where(['id' => $id, 'channels_uid' => $request->channels_uid])->find();
if (!$model) {
return $this->fail('数据不存在');
}
if (!$model->delete()) {
return $this->fail('删除失败');
}
Redis::hDel('domain_map', $model->domain);
return $this->success('删除成功');
}
}
@@ -0,0 +1,216 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\DataboardBuilder;
use app\expose\build\builder\databoardBuilder\component\Echarts;
use app\expose\build\builder\databoardBuilder\component\Statistic;
use app\expose\helper\Config;
use plugin\finance\app\model\PluginFinanceOrders;
use plugin\finance\utils\enum\OrdersState;
use plugin\shortplay\app\model\PluginShortplayDrama;
use plugin\user\app\model\PluginUser;
use support\Cache;
use support\Request;
class IndexController extends Basic
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $notNeedLogin = ['index'];
protected $notNeedAuth = ['index'];
public function index(Request $request)
{
$path = $request->path();
# 如果不是以/结尾的,就重定向到以/结尾的URL
if (substr($path, -1) != '/') {
return redirect($path . '/');
}
return view(public_path('index.html'));
}
public function control(Request $request)
{
$builder = new DataboardBuilder([
'gutter' => 6
]);
$today = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time')->count();
$yesterday = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', 'yesterday')->count();
$statistic = new Statistic;
$statistic->setLabel('今日新注册用户')
->setUnit('人')
->setData([
'today' => $today,
'yesterday' => $yesterday,
'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0
])
->setClass('p-6');
$builder->add($statistic, [
'xs' => 24,
'sm' => 12,
'md' => 6,
'lg' => 4,
'class' => 'bg-white p-4 rounded-4 shadow-lighter'
]);
$today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->count();
$yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->count();
$statistic = new Statistic;
$statistic->setLabel('今日支付订单数')
->setUnit('笔')
->setData([
'today' => $today,
'yesterday' => $yesterday,
'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0
])
->setClass('p-6');
$builder->add($statistic, [
'xs' => 24,
'sm' => 12,
'md' => 6,
'lg' => 4,
'class' => 'bg-white p-4 rounded-4 shadow-lighter'
]);
$today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->sum('money');
$yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->sum('money');
$statistic = new Statistic;
$statistic->setLabel('今日支付金额')
->setUnit('元')
->setData([
'today' => $today,
'yesterday' => $yesterday,
'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0
])
->setClass('p-6');
$builder->add($statistic, [
'xs' => 24,
'sm' => 12,
'md' => 6,
'lg' => 4,
'class' => 'bg-white p-4 rounded-4 shadow-lighter'
]);
$today = PluginShortplayDrama::where(['channels_uid' => $request->channels_uid])->whereDay('create_time')->count();
$yesterday = PluginShortplayDrama::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', 'yesterday')->count();
$statistic = new Statistic;
$statistic->setLabel('今日短剧数')
->setUnit('部')
->setData([
'today' => $today,
'yesterday' => $yesterday,
'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0
])
->setClass('p-6');
$builder->add($statistic, [
'xs' => 24,
'sm' => 12,
'md' => 6,
'lg' => 4,
'class' => 'bg-white p-4 rounded-4 shadow-lighter'
]);
$this->echarts($builder);
return $this->resData($builder);
}
private function echarts(DataboardBuilder $builder)
{
$endTime = strtotime('23:59:59', time()) - time();
$request = request();
$echarts = new Echarts;
$echarts->setClass('bg-white p-4 rounded-4 shadow-lighter vh-65');
$EchartsData = Cache::get('control_echarts_data_' . $request->channels_uid);
if (!$EchartsData) {
$EchartsData = $this->getEchartsData($request);
Cache::set('control_echarts_data_' . $request->channels_uid, $EchartsData, $endTime);
}
$echarts->setData($EchartsData);
$builder->add($echarts);
}
private function getEchartsData(Request $request)
{
$data = [
'color' => ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
'title' => [
'text' => '数据可视化'
],
'tooltip' => [
'trigger' => 'axis',
'axisPointer' => [
'type' => 'cross',
'label' => [
'backgroundColor' => '#6a7985'
]
]
],
'legend' => [
'data' => ["用户", "订单数", "支付金额"]
],
'toolbox' => [
'show' => false
],
'grid' => [
'left' => '3%',
'right' => '4%',
'bottom' => '3%',
'containLabel' => true
],
'xAxis' => [
[
'type' => 'category',
'boundaryGap' => false,
'data' => []
]
],
'yAxis' => [
[
'type' => 'value'
]
],
'series' => [
[
'name' => "用户",
'type' => 'line',
'smooth' => true,
'data' => []
],
[
'name' => "订单数",
'type' => 'line',
'smooth' => true,
'data' => []
],
[
'name' => "支付金额",
'type' => 'line',
'smooth' => true,
'data' => []
]
]
];
for ($i = 7; $i > 0; $i--) {
$date = date('Y-m-d', strtotime("-$i day"));
$data['xAxis'][0]['data'][] = $date;
$Statistic = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', $date)->count();
if ($Statistic) {
$data['series'][0]['data'][] = $Statistic;
} else {
$data['series'][0]['data'][] = 0;
}
$Order = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->count();
if ($Order) {
$data['series'][1]['data'][] = $Order;
} else {
$data['series'][1]['data'][] = 0;
}
$Payment = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->sum('money');
if ($Payment) {
$data['series'][2]['data'][] = $Payment;
} else {
$data['series'][2]['data'][] = 0;
}
}
return $data;
}
}
@@ -0,0 +1,157 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\enum\EventName;
use app\expose\utils\Password;
use app\expose\enum\ResponseCode;
use app\expose\enum\State;
use app\expose\helper\Captcha;
use app\expose\helper\Config;
use app\expose\helper\Vcode;
use app\expose\helper\Wechat;
use plugin\control\app\model\PluginChannelsUser;
use Exception;
use plugin\user\expose\helper\User as HelperUser;
use support\Redis;
use support\Request;
use Webman\Event\Event;
class LoginController extends Basic
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $notNeedLogin = ['login', 'vcode', 'qrcode', 'register', 'checkQrcode'];
protected $notNeedAuth = ['login', 'vcode', 'qrcode', 'register', 'checkQrcode'];
public function login(Request $request)
{
try {
$D = $request->post();
$captcha_state = Config::get('captcha', 'state');
if ($captcha_state) {
if (!Captcha::check($D['captcha'], $D['token'])) {
throw new Exception("验证码不正确", ResponseCode::CAPTCHA);
}
}
$where = [];
$where[] = ['mobile|username', '=', $D['username']];
$User = PluginChannelsUser::where($where)->find();
if (!$User) {
throw new Exception('用户不存在');
}
if (!$User->password) {
throw new Exception('当前用户不支持密码登录,请使用验证码登录');
}
if (!password_verify($D['password'], $User->password)) {
throw new Exception('密码错误');
}
if (!$User->state) {
throw new Exception('用户已被禁用');
}
if (!$User->activation_time) {
$User->activation_time = date('Y-m-d H:i:s');
}
$User->login_ip = $request->getRealIp(true);
$User->login_time = date('Y-m-d H:i:s');
if ($User->save()) {
Event::emit(EventName::USER_LOGIN['value'], $User);
return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User));
} else {
throw new Exception("登录失败");
}
} catch (\Throwable $th) {
return $this->exception($th);
}
}
public function vcode(Request $request)
{
$vcode = $request->post('vcode');
$username = $request->post('username');
$token = $request->post('token');
if (!Vcode::check($username, $vcode, 'login', $token)) {
return $this->fail('验证码不正确');
}
$User = PluginChannelsUser::where(['mobile' => $username])->find();
if (empty($User)) {
try {
HelperUser::register([
'mobile' => $username
]);
$User = PluginChannelsUser::where(['mobile' => $username])->find();
} catch (\Throwable $th) {
return $this->exception($th);
}
}
if ($User->state != State::YES['value']) {
return $this->fail('用户已被禁用');
}
if (!$User->activation_time) {
$User->activation_time = date('Y-m-d H:i:s');
}
$User->login_ip = $request->getRealIp(true);
$User->login_time = date('Y-m-d H:i:s');
if ($User->save()) {
Event::emit(EventName::USER_LOGIN['value'], $User);
return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User));
} else {
throw new Exception("登录失败");
}
}
public function register(Request $request)
{
$vcode = $request->post('vcode');
$username = $request->post('username');
$token = $request->post('token');
if (!Vcode::check($username, $vcode, 'register', $token)) {
return $this->fail('验证码不正确');
}
$password = $request->post('password');
try {
HelperUser::register([
'mobile' => $username,
'password' => $password
]);
return $this->success('注册成功');
} catch (\Throwable $th) {
return $this->exception($th);
}
}
public function qrcode(Request $request)
{
try {
$expire = 5 * 60;
$params = [];
if ($request->icode) {
$params['puid'] = HelperUser::getUidByIcode($request->icode);
}
$res = Wechat::createQrCode(Wechat::QR_STR_SCENE, $expire, [\plugin\control\event\WechatOfficialAccount::class, 'login', $params]);
} catch (\Throwable $th) {
return $this->exception($th);
}
$data = [
'id' => $res['id'],
'qrcode' => $res['url'],
'expire' => $res['expire_seconds']
];
return $this->resData($data);
}
public function checkQrcode(Request $request)
{
$id = $request->post('id');
$uid = Redis::get($id . '_callback');
if ($uid) {
$User = PluginChannelsUser::where('id', $uid)->find();
if ($User) {
return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User));
} else {
return $this->fail('登录失败');
}
}
return $this->code(ResponseCode::WAIT);
}
}
@@ -0,0 +1,190 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\PaymentChannels;
use app\expose\enum\Platform;
use app\expose\enum\State;
use app\model\PaymentConfig;
use app\model\PaymentTemplate;
use support\Request;
class PaymentController extends Basic
{
public function __construct()
{
$this->model = new PaymentConfig;
}
public function index(Request $request)
{
$tabs = [];
$PaymentConfig = PaymentConfig::where(['channels_uid' => $request->channels_uid])->select();
$Platform = Platform::getOptions(function($value){
return $value['value'] === Platform::PC['value'];
});
foreach ($Platform as $key => $item) {
$channels = [];
switch ($item['value']) {
case Platform::PC['value']:
$channels = [
PaymentChannels::WXPAY,
// PaymentChannels::ALIPAY,
// PaymentChannels::BALANCE,
// PaymentChannels::INTEGRAL
];
break;
case Platform::H5['value']:
$channels = [
PaymentChannels::WXPAY,
PaymentChannels::ALIPAY,
PaymentChannels::BALANCE,
PaymentChannels::INTEGRAL
];
break;
case Platform::WECHAT_MINIAPP['value']:
$channels = [
PaymentChannels::WXPAY,
PaymentChannels::BALANCE,
PaymentChannels::INTEGRAL
];
break;
case Platform::WECHAT_OFFICIAL_ACCOUNT['value']:
$channels = [
PaymentChannels::WXPAY,
PaymentChannels::BALANCE,
PaymentChannels::INTEGRAL
];
break;
case Platform::APP['value']:
$channels = [
PaymentChannels::WXPAY,
PaymentChannels::ALIPAY,
PaymentChannels::BALANCE,
PaymentChannels::INTEGRAL
];
break;
}
$tabs[] = $this->getFormBuilder($PaymentConfig, $item, $channels, $request);
}
return $this->resData($tabs);
}
private function getFormBuilder($PaymentConfig, $Platform, $channels, $request)
{
$builder = new TableBuilder([
'border' => false,
'stripe' => false
]);
$builder->add('channels', '支付方式', [
'component' => [
'name' => 'table-userinfo',
'props' => [
'nickname' => 'channels_text',
'avatar' => [
'field' => 'channels_icon',
'size' => 40
]
]
],
'props' => []
]);
$builder->add('template_id', '支付模板', [
'component' => [
'name' => 'select',
'api' => '/app/control/control/Payment/indexUpdateField',
'options' => PaymentTemplate::options(['channels_uid' => $request->channels_uid]),
'props' => [
'clearable' => true
]
],
'props' => [
'width' => '300px'
]
]);
$builder->add('state', '是否启用', [
'component' => [
'name' => 'switch',
'api' => '/app/control/control/Payment/indexUpdateState',
'props' => [
'active-value' => State::YES['value'],
'inactive-value' => State::NO['value']
]
],
'props' => [
'width' => '160px'
]
]);
$builder->add('is_default', '是否为默认支付', [
'component' => [
'name' => 'switch',
'api' => '/app/control/control/Payment/indexUpdateState',
'props' => [
'active-value' => State::YES['value'],
'inactive-value' => State::NO['value']
]
],
'props' => [
'width' => '160px'
]
]);
$tableData = [];
$exist = [];
foreach ($PaymentConfig as $item) {
if ($item->platform != $Platform['value']) continue;
$temp = $item->toArray();
$PaymentChannels = PaymentChannels::get($item->channels);
if ($PaymentChannels) {
$temp['channels_text'] = $PaymentChannels['label'];
$temp['channels_icon'] = $PaymentChannels['icon'];
}
$exist[] = $item->channels;
$tableData[] = $temp;
}
foreach ($channels as $key => $channel) {
if (!in_array($channel['value'], $exist)) {
$model = new PaymentConfig;
$model->channels_uid = $request->channels_uid;
$model->platform = $Platform['value'];
$model->channels = $channel['value'];
$model->state = State::NO['value'];
$model->save();
$tableData[] = [
'id' => $model->id,
'channels' => $channel['value'],
'channels_text' => $channel['label'],
'channels_icon' => $channel['icon'],
'template_id' => null,
'state' => State::NO['value'],
'is_default' => State::NO['value']
];
}
}
$data = [
'title' => $Platform['label'],
'name' => $Platform['value'],
'tips' => '在' . $Platform['label'] . '端付款时使用',
'builder' => $builder,
'data' => $tableData
];
return $data;
}
public function indexUpdateState(Request $request)
{
$id = $request->post('id');
$field = $request->post('field');
$value = $request->post('value');
$PaymentConfig = PaymentConfig::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find();
if (!$PaymentConfig) {
return $this->fail('数据不存在');
}
if ($field == 'is_default' && $value == State::YES['value']) {
$PaymentConfig->where(['platform' => $PaymentConfig->platform])->update(['is_default' => State::NO['value']]);
}
$PaymentConfig->{$field} = $value;
if ($PaymentConfig->save()) {
return $this->success();
}
return $this->fail('操作失败');
}
}
@@ -0,0 +1,645 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\Action;
use app\expose\enum\AlipayRsaModel;
use app\expose\enum\PaymentChannels;
use app\expose\enum\State;
use app\expose\enum\WxpayMchType;
use app\expose\enum\WxpayVersion;
use app\model\PaymentTemplate;
use support\Request;
class PaymentTemplateController extends Basic
{
public function indexGetTable(Request $request)
{
$builder = new TableBuilder;
$builder->addAction('操作', [
'width' => '200px',
'fixed' => 'right'
]);
$builder->addTableAction('编辑', [
'path' => '/app/control/control/PaymentTemplate/update',
'props' => [
'type' => 'primary',
'title' => '编辑《{title}》支付模板'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'primary',
'size' => 'small'
]
]
]);
$builder->addTableAction('删除', [
'model' => Action::COMFIRM['value'],
'path' => '/app/control/control/PaymentTemplate/delete',
'props' => [
'type' => 'error',
'message' => '确定要删除《{title}》支付模板吗?',
'confirmButtonClass' => 'el-button--danger'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'danger',
'size' => 'small'
]
]
]);
$builder->addHeader();
$builder->addHeaderAction('新增模板', [
'path' => '/app/control/control/PaymentTemplate/create',
'props' => [
'type' => 'success',
'title' => '新增支付模板'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'success',
'icon' => 'Plus'
]
]
]);
$builder->add('id', 'ID', [
'props' => [
'width' => '80px'
]
]);
$builder->add('title', '模板名称');
$builder->add('channels', '支付方式', [
'component' => [
'name' => 'tag',
'options' => PaymentChannels::getOptions()
],
'props' => [
'width' => '160px'
]
]);
$builder->add('state', '状态', [
'component' => [
'name' => 'switch',
'api' => '/app/control/control/PaymentTemplate/indexUpdateState',
'props' => [
'active-value' => State::YES['value'],
'inactive-value' => State::NO['value']
]
],
'props' => [
'width' => '100px'
]
]);
$builder->add('create_time', '时间', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'create_time',
'label' => '创建'
],
[
'field' => 'update_time',
'label' => '更新'
]
]
]
]
]);
return $this->resData($builder);
}
public function index(Request $request)
{
$limit = $request->get('limit', 10);
$where = [];
$where[] = ['channels_uid', '=', $request->channels_uid];
$list = PaymentTemplate::where($where)->withoutField('value')
->order('id desc')->paginate($limit)->each(function ($item) {});
return $this->resData($list);
}
public function create(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$model = new PaymentTemplate;
$model->channels_uid = $request->channels_uid;
$model->title = $D['title'];
unset($D['title']);
$model->channels = $D['channels'];
unset($D['channels']);
$D['notify_url'] = 'https://' . $request->host();
$model->state = State::YES['value'];
$model->value = $this->getValue($model->channels, $D);
if ($model->save()) {
return $this->success('新增成功');
}
return $this->fail('新增失败');
}
$builder = $this->getFormBuilder();
return $this->resData($builder);
}
public function update(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$model = PaymentTemplate::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find();
if (!$model) {
return $this->fail('数据不存在');
}
unset($D['id']);
$model->title = $D['title'];
unset($D['title']);
$model->channels = $D['channels'];
unset($D['channels']);
$D['notify_url'] = 'https://' . $request->host();
$model->state = State::YES['value'];
$model->value = $this->getValue($model->channels, $D);
if ($model->save()) {
return $this->success('更新成功');
}
return $this->fail('更新失败');
}
$id = $request->get('id');
$model = PaymentTemplate::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find();
if (!$model) {
return $this->fail('数据不存在');
}
$builder = $this->getFormBuilder(true);
$data = [
'id' => $model->id,
'title' => $model->title,
'channels' => $model->channels
];
foreach ($model->value as $key => $value) {
$data[$key] = $value;
}
$builder->setData($data);
return $this->resData($builder);
}
private function getValue($channels, $D)
{
$value = [];
switch ($channels) {
case PaymentChannels::WXPAY['value']:
$value = [
'mch_type' => $D['mch_type'],
'appid' => $D['appid'],
'mch_id' => $D['mch_id'],
'mch_key' => $D['mch_key'],
'ssl_cert' => $D['ssl_cert'],
'ssl_key' => $D['ssl_key'],
'notify_url' => $D['notify_url'],
];
break;
// case PaymentChannels::ALIPAY['value']:
// $value = [
// 'appid' => $D['appid'],
// 'ras_model' => $D['ras_model'],
// 'app_public_cert' => $D['app_public_cert'],
// 'alipay_public_cert' => $D['alipay_public_cert'],
// 'alipay_root_cert' => $D['alipay_root_cert'],
// 'alipay_public_key' => $D['alipay_public_key'],
// 'private_key' => $D['private_key'],
// 'notify_url' => $D['notify_url'],
// ];
// break;
// case PaymentChannels::INTEGRAL['value']:
// $value = [
// 'display_name' => $D['display_name'],
// 'proportion' => $D['proportion'],
// 'is_integer' => $D['is_integer'],
// 'integer' => $D['integer']
// ];
// break;
}
return $value;
}
private function getFormBuilder($update = false)
{
$builder = new FormBuilder(null, '', [
'labelWidth' => '300px',
'labelPosition' => 'right'
]);
$Component = new ComponentBuilder;
$builder->add('title', '模板名称', 'input', null, [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '仅用于后台管理使用,对前台用户不可见;例如:H5-支付宝支付;微信小程序-微信支付'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入模板名称'
]
]);
$builder->add('channels', '支付方式', 'radio', PaymentChannels::WXPAY['value'], [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '保存以后支付方式将不可修改,请谨慎操作,'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信商户平台'], ['type' => 'success', 'href' => 'https://pay.weixin.qq.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small'])
->add('text', ['default' => ''], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '支付宝开放平台'], ['type' => 'primary', 'href' => 'https://alipay.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small'])
->builder()
],
'options' => PaymentChannels::getOptions(function ($item) {
return $item['value'] === PaymentChannels::WXPAY['value'];
}),
'props' => [
'disabled' => $update
],
'subProps' => [
'border' => true
]
]);
# 微信支付开始
/* $builder->add('api_version', '微信支付接口版本', 'radio', WxpayVersion::V3['value'], [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']]
],
'options' => WxpayVersion::getOptions(),
'subProps' => [
'border' => true
]
]); */
$builder->add('mch_type', '微信商户号类型', 'radio', WxpayMchType::NORMAL['value'], [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']]
],
'options' => WxpayMchType::getOptions(function ($item) {
return $item['value'] === WxpayMchType::NORMAL['value'];
}),
'subProps' => [
'border' => true
]
]);
$builder->add('appid', '应用ID (AppID)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::NORMAL['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入应用ID (AppID)'
]
]);
$builder->add('mch_id', '微信商户号 (MchId)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::NORMAL['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入微信商户号 (MchId)'
]
]);
$builder->add('mch_key', '支付密钥 (APIKEY)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::NORMAL['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置APIv3密钥'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入支付密钥 (APIKEY)'
]
]);
$builder->add('ssl_cert', '证书文件 (CERT)', 'bundle', '', [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::NORMAL['value']]
],
'props' => [
'view' => 'file',
'multiple' => 1
]
]);
$builder->add('ssl_key', '证书文件 (KEY)', 'bundle', '', [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::NORMAL['value']]
],
'props' => [
'view' => 'file',
'multiple' => 1
]
]);
# 微信支付结束
# 微信支付服务商模式开始
$builder->add('service_appid', '服务商应用ID (AppID)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'prompt' => [
$Component->add('text', ['default' => '请填写微信支付服务商的AppID'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入服务商应用ID (AppID)'
]
]);
$builder->add('service_mch_id', '服务商户号 (MchId)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信支付服务商的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入服务商户号 (MchId)'
]
]);
$builder->add('service_mch_key', '服务商密钥 (APIKEY)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置API密钥'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入服务商密钥 (APIKEY)'
]
]);
$builder->add('appid', '子商户应用ID (AppID)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入子商户应用ID (AppID)'
]
]);
$builder->add('mch_id', '子商户号 (MchId)', 'input', null, [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'prompt' => [
$Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入子商户号 (MchId)'
]
]);
$builder->add('service_ssl_cert', '服务商证书文件 (CERT)', 'bundle', '', [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'props' => [
'view' => 'file',
'multiple' => 1
]
]);
$builder->add('service_ssl_key', '服务商证书文件 (KEY)', 'bundle', '', [
'required' => true,
'where' => [
['channels', '=', PaymentChannels::WXPAY['value']],
['mch_type', '=', WxpayMchType::SERVICE['value']]
],
'props' => [
'accept' => 'application/octet-stream',
'view' => 'file',
'multiple' => 1
]
]);
# 微信支付服务商模式结束
# 支付宝支付开始
// $builder->add('appid', '支付宝应用 (AppID)', 'input', null, [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '支付宝分配给开发者的应用ID;例如:2021072300007148'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'type' => 'text',
// 'placeholder' => '请输入支付宝应用 (AppID)'
// ]
// ]);
// $builder->add('ras_model', '加签模式', 'radio', AlipayRsaModel::PUBLIC_CERT['value'], [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '如需使用资金支出类的接口,则必须使用公钥证书模式'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'options' => AlipayRsaModel::getOptions(),
// 'subProps' => [
// 'border' => true
// ]
// ]);
// $builder->add('app_public_cert', '应用公钥证书', 'bundle', '', [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']],
// ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '请上传 "appCertPublicKey_xxxxxxxx.crt" 文件'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'view' => 'file',
// 'multiple' => 1
// ]
// ]);
// $builder->add('alipay_public_cert', '支付宝公钥证书', 'bundle', '', [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']],
// ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '请上传 "alipayCertPublicKey_RSA2.crt" 文件'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'view' => 'file',
// 'multiple' => 1
// ]
// ]);
// $builder->add('alipay_root_cert', '支付宝根证书', 'bundle', '', [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']],
// ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '请上传 "alipayRootCert.crt" 文件'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'view' => 'file',
// 'multiple' => 1
// ]
// ]);
// $builder->add('alipay_public_key', '支付宝公钥 (alipayPublicKey)', 'input', '', [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']],
// ['ras_model', '=', AlipayRsaModel::PUBLIC_KEY['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '可在 "支付宝开放平台" - "应用信息" - "接口加签方式" - "支付宝公钥" 中复制'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'type' => 'textarea',
// 'autosize' => [
// 'minRows' => 4,
// 'maxRows' => 6
// ],
// 'placeholder' => '请输入支付宝公钥 (alipayPublicKey)'
// ]
// ]);
// $builder->add('private_key', '应用私钥 (privateKey)', 'input', '', [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::ALIPAY['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '查看 "应用私钥RSA2048-敏感数据,请妥善保管.txt" 文件,将全部内容复制到此处'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'type' => 'textarea',
// 'autosize' => [
// 'minRows' => 4,
// 'maxRows' => 6
// ],
// 'placeholder' => '请输入应用私钥 (privateKey)'
// ]
// ]);
// $builder->add('display_name', '显示名称', 'input', null, [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::INTEGRAL['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '在客户端显示的名称'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'type' => 'text',
// 'placeholder' => '请输入显示名称'
// ]
// ]);
// $builder->add('proportion', '充值比例', 'input-number', null, [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::INTEGRAL['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '1元=多少积分'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'controls' => false,
// 'min' => 1,
// 'max' => 1000000
// ]
// ]);
// $builder->add('is_integer', '整数使用', 'switch', State::NO['value'], [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::INTEGRAL['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '开启后只能使用整数的倍数积分'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'active-value' => State::YES['value'],
// 'inactive-value' => State::NO['value']
// ]
// ]);
// $builder->add('integer', '整数', 'input-number', null, [
// 'required' => true,
// 'where' => [
// ['channels', '=', PaymentChannels::INTEGRAL['value']],
// ['is_integer', '=', State::YES['value']]
// ],
// 'prompt' => [
// $Component->add('text', ['default' => '整数的倍数使用'], ['type' => 'info', 'size' => 'small'])
// ->builder()
// ],
// 'props' => [
// 'controls' => false,
// 'min' => 1,
// 'max' => 1000000
// ]
// ]);
/* $builder->add('notify_url', '支付通知', 'input', null, [
'required' => true,
'where' => [
['channels', 'in', [PaymentChannels::WXPAY['value'], PaymentChannels::ALIPAY['value']]]
],
'prompt' => [
$Component->add('text', ['default' => '只需要填写域名,不需要填写路径,如:https://www.baidu.com'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '支付通知接口域名'
]
]); */
return $builder;
}
}
@@ -0,0 +1,22 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\trait\Config;
use support\Request;
class PlatformController extends Basic
{
use Config;
public function wechat_miniproject(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->builder();
}
public function wechat_official_account(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->builder();
}
}
@@ -0,0 +1,195 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\config\Action;
use app\expose\build\config\Web;
use app\expose\enum\Action as EnumAction;
use app\expose\enum\Filesystem;
use app\expose\enum\State;
use app\expose\helper\Captcha;
use app\expose\helper\Config;
use plugin\control\expose\helper\Control;
use app\expose\helper\Vcode;
use loong\oauth\facade\Auth;
use plugin\control\app\model\PluginChannelsRole;
use support\Request;
class PublicController extends Basic
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $notNeedLogin = ['config', 'menus', 'vcode', 'outLogin', 'unlock'];
protected $notNeedAuth = ['config', 'menus', 'vcode', 'outLogin'];
public function config(Request $request)
{
$lang = $request->lang;
$domain = 'control';
$config = new Config('basic', '');
$config = new Web($config->toArray());
$captcha_config = new Config('captcha', '');
$config->useLogin([
'url' => 'Login/login',
'title' => trans('Login Title', [], $domain, $lang),
'captcha' => $captcha_config->state,
'link_text' => trans('Login Link Text', [], $domain, $lang),
'user_agreement_config' => [
'title' => trans('Login User Agreement Title', [], $domain, $lang),
'label' => trans('Login User Agreement Label', [], $domain, $lang)
]
]);
$config->useVcode([
'url' => 'Login/vcode',
'title' => trans('Vcode Login', [], $domain, $lang)
]);
$config->useRegister([
'url' => 'Login/register',
'title' => trans('Register Title', ['%web_name%' => $config['web_name']], $domain, $lang),
'link_text' => trans('Register Link Text', [], $domain, $lang),
'user_agreement_config' => [
'title' => trans('Register User Agreement Title', [], $domain, $lang),
'label' => trans('Register User Agreement Label', [], $domain, $lang)
]
]);
$wechat_official_account_config = new Config('wechat_official_account', '');
if ($wechat_official_account_config->state && $wechat_official_account_config->message_state) {
$config->useQrcodeLogin([
'url' => 'Login/qrcode',
'check' => 'Login/checkQrcode',
'title' => trans('Qrcode Login', [], $domain, $lang)
]);
}
$config->useApis([
'userinfo' => '/app/control/control/User/getInfo',
'lock' => '/app/control/control/User/lock',
'unlock' => '/app/control/control/Public/unlock',
'menus' => '/app/control/control/Public/menus',
'vcode' => '/app/control/control/Public/vcode',
'outLogin' => '/app/control/control/Public/outLogin',
]);
$toolbar = new Action();
$toolbar->add(EnumAction::LOCK['value'], [
'icon' => 'Lock',
'tips' => trans('toolbar Lock', [], $domain, $lang)
]);
$toolbar->add(EnumAction::SEARCH['value'], [
'icon' => 'Search',
'tips' => trans('toolbar Search', [], $domain, $lang)
]);
$toolbar->add(EnumAction::NOTIFICATION['value'], [
'icon' => 'Notification',
'tips' => trans('toolbar Notification', [], $domain, $lang)
]);
$toolbar->add(EnumAction::FULL_SCREEN['value'], [
'icon' => 'FullScreen',
'tips' => trans('toolbar FullScreen', [], $domain, $lang)
]);
$config->useToolbar($toolbar->toArray());
$userDropdownMenu = new Action();
$userDropdownMenu->add(EnumAction::DIALOG['value'], [
'path' => '/app/control/control/User/update',
'label' => trans('userDropdownMenu updateSelf', [], $domain, $lang),
'icon' => 'User',
'props' => [
'title' => trans('userDropdownMenu updateSelf', [], $domain, $lang)
]
]);
$config->useUserDropdownMenu($userDropdownMenu->toArray());
$config->storage = Filesystem::getOptions(function ($item) {
return !in_array($item['value'], [Filesystem::PUBLIC['value'], Filesystem::LOCAL['value']]);
});
$pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php"));
foreach ($pluginConfig as $path) {
$plugin_name = basename(dirname(dirname(dirname($path))));
if ($plugin_name == 'control') {
continue;
}
$class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController";
if (!class_exists($class)) {
continue;
}
$plugin = new $class;
if (method_exists($plugin, 'config')) {
$plugin->config($config);
}
}
$updateVersionContent = file_get_contents(base_path('update/VERSION'));
$updateVersionArr = explode("\n", $updateVersionContent);
$config->version_name=$updateVersionArr[1];
$config->version=$updateVersionArr[0];
return $this->resData($config);
}
public function menus(Request $request)
{
$Control = new \plugin\control\api\Control;
$menus = new Control($Control);
return $this->resData($menus);
}
// private function filterMenu(array $menus, array $allowPaths): array
// {
// $result = [];
// foreach ($menus as $menu) {
// // 递归处理 children
// if (!empty($menu['children'])) {
// $menu['children'] = $this->filterMenu($menu['children'], $allowPaths);
// }
// // 是否保留当前节点
// $keep = in_array($menu['path'], $allowPaths, true)
// || !empty($menu['children']);
// if ($keep) {
// $result[] = $menu;
// }
// }
// return $result;
// }
public function outLogin(Request $request)
{
$token = $request->header('Authorization');
if ($token) {
try {
Auth::setPrefix('CONTROL')->delete($token);
} catch (\Throwable $th) {
}
}
return $this->success(trans('Logout Success', [], $request->app, $request->lang));
}
public function vcode(Request $request)
{
$username = $request->post('username');
$token = $request->post('token');
$captcha = $request->post('captcha');
if (!Captcha::check($captcha, $token)) {
return $this->fail(trans('Captcha Incorrect', [], $request->app, $request->lang));
}
$scene = $request->post('scene');
if (!$scene) {
return $this->fail(trans('Scene Cannot Be Empty', [], $request->app, $request->lang));
}
try {
Vcode::send($username, $scene, $token);
return $this->success(trans('Vcode Send Success', [], $request->app, $request->lang));
} catch (\Throwable $th) {
return $this->exception($th);
}
}
public function unlock(Request $request)
{
$password = $request->post('password');
try {
$token = $request->header('Authorization');
Auth::setPrefix('CONTROL')->unlock($token, $password);
return $this->success(trans('Unlock Success', [], $request->app, $request->lang));
} catch (\Throwable $th) {
return $this->exception($th);
}
}
}
@@ -0,0 +1,260 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\Action;
use app\expose\enum\ResponseEvent;
use app\expose\enum\State;
use app\expose\helper\Menus;
use app\model\Admin;
use plugin\control\app\model\PluginChannelsRole;
use support\Request;
use plugin\control\expose\helper\Control;
class RoleController extends Basic
{
public function indexGetTable(Request $request)
{
$builder = new TableBuilder([
'rowKey' => 'id',
'api' => '/app/control/Role/index',
'lazy' => true,
'treeProps' => [
'children' => 'children',
'hasChildren' => 'hasChildren'
]
]);
$builder->addAction('操作', [
'width' => '200px',
'fixed' => 'right'
]);
$builder->addTableAction('编辑', [
'path' => '/app/control/control/Role/update',
'where' => [
['is_system', '!=', 1]
],
'props' => [
'type' => 'primary',
'title' => '编辑《{name}》角色'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'primary',
'size' => 'small'
]
]
]);
$builder->addTableAction('删除', [
'model' => Action::COMFIRM['value'],
'path' => 'AdminRole/delete',
'where' => [
['is_system', '!=', 1]
],
'props' => [
'type' => 'error',
'message' => '确定要删除《{name}》角色吗?',
'confirmButtonClass' => 'el-button--danger'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'danger',
'size' => 'small'
]
]
]);
$builder->addHeader();
$builder->addHeaderAction('创建角色', [
'path' => '/app/control/control/Role/create',
'props' => [
'type' => 'success',
'title' => '创建角色'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'success'
]
]
]);
$builder->add('id', 'ID', [
'props' => [
'width' => '80px'
]
]);
$builder->add('name', '角色名称');
$builder->add('admin_num', '人数', [
'props' => [
'width' => '120px'
]
]);
$builder->add('rule_num', '拥有权限数', [
'props' => [
'width' => '120px'
]
]);
$builder->add('create_time', '时间', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'create_time',
'label' => '创建'
],
[
'field' => 'update_time',
'label' => '更新'
]
]
]
]
]);
$builder = $builder->builder();
return $this->resData($builder);
}
public function index(Request $request)
{
$limit = $request->get('limit', 10);
$where = [];
$where[]=['channels_uid','=',$request->channels_uid];
$list = PluginChannelsRole::where($where)
->order('id asc')->paginate($limit)->each(function ($item) {
$item->admin_num = Admin::where(['role_id' => $item->id])->count();
$item->rule_num = 0;
if ($item->is_system) {
$item->rule_num = '所有';
} elseif ($item->rule) {
$item->rule_num = count($item->rule);
}
$item->hasChildren = PluginChannelsRole::where(['pid' => $item->id])->count() > 0;
});
return $this->resData($list);
}
public function indexUpdateState(Request $request)
{
$id = $request->post('id');
$field = $request->post('field');
$value = $request->post('value');
$AdminRole = PluginChannelsRole::where(['id' => $id])->find();
if (!$AdminRole) {
return $this->fail('角色不存在');
}
if ($AdminRole->is_system) {
return $this->fail('系统角色不允许操作');
}
$AdminRole->{$field} = $value;
if ($AdminRole->save()) {
return $this->success();
}
return $this->fail('操作失败');
}
public function delete(Request $request)
{
$id = $request->post('id');
$AdminRole = PluginChannelsRole::where(['id' => $id])->find();
if (!$AdminRole) {
return $this->fail('角色不存在');
}
if ($AdminRole->is_system) {
return $this->fail('系统角色不允许操作');
}
if (PluginChannelsRole::where(['pid' => $id])->count() > 0) {
return $this->fail('请先删除子角色');
}
if (Admin::where(['role_id' => $id])->count() > 0) {
return $this->fail('请先将拥有该角色的用户移除');
}
if ($AdminRole->delete()) {
return $this->success();
}
return $this->fail('操作失败');
}
public function create(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$AdminRole = new PluginChannelsRole;
$AdminRole->name = $D['name'];
$AdminRole->rule = $D['rule'];
$AdminRole->channels_uid=$request->channels_uid;
if ($AdminRole->save()) {
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
}
return $this->fail('保存失败');
}
$builder = $this->getFormBuilder();
return $this->resData($builder);
}
public function update(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$AdminRole = PluginChannelsRole::where(['id' => $D['id']])->find();
if (!$AdminRole) {
return $this->fail('角色不存在');
}
$AdminRole->name = $D['name'];
$AdminRole->rule = $D['rule'];
if ($AdminRole->save()) {
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
}
return $this->fail('保存失败');
}
$id = $request->get('id');
$AdminRole = PluginChannelsRole::where(['id' => $id])->find();
if (!$AdminRole) {
return $this->fail('角色不存在');
}
$builder = $this->getFormBuilder(true);
if (!$AdminRole->rule) {
$AdminRole->rule = [];
}
$builder->setData($AdminRole->toArray());
return $this->resData($builder);
}
private function getFormBuilder($update = false)
{
$builder = new FormBuilder;
$Component = new ComponentBuilder;
$builder->add('name', '角色名称', 'input', '', [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '建议使用公司组织架构为角色名称'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'maxlength' => 30,
'show-word-limit' => true
]
]);
$Control = new \plugin\control\api\Control;
$menus = new Control($Control);
$builder->add('rule', '规则', 'admin-rule', [], [
'required' => true,
'options' => $menus,
'props' => [
'rightDefaultChecked' => [
'Index',
'Index/index',
'Index/control',
'Admin/updateSelf',
'Admin/getSelfInfo',
'Public',
'Public/outLogin'
]
]
]);
return $builder;
}
}
@@ -0,0 +1,326 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\Filesystem;
use app\expose\enum\SubmitEvent;
use app\expose\trait\Config;
use app\model\Config as ModelConfig;
use support\Request;
class SystemController extends Basic
{
use Config;
public function basic(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->builder();
}
public function yidevs(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->builder();
}
public function payment(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->builder();
}
public function sms(Request $request)
{
$this->channels_uid = $request->channels_uid;
return $this->tabsBuilder();
}
public function upload(Request $request)
{
$request = request();
if ($request->method() === 'POST') {
$D = $request->post();
$config = [];
$config['default'] = $D['default'];
$config['max_size'] = (int)$D['max_size'];
foreach ($D['ftp'] as $key => $value) {
$config['storage']['ftp'][$key] = $value;
}
foreach ($D['s3'] as $key => $value) {
if (in_array($key, ['key', 'secret'])) {
$config['storage']['s3']['credentials'][$key] = $value;
} else {
$config['storage']['s3'][$key] = $value;
}
}
foreach ($D['minio'] as $key => $value) {
if (in_array($key, ['key', 'secret'])) {
$config['storage']['minio']['credentials'][$key] = $value;
} else {
$config['storage']['minio'][$key] = $value;
}
}
foreach ($D['oss'] as $key => $value) {
$config['storage']['oss'][$key] = $value;
}
foreach ($D['qiniu'] as $key => $value) {
$config['storage']['qiniu'][$key] = $value;
}
foreach ($D['cos'] as $key => $value) {
$config['storage']['cos'][$key] = $value;
}
$ModelConfig = ModelConfig::where(['group' => 'filesystem', 'channels_uid' => $request->channels_uid])->find();
if (!$ModelConfig) {
$ModelConfig = new ModelConfig;
$ModelConfig->group = 'filesystem';
$ModelConfig->channels_uid = $request->channels_uid;
}
$ModelConfig->value = $config;
$ModelConfig->save();
return $this->success('保存成功');
}
$config = [
'enable' => true,
'default' => Filesystem::OSS['value'],
'max_size' => 1024 * 1024 * 10, //单个文件大小10M
'ext_yes' => [], //允许上传文件类型 为空则为允许所有
'ext_no' => [], // 不允许上传文件类型 为空则不限制
'storage' => [
'ftp' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class,
'host' => 'ftp.example.com',
'username' => 'username',
'password' => 'password',
'url' => '', // 静态文件访问域名
'port' => 21,
'root' => '/path/to/root',
'passive' => true,
'ssl' => true,
'timeout' => 30,
'ignorePassiveAddress' => false,
'timestampsOnUnixListingsEnabled' => true,
],
'memory' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class,
],
's3' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => 'S3_KEY',
'secret' => 'S3_SECRET',
],
'region' => 'S3_REGION',
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => false,
'endpoint' => 'S3_ENDPOINT',
'bucket_name' => 'S3_BUCKET',
'url' => '' // 静态文件访问域名
],
'minio' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => 'S3_KEY',
'secret' => 'S3_SECRET',
],
'region' => 'S3_REGION',
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => true,
'endpoint' => 'S3_ENDPOINT',
'bucket_name' => 'S3_BUCKET',
'url' => '' // 静态文件访问域名
],
'oss' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class,
'accessId' => 'OSS_ACCESS_ID',
'accessSecret' => 'OSS_ACCESS_SECRET',
'bucket' => 'OSS_BUCKET',
'endpoint' => 'OSS_ENDPOINT',
'url' => '', // 静态文件访问域名
'timeout' => 3600,
'connectTimeout' => 10,
'isCName' => false,
'token' => null,
'proxy' => null,
],
'qiniu' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class,
'accessKey' => 'QINIU_ACCESS_KEY',
'secretKey' => 'QINIU_SECRET_KEY',
'bucket' => 'QINIU_BUCKET',
'domain' => 'QINBIU_DOMAIN',
'url' => '' // 静态文件访问域名
],
'cos' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class,
'region' => 'COS_REGION',
'app_id' => 'COS_APPID',
'secret_id' => 'COS_SECRET_ID',
'secret_key' => 'COS_SECRET_KEY',
// 可选,如果 bucket 为私有访问请打开此项
'signed_url' => false,
'bucket' => 'COS_BUCKET',
'read_from_cdn' => false,
'url' => '' // 静态文件访问域名
// 'timeout' => 60,
// 'connect_timeout' => 60,
// 'cdn' => '',
// 'scheme' => 'https',
],
],
];
$ModelConfig = ModelConfig::where(['group' => 'filesystem', 'channels_uid' => $request->channels_uid])->find();
if ($ModelConfig) {
$user = $ModelConfig->value;
foreach ($user as $key => $value) {
if ($key !== 'storage') {
$config[$key] = $value;
}
}
foreach ($config['storage'] as $key => $value) {
if (isset($user['storage'][$key])) {
$config['storage'][$key] = array_merge($config['storage'][$key], $user['storage'][$key]);
}
}
}
$builder = new FormBuilder(null, null, [
'translations' => true,
'submitEvent' => SubmitEvent::SILENT
]);
$defaultOptions = Filesystem::getOptions(function ($item) {
return !in_array($item['value'], [Filesystem::PUBLIC['value'], Filesystem::LOCAL['value']]);
});
$builder->add('default', '默认存储渠道商', 'select', $config['default'], [
'options' => $defaultOptions
]);
$builder->add('max_size', '单个文件大小(字节)', 'input-number', $config['max_size'], [
'props' => [
'min' => 0,
'controls' => false,
'style' => [
'width' => '200px'
]
]
]);
$Component = new ComponentBuilder;
$subBuilder = new FormBuilder('oss', '阿里云存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class);
$subBuilder->add('accessId', 'AccessId', 'input', $config['storage']['oss']['accessId']);
$subBuilder->add('accessSecret', 'AccessSecret', 'input', $config['storage']['oss']['accessSecret']);
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['oss']['bucket']);
$subBuilder->add('endpoint', 'Bucket域名', 'input', $config['storage']['oss']['endpoint']);
$subBuilder->add('isCName', '私有空间', 'switch', $config['storage']['oss']['isCName']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['oss']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
$subBuilder = new FormBuilder('cos', '腾讯云存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class);
$subBuilder->add('app_id', 'Appid', 'input', $config['storage']['cos']['app_id']);
$subBuilder->add('secret_id', 'SecretId', 'input', $config['storage']['cos']['secret_id']);
$subBuilder->add('secret_key', 'SecretKey', 'input', $config['storage']['cos']['secret_key']);
$subBuilder->add('region', 'Region', 'input', $config['storage']['cos']['region']);
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['cos']['bucket']);
$subBuilder->add('read_from_cdn', '从CDN读取', 'switch', $config['storage']['cos']['read_from_cdn']);
$subBuilder->add('signed_url', '私有空间', 'switch', $config['storage']['cos']['signed_url']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['cos']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
$subBuilder = new FormBuilder('qiniu', '七牛存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class);
$subBuilder->add('accessKey', 'AccessKey', 'input', $config['storage']['qiniu']['accessKey']);
$subBuilder->add('secretKey', 'SecretKey', 'input', $config['storage']['qiniu']['secretKey']);
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['qiniu']['bucket']);
$subBuilder->add('domain', 'Domain', 'input', $config['storage']['qiniu']['domain']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['qiniu']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
$subBuilder = new FormBuilder('ftp', 'FTP存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class);
$subBuilder->add('host', 'HOST', 'input', $config['storage']['ftp']['host']);
$subBuilder->add('username', 'UserName', 'input', $config['storage']['ftp']['username']);
$subBuilder->add('password', 'Password', 'input', $config['storage']['ftp']['password']);
$subBuilder->add('port', 'Port', 'input', $config['storage']['ftp']['port']);
$subBuilder->add('root', '目录', 'input', $config['storage']['ftp']['root']);
$subBuilder->add('passive', '被动模式', 'switch', $config['storage']['ftp']['passive']);
$subBuilder->add('ssl', 'SSL', 'switch', $config['storage']['ftp']['ssl']);
$subBuilder->add('timeout', '超时', 'input-number', $config['storage']['ftp']['timeout']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['ftp']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
$subBuilder = new FormBuilder('s3', 'S3存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class);
$subBuilder->add('key', 'KEY', 'input', $config['storage']['s3']['credentials']['key']);
$subBuilder->add('secret', 'Secret', 'input', $config['storage']['s3']['credentials']['secret']);
$subBuilder->add('region', 'Region', 'input', $config['storage']['s3']['region']);
$subBuilder->add('version', 'Version', 'input', $config['storage']['s3']['version']);
$subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['s3']['bucket_endpoint']);
$subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['s3']['use_path_style_endpoint']);
$subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['s3']['endpoint']);
$subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['s3']['bucket_name']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['s3']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
$subBuilder = new FormBuilder('minio', 'Minio存储');
$subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class);
$subBuilder->add('key', 'KEY', 'input', $config['storage']['minio']['credentials']['key']);
$subBuilder->add('secret', 'Secret', 'input', $config['storage']['minio']['credentials']['secret']);
$subBuilder->add('region', 'Region', 'input', $config['storage']['minio']['region']);
$subBuilder->add('version', 'Version', 'input', $config['storage']['minio']['version']);
$subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['minio']['bucket_endpoint']);
$subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['minio']['use_path_style_endpoint']);
$subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['minio']['endpoint']);
$subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['minio']['bucket_name']);
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['minio']['url'], [
'prompt' => [
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => 'http://static.example.com'
]
]);
$builder->addGroupForm($subBuilder);
return $this->resData($builder);
}
}
@@ -0,0 +1,22 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\helper\Config;
use plugin\control\expose\trait\Uploads;
class UploadsController extends Basic
{
use Uploads;
public function __construct()
{
$request = request();
$this->channels_uid = $request->channels_uid;
$this->admin_uid = $request->uid;
$Config = new Config('filesystem', '', $this->channels_uid);
if (empty($Config->toArray())) {
throw new \Exception('请先完善上传配置');
}
}
}
@@ -0,0 +1,385 @@
<?php
namespace plugin\control\app\control\controller;
use app\Basic;
use app\expose\build\builder\FormBuilder;
use app\expose\build\builder\TableBuilder;
use app\expose\enum\Action;
use app\expose\enum\ResponseEvent;
use app\expose\enum\State;
use app\expose\utils\Password;
use app\validate\User as ValidateUser;
use loong\oauth\facade\Auth;
use plugin\control\app\model\PluginChannelsRole;
use plugin\control\app\model\PluginChannelsUser;
use support\Request;
class UserController extends Basic
{
public function indexGetTable(Request $request)
{
$builder = new TableBuilder();
$builder->addAction('操作', [
'width' => '200px',
'fixed' => 'right'
]);
$builder->addTableAction('编辑', [
'model' => Action::DIALOG['value'],
'path' => '/app/control/control/User/updateUser',
'props' => [
'title' => '编辑《{id}》账号'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'primary',
'size' => 'small'
]
]
]);
$builder->addHeader();
$builder->addHeaderAction('创建账号', [
'model' => Action::DIALOG['value'],
'path' => '/app/control/control/User/create',
'props' => [
'title' => '创建账号'
],
'component' => [
'name' => 'button',
'props' => [
'type' => 'success'
]
]
]);
$builder->add('id', 'ID', [
'props' => [
'width' => '80px'
]
]);
$builder->add('userinfo', '用户', [
'component' => [
'name' => 'table-userinfo',
'props' => [
'nickname' => 'nickname',
'avatar' => 'headimg',
'info' => 'username',
'nicknameTags' => [
[
'field' => 'new_text',
'props' => [
'type' => 'success',
'size' => 'small'
]
],
[
'field' => 'week_text',
'props' => [
'type' => 'warning',
'size' => 'small'
]
],
[
'field' => 'month_text',
'props' => [
'type' => 'info',
'size' => 'small'
]
]
]
]
],
'props' => [
'minWidth' => '300px'
]
]);
$builder->add('contact', '联系方式', [
'props' => [
'width' => '280px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'mobile',
'label' => '手机号'
],
[
'field' => 'email',
'label' => '邮箱'
]
]
]
]
]);
$builder->add('login_ip', '登录IP', [
'props' => [
'width' => '130px'
]
]);
$builder->add('role.name', '角色', [
'props' => [
'width' => '100px'
]
]);
$builder->add('role.state', '状态', [
'props' => [
'width' => '100px'
]
]);
$builder->add('sort', '排序(小到大)', [
'props' => [
'width' => '130px'
]
]);
$builder->add('create_time', '时间', [
'props' => [
'width' => '200px'
],
'component' => [
'name' => 'table-times',
'props' => [
'group' => [
[
'field' => 'create_time',
'label' => '创建'
],
[
'field' => 'update_time',
'label' => '更新'
]
]
]
]
]);
$builder = $builder->builder();
return $this->resData($builder);
}
public function index(Request $request)
{
$limit = $request->get('limit', 10);
$where = [];
$where[] = ['channels_uid', '=', $request->channels_uid];
$list = PluginChannelsUser::with('role')->where($where)->paginate($limit);
return $this->resData($list);
}
public function create(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$D['channels_uid'] = $request->channels_uid;
if (empty($D['password'])) {
return $this->fail('密码不能为空');
}
try {
PluginChannelsUser::create($D);
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->success('创建成功');
}
$builder = $this->getForm($request->channels_uid);
return $this->resData($builder);
}
public function updateUser(Request $request)
{
if ($request->method() === 'POST') {
$D = $request->post();
$User = PluginChannelsUser::where(['id' => $D['id']])->find();
if (!$User) {
return $this->fail('用户不存在');
}
if (empty($D['password'])) {
unset($D['password']);
} else {
$password = $D['password'];
}
$User->role_id = $D['role_id'];
$User->username = $D['username'];
$User->nickname = $D['nickname'];
$User->headimg = $D['headimg'];
$User->mobile = $D['mobile'];
$User->email = $D['email'];
$User->password = $password;
if ($User->save()) {
return $this->success('更新成功');
}
return $this->fail('更新失败');
}
$id = $request->get('id');
$User = PluginChannelsUser::where(['id' => $id])->find();
if (!$User) {
return $this->fail('用户不存在');
}
$builder = $this->getForm($request->channels_uid);
unset($User->password);
$builder->setData($User->toArray());
return $this->resData($builder);
}
private function getForm($uid)
{
$list = PluginChannelsRole::field('id as value,name as label')->where('channels_uid', $uid)->where('state', State::YES['value'])->select();
$builder = new FormBuilder;
$builder->add('role_id', '所属角色', 'select', null, [
'options' => $list
]);
$builder->add('username', '账号', 'input', '', [
'props' => [
'maxlength' => 30,
'show-word-limit' => true
]
]);
$builder->add('password', '密码', 'input', '', [
'props' => [
'placeholder' => '不修改密码请留空',
'maxlength' => 30,
'show-word-limit' => true
]
]);
$builder->add('nickname', '昵称', 'input', '', [
'required' => true,
'maxlength' => 30,
'show-word-limit' => true
]);
$builder->add('headimg', '头像', 'bundle', '', [
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]);
$builder->add('mobile', '手机号', 'input', '', [
'props' => [
'maxlength' => 11,
'show-word-limit' => true
]
]);
$builder->add('email', '邮箱', 'input', '', [
'props' => [
'maxlength' => 50,
'show-word-limit' => true
]
]);
return $builder;
}
public function update(Request $request)
{
$id = $request->channels_uid;
if ($request->method() === 'POST') {
$D = $request->post();
$D['id'] = $id;
try {
$validate = new ValidateUser;
$validate->scene('self')->check($D);
} catch (\Throwable $th) {
return $this->exception($th);
}
$User = PluginChannelsUser::where(['id' => $D['id']])->find();
if (!$User) {
return $this->fail('用户不存在');
}
if (!$User->username) {
$User->username = $D['username'];
}
$User->nickname = $D['nickname'];
$User->headimg = $D['headimg'];
$User->mobile = $D['mobile'];
$User->email = $D['email'];
if ($D['password']) {
$User->password = $D['password'];
}
if ($User->save()) {
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
}
return $this->fail('保存失败');
}
$User = PluginChannelsUser::where(['id' => $id])->withoutField('password')->find();
if (!$User) {
return $this->fail('用户不存在');
}
$builder = new FormBuilder();
$builder->add('username', '账号', 'input', '', [
'props' => [
'maxlength' => 30,
'show-word-limit' => true,
'disabled' => $User->username ? true : false
]
]);
$builder->add('password', '密码', 'input', '', [
'props' => [
'placeholder' => '不修改密码请留空',
'maxlength' => 30,
'show-word-limit' => true
]
]);
$builder->add('nickname', '昵称', 'input', '', [
'required' => true,
'maxlength' => 30,
'show-word-limit' => true
]);
$builder->add('headimg', '头像', 'bundle', '', [
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]);
$builder->add('mobile', '手机号', 'input', '', [
'props' => [
'maxlength' => 11,
'show-word-limit' => true
]
]);
$builder->add('email', '邮箱', 'input', '', [
'props' => [
'maxlength' => 50,
'show-word-limit' => true
]
]);
$builder->setData($User->toArray());
return $this->resData($builder);
}
public function getInfo(Request $request)
{
$User = PluginChannelsUser::where(['id' => $request->uid])->withoutField('password')->find();
return $this->resData(PluginChannelsUser::getTokenInfo($User));
}
public function refresh()
{
return $this->event(ResponseEvent::UPDATE_USERINFO, '刷新成功');
}
public function lock(Request $request)
{
try {
$password = $request->post('password');
if (!$password) {
return $this->fail('PIN码不能为空');
}
if (mb_strlen($password) != 6) {
return $this->fail('请输入6位PIN码');
}
$token = $request->header('Authorization');
Auth::setPrefix('CONTROL')->lock($token, $password);
return $this->event(ResponseEvent::UPDATE_USERINFO, '锁定成功');
} catch (\Throwable $th) {
return $this->exception($th);
}
}
}
@@ -0,0 +1,32 @@
<?php
namespace plugin\control\app\controller;
use app\expose\helper\Config;
use app\expose\utils\wechat\OfficialAccount;
use support\Log;
use support\Request;
class WechatOfficialAccountController
{
public function message(Request $request, $channels_uid)
{
$OfficialAccount = new OfficialAccount();
$config = new Config('wechat_official_account', 'control', $channels_uid);
if (!$config->state) {
return 'wechat official account is not enabled';
}
try {
$OfficialAccount->checkSignature($request, $config);
if ($request->method() === 'GET') {
return $request->get('echostr');
}
} catch (\Throwable $th) {
if ($request->method() === 'GET') {
return $th->getMessage();
} else {
}
}
return $OfficialAccount->handle($request, $config);
}
}
@@ -0,0 +1,9 @@
<?php
namespace plugin\control\app\middleware;
use plugin\control\expose\middleware\ControlAuth;
class Auth extends ControlAuth
{
}
@@ -0,0 +1,7 @@
<?php
namespace plugin\control\app\model;
use app\model\Basic;
class PluginChannelsDomain extends Basic {}
@@ -0,0 +1,23 @@
<?php
namespace plugin\control\app\model;
use app\model\Basic;
class PluginChannelsRole extends Basic
{
public function getRuleAttr($value)
{
if (empty($value)) {
return '';
}
return json_decode($value, true);
}
public function setRuleAttr($value)
{
if (empty($value)) {
return '';
}
return json_encode($value, JSON_UNESCAPED_UNICODE);
}
}
@@ -0,0 +1,155 @@
<?php
namespace plugin\control\app\model;
use app\expose\utils\Password;
use app\expose\utils\Rsa;
use app\expose\utils\Str;
use app\model\Basic;
use Exception;
use loong\oauth\facade\Auth;
use plugin\control\expose\helper\Uploads;
use plugin\user\utils\enum\UserPermission;
class PluginChannelsUser extends Basic
{
public function getHeadimgAttr($value, $data)
{
$id = $data['channels_uid'] ?? 0;
return Uploads::url($data['id'], $value);
}
public function setHeadimgAttr($value, $data)
{
$id = $data['channels_uid'] ?? 0;
return Uploads::path($id, $value);
}
public function setPasswordAttr($value)
{
return $value ? Password::encrypt($value) : $value;
}
public static function options($where = [])
{
$models = self::where($where)->field('id,nickname,mobile')->select();
$data = [];
foreach ($models as $item) {
$data[] = [
'label' => $item->nickname,
'value' => $item->id,
'tips' => "UID{$item->id}M{$item->mobile}"
];
}
return $data;
}
public static function genUser($id)
{
$id = (int)$id;
return str_replace(['+', '/'], ['-', '_'], Rsa::encryptNumber($id));
}
public static function getUidByUser($user)
{
return Rsa::decryptNumber(str_replace(['-', '_'], ['+', '/'], $user));
}
public static function getTokenInfo($model, $twofa = false)
{
$request = request();
/* 重组用户信息 */
$User = new \stdClass;
$User->user = self::genUser($model->id);
$User->nickname = $model->nickname;
$User->headimg = $model->headimg;
$User->username = $model->username;
$User->username_time = $model->username_time ? 30 - ceil((time() - strtotime($model->username_time)) / 86400) : 0;
$User->mobile = Str::mask($model->mobile);
$User->email = Str::mask($model->email);
$User->password = $model->password ? 1 : 0;
$User->create_time = $model->create_time;
$User->last_login_time = $model->login_time;
$User->last_login_ip = $model->login_ip;
$User->twofa_state = $model->twofa_state;
$PluginChannelsWechat = PluginChannelsWechat::where(['uid' => $model->id])->field('openid,unionid,mp_openid,nickname,headimg,subscribe')->find();
if ($PluginChannelsWechat) {
$User->wechat = $PluginChannelsWechat;
}
if ($model->channels_uid === null) {
$User->is_system = 1;
$User->permissions = null;
$User->channels_uid = $model->id;
} else {
$role = PluginChannelsRole::where(['id' => $model->role_id, 'channels_uid' => $model->channels_uid])->find();
if (!$role) {
throw new Exception("无权限访问");
}
# 是否为主账号
$User->is_system = 0;
# 不为主账号则所拥有的权限列表
$User->permissions = $role->rule;
}
$pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php"));
foreach ($pluginConfig as $path) {
$plugin_name = basename(dirname(dirname(dirname($path))));
if ($plugin_name == 'user') {
continue;
}
$class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController";
if (!class_exists($class)) {
continue;
}
$plugin = new $class;
if (method_exists($plugin, 'appendUserInfo')) {
$plugin->appendUserInfo($User, $model);
}
}
/* 生成token */
$data = new \stdClass;
$data->uid = $model->id;
$data->username = $model->username;
$data->mobile = $model->mobile;
$data->email = $model->email;
$data->channels_uid = $User->channels_uid;
$data->is_system = $User->is_system;
$data->permissions = $User->permissions;
$data->twofa_state = $model->twofa_state;
if ($twofa) {
if ($twofa === true) {
$data->twofa = [
'expire' => time() + (config('oauth.expire') / 2),
'time' => time(),
];
} else {
$data->twofa = $twofa;
}
}
$User->token = Auth::setPrefix('CONTROL')->encrypt($data);
if ($request->token) {
Auth::setPrefix('CONTROL')->refresh($request->token, 60);
}
return $User;
}
public static function onAfterRead($model)
{
if (empty($model->nickname)) {
if (!empty($model->mobile)) {
$model->nickname = 'FM-' . substr($model->mobile, -4);
} else {
$model->nickname = '未命名的用户';
}
}
}
public static function onBeforeWrite($model)
{
if (empty($model->nickname)) {
if (!empty($model->mobile)) {
$model->nickname = 'FM-' . substr($model->mobile, -4);
} else {
$model->nickname = 'FM-' . Str::random();
}
} else {
$model->nickname = iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname);
}
}
public function role(){
return $this->hasOne(PluginChannelsRole::class, 'id', 'role_id');
}
}
@@ -0,0 +1,15 @@
<?php
namespace plugin\control\app\model;
use app\model\Basic;
class PluginChannelsWechat extends Basic
{
public static function onBeforeWrite($model)
{
if(!empty($model->nickname)){
$model->nickname= iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname);
}
}
}
@@ -0,0 +1,9 @@
<?php
use support\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => false
];
@@ -0,0 +1,3 @@
<?php
return [
];
@@ -0,0 +1,2 @@
<?php
return new Webman\Container;
@@ -0,0 +1,4 @@
<?php
return [
];
@@ -0,0 +1,5 @@
<?php
return [
// '' => app\exception\Handler::class,
];
@@ -0,0 +1,20 @@
<?php
return [
'default' => [
'handlers' => [
[
'class' => Monolog\Handler\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/article.log',
7,
\Monolog\Level::Debug,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];
@@ -0,0 +1,19 @@
<?php
return [
'' => [
app\middleware\Template::class,
app\middleware\Platform::class
],
'control' => [
plugin\control\expose\middleware\ControlAuth::class
],
'admin' => [
app\expose\middleware\AdminAuth::class
],
'api' => [
plugin\control\expose\middleware\DomainMapping::class,
plugin\user\expose\middleware\UserAuth::class,
plugin\user\expose\middleware\Twofa::class,
]
];
@@ -0,0 +1,5 @@
<?php
return [
];
@@ -0,0 +1,70 @@
<?php
use Webman\Route;
Route::get('/control', [plugin\control\app\control\controller\IndexController::class, 'index']);
Route::get('/control/', [plugin\control\app\control\controller\IndexController::class, 'index']);
$controllersClass = glob(base_path('plugin/control') . '/app/control/controller/*Controller.php');
$len = count($controllersClass);
$routes = [];
for ($i = 0; $i < $len; $i++) {
$value = $controllersClass[$i];
$controllerName = str_replace('Controller', '', basename($value, '.php'));
$classStr = str_replace('.php', '', substr($value, strlen(base_path()) + 1));
$classStr = str_replace('/', '\\', $classStr);
$reflection = new \ReflectionClass('\\' . $classStr);
// 忽略抽象类、接口
if ($reflection->isAbstract() || $reflection->isInterface()) {
continue;
}
$actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
$actionsLen = count($actions);
for ($n = 0; $n < $actionsLen; $n++) {
$name = $actions[$n]->name;
if (!str_starts_with($name, '__')) {
$routes["/{$controllerName}/{$name}"] = [$classStr, $name];
}
}
}
$controllersVersionClass = glob(base_path('plugin/control') . '/app/control/controller/**/*Controller.php');
if (!empty($controllersVersionClass)) {
$len = count($controllersVersionClass);
for ($i = 0; $i < $len; $i++) {
$value = $controllersVersionClass[$i];
$version = basename(dirname($value));
$controllerName = str_replace('Controller', '', basename($value, '.php'));
$classStr = str_replace('.php', '', substr($value, strlen(base_path()) + 1));
$classStr = str_replace('/', '\\', $classStr);
$reflection = new \ReflectionClass('\\' . $classStr);
// 忽略抽象类、接口
if ($reflection->isAbstract() || $reflection->isInterface()) {
continue;
}
$actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
$actionsLen = count($actions);
for ($n = 0; $n < $actionsLen; $n++) {
$name = $actions[$n]->name;
if (!str_starts_with($name, '__')) {
$routes["/{$version}/{$controllerName}/{$name}"] = [$classStr, $name];
}
}
}
}
Route::group('/control', function () use ($routes) {
foreach ($routes as $key => $value) {
if (in_array($value[1], ['indexUpdateField', 'indexUpdateState'])) {
Route::post($key, $value);
} else if (strpos($value[1], 'GetTable')) {
Route::get($key, $value);
} else {
Route::any($key, $value);
}
}
});
Route::any('/app/control/WechatOfficialAccount/message/{channels_uid}', [plugin\control\app\controller\WechatOfficialAccountController::class, 'message']);
@@ -0,0 +1,272 @@
<?php
use app\expose\build\builder\ComponentBuilder;
use app\expose\enum\SmsChannels;
use plugin\user\utils\enum\VcodeScene;
$Component = new ComponentBuilder;
$vcode_templates=[];
$options=VcodeScene::getOptions();
foreach ($options as $key => $value) {
$prompt=$Component->add("text", ["default" => "短信模板ID,变量:"], ["type" => "info", "size" => "small"]);
foreach ($value['variable'] as $k => $v) {
$prompt->add("tag", ["default" => $v.''.$k], ["type" => "info", "size" => "small"]);
}
$vcode_templates[]=[
'title' => $value['label'].'短信模板ID',
'field' => 'vcode_template_'.$value['value'],
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
"prompt" => [
$prompt->builder()
]
]
];
}
return [
[
'title' => '服务商',
'field' => 'channels',
'value' => [SmsChannels::ALIYUN['value'], SmsChannels::TENCENT['value'], SmsChannels::SMSBAO['value']],
'component' => 'drag-sort',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '短信服务商优先级,当发送失败时,会自动切换到下一个服务商'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'options' => SmsChannels::getOptions(),
]
],
'group' => [
[
'title' => SmsChannels::ALIYUN['label'],
'name' => SmsChannels::ALIYUN['value'],
'children' => [
[
'title' => '开关',
'field' => 'enable',
'value' => false,
'component' => 'switch',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '是否启用阿里云短信服务'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
'title' => 'AccessKey ID',
'field' => 'access_key_id',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey ID'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '阿里云AccessKey'], ['target' => '_blank', 'href' => 'https://ram.console.aliyun.com/manage/ak', 'type' => 'info', 'underline' => 'never', 'size' => 'small'])
->builder()
]
]
],
[
'title' => 'AccessKey Secret',
'field' => 'access_secret',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey Secret'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
'title' => '短信签名',
'field' => 'sign_name',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '阿里云-短信服务-国内消息-签名管理,'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '阿里云短信签名'], ['target' => '_blank', 'href' => 'https://dysms.console.aliyun.com/domestic/text', 'type' => 'info', 'underline' => 'never', 'size' => 'small'])
->builder()
]
]
],
...$vcode_templates
]
],
[
"title" => SmsChannels::TENCENT['label'],
"name" => SmsChannels::TENCENT['value'],
"children" => [
[
'title' => '开关',
'field' => 'enable',
'value' => false,
'component' => 'switch',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '是否启用腾讯云短信服务'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
"title" => "SecretId",
"field" => "secret_id",
"value" => "",
"component" => "input",
"extra" => [
"required" => true,
'where' => [
['enable', '=', true]
],
"prompt" => [
$Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey ID"], ["type" => "info", "size" => "small"])
->add("link", ["default" => "腾讯云SecretId"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"])
->builder()
]
]
],
[
"title" => "SecretKey",
"field" => "secret_key",
"value" => "",
"component" => "input",
"extra" => [
"required" => true,
'where' => [
['enable', '=', true]
],
"prompt" => [
$Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey Secret"], ["type" => "info", "size" => "small"])
->add("link", ["default" => "腾讯云SecretKey"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"])
->builder()
]
]
],
[
"title" => "应用 ID",
"field" => "appid",
"value" => "",
"component" => "input",
"extra" => [
"required" => true,
'where' => [
['enable', '=', true]
],
"prompt" => [
$Component->add("text", ["default" => "腾讯云-短信-应用管理-应用 ID"], ["type" => "info", "size" => "small"])
->add("link", ["default" => "腾讯云应用 ID"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/app-manage", "type" => "info", 'underline' => 'never', "size" => "small"])
->builder()
]
]
],
[
"title" => "短信签名",
"field" => "sign_name",
"value" => "",
"component" => "input",
"extra" => [
"required" => true,
'where' => [
['enable', '=', true]
],
"prompt" => [
$Component->add("text", ["default" => "腾讯云-短信-国内短信-签名管理,"], ["type" => "info", "size" => "small"])
->add("link", ["default" => "腾讯云短信签名"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/csms-sign", "type" => "info", 'underline' => 'never', "size" => "small"])
->builder()
]
]
],
...$vcode_templates
]
],
[
"title" => SmsChannels::SMSBAO['label'],
"name" => SmsChannels::SMSBAO['value'],
"children" => [
[
'title' => '开关',
'field' => 'enable',
'value' => false,
'component' => 'switch',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '是否启用短信宝短信服务'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
'title' => 'UserName',
'field' => 'key',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '短信宝-用户名'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
'title' => 'API Key或密码',
'field' => 'secret',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '短信宝-API Key或密码'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
[
'title' => '短信签名',
'field' => 'sign_name',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['enable', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '短信宝-短信签名'], ['type' => 'info', 'size' => 'small'])
->builder()
]
]
],
...$vcode_templates
]
]
],
];
@@ -0,0 +1,219 @@
<?php
use app\expose\build\builder\ComponentBuilder;
use app\expose\build\builder\SoltBuilder;
$Component = new ComponentBuilder;
return [
[
'title' => 'Settings_basic_web_name',
'field' => 'web_name',
'value' => 'FastMovie',
'component' => 'input',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '应用名称并非网页标题'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入应用名称'
]
]
],
[
'title' => '网站标题',
'field' => 'web_title',
'value' => 'FastMovie',
'component' => 'input',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '网页标题,显示于浏览器tab标题'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入网站标题'
]
]
],
[
'title' => 'LOGO展示方式',
'field' => 'logo_use',
'value' => 'svg',
'component' => 'radio',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '应用名称将会显示在页面标题中'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'options' => [
['label' => '默认', 'value' => 'svg'],
['label' => '图片', 'value' => 'image']
]
]
],
[
'title' => 'LOGO(浅色)',
'field' => 'web_logo_light',
'value' => '',
'component' => 'bundle',
'extra' => [
'where' => [
['logo_use', '=', 'image']
],
'prompt' => [
$Component->add('text', ['default' => '浅色LOGO,用于深色背景'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]
],
[
'title' => 'LOGO(深色)',
'field' => 'web_logo_dark',
'value' => '',
'component' => 'bundle',
'extra' => [
'where' => [
['logo_use', '=', 'image']
],
'prompt' => [
$Component->add('text', ['default' => '深色LOGO,用于浅色背景'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]
],
[
'title' => 'ICP备案号',
'field' => 'web_icp',
'value' => '',
'component' => 'input',
'extra' => [
'props' => [
'type' => 'text',
'placeholder' => '请输入ICP备案号'
]
]
],
[
'title' => '公安备案号',
'field' => 'web_mps',
'value' => '',
'component' => 'input',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '用于跳转至公安备案系统携带的备案号'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入公安备案号'
],
'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px'])
]
],
[
'title' => '公安备案号文本',
'field' => 'web_mps_text',
'value' => '',
'component' => 'input',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '展示在网站底部公安备案号'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入公安备案号文本'
],
'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px'])
]
],
[
'title' => '客服链接',
'field' => 'wechat_url',
'value' => '',
'component' => 'input',
'extra' => [
'props' => [
'type' => 'text',
'placeholder' => '请输入客服链接链接'
]
]
],
[
'title' => '使用教程链接',
'field' => 'guide_url',
'value' => '',
'component' => 'input',
'extra' => [
'props' => [
'type' => 'text',
'placeholder' => '请输入使用教程链接'
]
]
],
[
'title' => '客服二维码',
'field' => 'wechat_qrcode_url',
'value' => '',
'component' => 'bundle',
'extra' => [
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]
],
[
'title' => '微信群二维码',
'field' => 'wechat_group_qrcode_url',
'value' => '',
'component' => 'bundle',
'extra' => [
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]
],
[
'title' => '登录背景图',
'field' => 'login_background_image_url',
'value' => '',
'component' => 'bundle',
'extra' => [
'props' => [
'accept' => 'image/*',
'multiple' => 1
]
]
],
[
'title' => '项目背景视频',
'field' => 'project_background_video_url',
'value' => '',
'component' => 'bundle',
'extra' => [
'prompt' => [
$Component->add('text', ['default' => '项目背景视频,用于展示在网站首页,建议使用webm格式'], ['type' => 'success', 'size' => 'small'])
->builder()
],
'props' => [
'accept' => 'video/*',
'multiple' => 1
]
]
]
];
@@ -0,0 +1,24 @@
<?php
use app\expose\build\builder\ComponentBuilder;
$Component = new ComponentBuilder;
return [
[
'title' => '状态',
'field' => 'state',
'value' => 0,
'component' => 'radio',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '是否开启内置Control'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'options' => [
['label' => '关闭', 'value' => 0],
['label' => '开启', 'value' => 1]
]
]
],
];
@@ -0,0 +1,138 @@
<?php
use app\expose\build\builder\ComponentBuilder;
$Component = new ComponentBuilder;
return [
[
'title' => '开关',
'field' => 'state',
'value' => false,
'component' => 'switch',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '是否开启微信小程序'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => []
]
],
[
'title' => 'AppID',
'field' => 'app_id',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序AppID(小程序ID)'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入AppID'
]
]
],
[
'title' => 'AppSecret',
'field' => 'app_secret',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序AppSecret(小程序密钥) '], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入AppSecret'
]
]
],
[
'title' => '小程序代码上传密钥',
'field' => 'code_upload_key',
'value' => '',
'component' => 'bundle',
'extra' => [
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序代码上传密钥 ,'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small'])
->builder()
],
'props' => [
'view' => 'file'
]
]
],
[
'title' => 'request合法域名',
'field' => 'request_domain',
'value' => '{DOMAIN}',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序request合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-request合法域名'], ['type' => 'warning', 'size' => 'small'])->builder()
],
'props' => [
'placeholder' => '请输入request合法域名'
]
]
],
[
'title' => 'uploadFile合法域名',
'field' => 'upload_file_domain',
'value' => '{DOMAIN}',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序uploadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-uploadFile合法域名'], ['type' => 'warning', 'size' => 'small'])->builder()
],
'props' => [
'placeholder' => '请输入uploadFile合法域名'
]
]
],
[
'title' => 'downloadFile合法域名',
'field' => 'download_file_domain',
'value' => '{DOMAIN}',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信小程序downloadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-downloadFile合法域名,'], ['type' => 'warning', 'size' => 'small'])
->add('text', ['default' => '注:如若该域名与本站系统-上传配置-中储存域名不一致时,以上传配置中使用的域名为准'], ['type' => 'danger', 'size' => 'small'])->builder()
],
'props' => [
'placeholder' => '请输入downloadFile合法域名'
]
]
],
];
@@ -0,0 +1,142 @@
<?php
use app\expose\build\builder\ComponentBuilder;
use app\expose\utils\Str;
$Component = new ComponentBuilder;
return [
[
'title' => '开关',
'field' => 'state',
'value' => false,
'component' => 'switch',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '是否开启微信公众号'], ['type' => 'info', 'size' => 'small'])
->builder()
],
'props' => [
]
]
],
[
'title' => 'AppID',
'field' => 'app_id',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信公众号开发者ID(AppID)'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入AppID'
]
]
],
[
'title' => 'AppSecret',
'field' => 'app_secret',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信公众号开发者密码(AppSecret)'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入AppSecret'
]
]
],
[
'title' => '接收公众号消息通知状态',
'field' => 'message_state',
'value' => false,
'component' => 'switch',
'extra' => [
'required' => true,
'where' => [
['state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '是否开启接收公众号消息通知'], ['type' => 'info', 'size' => 'small'])->builder()
],
'props' => [
]
]
],
[
'title' => '服务器地址',
'field' => 'url',
'value' => '{DOMAIN}/app/control/WechatOfficialAccount/message/{CHANNELS_UID}',
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true],
['message_state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信公众号服务器地址(URL),请将此URL填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-服务器地址(URL)'], ['type' => 'warning', 'size' => 'small'])->builder()
],
'props' => [
'placeholder' => '请输入服务器地址'
]
]
],
[
'title' => '令牌',
'field' => 'token',
'value' => Str::random(32),
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true],
['message_state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信公众号令牌(Token),请将此Token填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-令牌(Token)'], ['type' => 'warning', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入令牌'
]
]
],
[
'title' => '消息加解密密钥',
'field' => 'aes_key',
'value' => Str::random(43),
'component' => 'input',
'extra' => [
'required' => true,
'where' => [
['state', '=', true],
['message_state', '=', true]
],
'prompt' => [
$Component->add('text', ['default' => '微信公众号消息加解密密钥(AESKey),请将此AESKey填入:'], ['type' => 'info', 'size' => 'small'])
->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-消息加解密方式(AESKey)'], ['type' => 'warning', 'size' => 'small'])
->builder()
],
'props' => [
'placeholder' => '请输入消息加解密密钥'
]
]
]
];
@@ -0,0 +1,25 @@
<?php
use app\expose\build\builder\ComponentBuilder;
$Component = new ComponentBuilder;
return [
[
'title' => '壹定开放平台密钥',
'field' => 'token',
'value' => '',
'component' => 'input',
'extra' => [
'required' => true,
'prompt' => [
$Component->add('text', ['default' => '壹定开放平台密钥,用于获取壹定开放平台数据。获取方式:在壹定开放平台->API服务->'], ['type' => 'info', 'size' => 'small'])
->add('link', ['default' => '密钥'], ['href'=>'https://api.yidevs.com/?icode=V7hN2xiudv5qeISzYiklLFFTMElweG44dmwzTEp4K3Mwa3ZGelE9PQ%3D%3D','type' => 'primary', 'size' => 'small', 'target' => '_blank', 'underline' => 'never'])
->builder()
],
'props' => [
'type' => 'text',
'placeholder' => '请输入壹定开放平台密钥'
]
]
],
];
@@ -0,0 +1,25 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Multilingual configuration
*/
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => base_path() . '/plugin/control/resource/translations',
];
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\view\Raw;
use support\view\Twig;
use support\view\Blade;
// use support\view\ThinkPHP;
use support\ThinkPHP;
return [
'handler' => ThinkPHP::class,
];
+235
View File
@@ -0,0 +1,235 @@
[
{
"title": "首页",
"icon": "House",
"path": "/app/control/control",
"sort": 0,
"children": [
{
"title": "控制台",
"icon": "ElementPlus",
"path": "/app/control/control/Index/control",
"component": "databoard"
}
]
},
{
"title": "Systems",
"icon": "Setting",
"path": "/app/control/control/System",
"children": [
{
"title": "Basic Settings",
"icon": "Setting",
"path": "/app/control/control/System/basic",
"component": "form"
},
{
"title": "壹定开放平台",
"icon": "Setting",
"path": "/app/control/control/System/yidevs",
"component": "form"
},
{
"title": "Upload Settings",
"icon": "UploadFilled",
"path": "/app/control/control/System/upload",
"component": "form"
},
{
"title": "SMS Settings",
"icon": "Message",
"path": "/app/control/control/System/sms",
"component": "form"
},
{
"title": "域名管理",
"icon": "Link",
"path": "/app/control/control/Domain/index",
"component": "table",
"children": [
{
"title": "绑定域名",
"path": "/app/control/control/Domain/create",
"component": "form",
"show": false
},
{
"title": "编辑域名",
"path": "/app/control/control/Domain/update",
"component": "form",
"show": false
}
]
},
{
"title": "Payment Management",
"icon": "Wallet",
"path": "/app/control/control/Payment/group",
"children": [
{
"title": "Payment Settings",
"path": "/app/control/control/Payment/index",
"component": "payment-form"
},
{
"title": "Payment Template",
"path": "/app/control/control/PaymentTemplate/index",
"component": "table",
"children": [
{
"title": "Create Payment Template",
"path": "/app/control/control/PaymentTemplate/create",
"show": false,
"component": "form"
},
{
"title": "Edit Payment Template",
"path": "/app/control/control/PaymentTemplate/update",
"show": false,
"component": "form"
},
{
"title": "Delete Payment Template",
"path": "/app/control/control/PaymentTemplate/delete",
"show": false,
"component": ""
}
]
}
]
},
{
"title": "Platform Management",
"icon": "Monitor",
"path": "/app/control/control/Platform/index",
"children": [
{
"title": "微信",
"path": "/app/control/control/Platform/wechat",
"children": [
{
"title": "WeChat Official Account",
"path": "/app/control/control/Platform/wechat_official_account",
"component": "form"
}
]
}
]
},
{
"title": "权限管理",
"icon": "Setting",
"path": "/app/control/control/Permission",
"component": "table",
"children": [
{
"title": "用户列表",
"path": "/app/control/control/User/index",
"component": "table",
"children": [
{
"title": "创建用户",
"path": "/app/control/control/User/create",
"component": "form",
"show": false
},
{
"title": "编辑用户",
"path": "/app/control/control/User/updateUser",
"component": "form",
"show": false
},
{
"title": "删除用户",
"path": "/app/control/control/User/delete",
"component": "form",
"show": false
}
]
},
{
"title": "角色管理",
"path": "/app/control/control/Role/index",
"component": "table",
"children": [
{
"title": "创建角色",
"path": "/app/control/control/Role/create",
"component": "form",
"show": false
},
{
"title": "编辑角色",
"path": "/app/control/control/Role/update",
"component": "form",
"show": false
},
{
"title": "删除角色",
"path": "/app/control/control/Role/delete",
"component": "form",
"show": false
}
]
}
]
}
]
},
{
"title": "公共权限",
"path": "/app/control/control/Public",
"show": false,
"children": [
{
"title": "更新用户信息",
"path": "/app/control/control/User/update",
"show": false,
"component": "form"
},
{
"title": "获取自己的信息",
"show": false,
"path": "/app/control/control/User/getInfo",
"component": ""
},
{
"title": "退出登录",
"show": false,
"path": "/app/control/control/Public/outLogin",
"component": ""
},
{
"title": "获取上传分类",
"path": "/app/control/control/Uploads/getUploadClassify",
"show": false,
"component": ""
},
{
"title": "上传文件",
"path": "/app/control/control/Uploads/upload",
"show": false,
"component": ""
},
{
"title": "删除上传文件",
"path": "/app/control/control/Uploads/deleteUploads",
"show": false,
"component": ""
},
{
"title": "编辑上传文件",
"path": "/app/control/control/Uploads/updateUploads",
"show": false,
"component": ""
},
{
"title": "获取上传文件列表",
"path": "/app/control/control/Uploads/getList",
"show": false,
"component": ""
}
]
}
]
@@ -0,0 +1,69 @@
<?php
namespace plugin\control\event;
use app\expose\enum\EventName;
use app\expose\helper\Uploads;
use app\expose\utils\wechat\modules\Reply;
use plugin\control\app\model\PluginChannelsUser;
use plugin\control\app\model\PluginChannelsWechat;
use support\Log;
use support\Redis;
use think\facade\Db;
use Webman\Event\Event;
class WechatOfficialAccount
{
use Reply;
public function login($data)
{
Db::startTrans();
try {
$UserWechat = PluginChannelsWechat::where(['openid' => $data['FromUserName']])->find();
if ($UserWechat) {
if ($UserWechat->uid) {
$User = PluginChannelsUser::where('id', $UserWechat->uid)->find();
Redis::set($data['EventKey'] . '_callback', $User->id, 'EX', 60);
Db::commit();
Event::emit(EventName::USER_LOGIN['value'], $User);
return $this->replyText($data, '欢迎登录:' . $User->nickname);
}
$User = new PluginChannelsUser();
if ($UserWechat->nickname) {
$User->nickname = $UserWechat->nickname;
}
if ($UserWechat->headimg) {
try {
$User->headimg = Uploads::download($UserWechat->headimg);
} catch (\Throwable $th) {
}
}
if (isset($data['params']) && isset($data['params']['puid'])) {
$User->puid = $data['params']['puid'];
}
$User->activation_time = date('Y-m-d H:i:s');
$User->save();
} else {
$User = new PluginChannelsUser();
if (isset($data['params']) && isset($data['params']['puid'])) {
$User->puid = $data['params']['puid'];
}
$User->activation_time = date('Y-m-d H:i:s');
$User->save();
$UserWechat = new PluginChannelsWechat();
$UserWechat->openid = $data['FromUserName'];
}
$UserWechat->subscribe = 1;
$UserWechat->uid = $User->id;
$UserWechat->save();
Redis::set($data['EventKey'] . '_callback', $User->id, 'EX', 60);
Db::commit();
} catch (\Throwable $th) {
Db::rollback();
Log::error('微信扫码登录失败:' . $th->getMessage(), $th->getTrace());
return $this->replyText($data, '登录失败');
}
// Event::emit(EventName::USER_REGISTER['value'], $User);
return $this->replyText($data, '注册成功');
}
}
@@ -0,0 +1,56 @@
<?php
namespace plugin\control\expose\api;
trait Control
{
protected $path;
/**
* 多语言必须在构造函数中设置plugin值为插件名
*/
protected $plugin;
/**
* 获取菜单
*
* @return array|mixed
*/
public function getMenus()
{
clearstatcache();
# 当前类的运行目录
if (is_file($menu_file = $this->path . '/../control.json')) {
return json_decode(file_get_contents($menu_file), true);
}
return false;
}
public function getPermissions(){
$menus = $this->getMenus();
$permissions = [];
if($menus){
$this->getPath($menus,$permissions);
}
return $permissions;
}
public function getPath($arr,&$data)
{
if(isset($arr['path'])){
$data[] = $arr['path'];
if(isset($arr['children'])){
$this->getPath($arr['children'],$data);
}
}else{
foreach ($arr as $key => $value) {
if (isset($value['path'])) {
$data[] = $value['path'];
}
if (isset($value['children'])) {
$this->getPath($value['children'],$data);
}
}
}
}
public function getPlugin()
{
return $this->plugin;
}
}
@@ -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'];
}
}
@@ -0,0 +1,127 @@
<?php
namespace plugin\control\expose\middleware;
use app\expose\enum\ResponseCode;
use app\expose\helper\Config;
use app\expose\trait\Json;
use Exception;
use loong\oauth\exception\LockException;
use loong\oauth\exception\TokenExpireException;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
use loong\oauth\facade\Auth;
use support\Log;
/**
* 中台应用必须继承此中间件,否者无法正常访问
*/
class ControlAuth implements MiddlewareInterface
{
use Json;
public function process(Request $request, callable $next): Response
{
if ($request->method() == 'OPTIONS') {
return response('', 204);
}
// 鉴权检测
try {
$this->Icode($request);
$this->Authorization($request);
$response = $next($request);
} catch (\Throwable $th) {
if ($request->expectsJson()) {
$response = $this->exception($th);
} else {
if (config('app.debug')) {
Log::error($th->getTraceAsString());
throw $th;
} else {
$response = response($th->getMessage(), 500);
}
}
}
return $response;
}
public function Icode(Request $request)
{
$icode = $request->header('x-icode');
if (!$icode) {
return true;
}
$request->icode = $icode;
}
/**
* 业务逻辑
* @author 贵州猿创科技有限公司
* @Email 416716328@qq.com
* @DateTime 2023-03-11
*/
public function Authorization(Request $request)
{
# 无控制器地址
if (!$request->controller) {
return true;
}
# 获取控制器鉴权信息
$controller = new \ReflectionClass($request->controller);
$properties = $controller->getDefaultProperties();
# 无需登录方法
$notNeedLogin = $properties['notNeedLogin'] ?? [];
# 是否强制登录
$isForceLogin = true;
if (in_array($request->action, $notNeedLogin)) {
$isForceLogin = false;
}
try {
# 令牌验证
$token = $request->header('Authorization');
if (!$token) {
throw new Exception('请先登录', ResponseCode::NEED_LOGIN);
}
$user = Auth::setPrefix('CONTROL')->decrypt($token);
if (!$user) {
throw new TokenExpireException();
}
$request->uid = $user['uid'];
$request->channels_uid = $user['channels_uid'];
$request->token = $token;
} catch (TokenExpireException $e) {
if ($isForceLogin) {
throw new Exception('登录已过期,请重新登录', ResponseCode::NEED_LOGIN);
}
} catch (LockException $e) {
if ($isForceLogin) {
throw new Exception($e->getMessage(), ResponseCode::LOCK);
}
} catch (\Throwable $th) {
if ($isForceLogin) {
throw new \Exception($th->getMessage(), ResponseCode::NEED_LOGIN);
}
}
if (!$isForceLogin) {
return true;
}
if ($user['is_system']) {
return true;
}
if (empty($user['permissions'])) {
throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION);
}
$plugin = $request->plugin;
// plugin\api\app\admin\controller\IndexController,只取Index
$controllers = explode('\\', $request->controller);
$controller = str_replace('Controller', '', end($controllers));
$action = str_replace(['GetTable', 'GetGrid'], ['', ''], $request->action);
$rule = $controller . '/' . $action;
if ($plugin) {
$app = $request->app;
$rule = "/app/{$plugin}/{$app}/{$rule}";
}
if (!in_array($rule, $user['permissions'])) {
throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION);
}
}
}
@@ -0,0 +1,80 @@
<?php
namespace plugin\control\expose\middleware;
use app\expose\enum\State;
use plugin\control\app\model\PluginChannelsDomain;
use plugin\control\utils\LRUCache;
use support\Redis;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class DomainMapping implements MiddlewareInterface
{
private static ?LRUCache $cache = null;
public function __construct()
{
if (!self::$cache) {
self::$cache = new LRUCache(2000);
}
}
public function process(Request $request, callable $next): Response
{
$headersKeys=['Authorization','X-ICODE','X-Platform','lang','X-developer','content-type'];
// 1. 优先 Origin
if ($origin = $request->header('origin')) {
$domain = parse_url($origin, PHP_URL_HOST);
}
// 2. 再尝试 Referer
elseif ($referer = $request->header('referer')) {
$domain = parse_url($referer, PHP_URL_HOST);
} elseif ($request->host()) {
$domain = $request->host();
} else {
return response('Domain not found', 401);
}
$corsHeaders = [
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS, PATCH',
'Access-Control-Allow-Headers' => strtolower(implode(',', $headersKeys)),
];
// 1. LRU 进程缓存
$userId = self::$cache->get($domain);
if ($userId) {
$request->channels_uid = $userId;
$response = $next($request);
# 允许跨域
$response->withHeaders($corsHeaders);
return $response;
}
// 2. Redis Hash
$userId = Redis::hGet('domain_map', $domain);
if ($userId) {
self::$cache->set($domain, $userId);
$request->channels_uid = $userId;
$response = $next($request);
# 允许跨域
$response->withHeaders($corsHeaders);
return $response;
}
// 3. MySQL 兜底
$PluginChannelsDomain = PluginChannelsDomain::where('domain', $domain)->where('state', State::YES['value'])->find();
if ($PluginChannelsDomain) {
Redis::hSet('domain_map', $domain, $PluginChannelsDomain->channels_uid);
self::$cache->set($domain, $PluginChannelsDomain->channels_uid);
$request->channels_uid = $PluginChannelsDomain->channels_uid;
$response = $next($request);
# 允许跨域
$response->withHeaders($corsHeaders);
return $response;
}
return response('Domain not found', 401);
}
}
@@ -0,0 +1,293 @@
<?php
namespace plugin\control\expose\trait;
use app\expose\enum\Filesystem;
use app\expose\helper\Config;
use app\model\Uploads as ModelUploads;
use app\model\UploadsClassify;
use plugin\control\expose\helper\Uploads as HelperUploads;
use Shopwwi\WebmanFilesystem\Storage;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use support\Log;
use support\Request;
trait Uploads
{
protected $channels_uid;
protected $admin_uid;
protected $uid;
public function getList(Request $request)
{
$limit = $request->get('limit', 10);
$dir_name = $request->get('dir_name', 'all');
$where = [];
$where[] = ['u.channels_uid', '=', $this->channels_uid];
if ($dir_name != 'all') {
$where[] = ['c.dir_name', '=', $dir_name];
}
$accept = $request->get('accept');
if ($accept) {
# 判断是否包含*号
if (strpos($accept, '*') !== false) {
$accept = str_replace('*', '', $accept);
$where[] = ['u.mime', 'like', "%{$accept}%"];
} elseif (strpos($accept, '.') !== false) {
$accept = str_replace('.', '', $accept);
if (strpos($accept, ',') !== false) {
$accept = explode(',', $accept);
$where[] = ['u.ext', 'in', $accept];
} else {
$where[] = ['u.ext', '=', $accept];
}
} else {
$where[] = ['u.mime', 'in', explode(',', $accept)];
}
}
if ($this->uid) {
$where[] = ['u.uid', '=', $this->uid];
}
if ($this->admin_uid) {
$where[] = ['u.admin_uid', '=', $this->admin_uid];
}
$ModelUploads = ModelUploads::alias('u')->where($where)
->join('uploads_classify c', 'u.classify_id=c.id')
->field('u.*,c.title as classify_title')
->order('u.id desc')
->paginate($limit)->each(function ($item) {
$item->url = HelperUploads::url($item->channels_uid, $item->path);
return $item;
});
return $this->resData($ModelUploads);
}
public function upload(Request $request)
{
$dir_name = $request->post('dir_name');
$dir_title = $request->post('dir_title');
$config = new Config('filesystem', '', $this->channels_uid);
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
if (empty($dir_title)) {
return $this->fail('分类不存在');
}
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $dir_title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $config->default;
$UploadsClassify->channels_uid = $this->channels_uid;
$UploadsClassify->save();
}
}
$channels = $request->post('channels');
if (!$channels) {
$channels = $UploadsClassify->channels;
}
$date_path = date('Ymd');
//单文件上传
$file = $request->file('file');
try {
$Storage = new Storage($config->toArray());
$result = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
} catch (\Throwable $th) {
return $this->exception($th);
}
try {
$Uploads = new ModelUploads;
$Uploads->uid = $this->uid;
$Uploads->admin_uid = $this->admin_uid;
$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 = $this->channels_uid;
$Uploads->save();
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->resData([
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
]);
}
public function uploads(Request $request)
{
$dir_name = $request->post('dir_name');
$dir_title = $request->post('dir_title');
$config = new Config('filesystem', '', $this->channels_uid);
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
if (empty($dir_title)) {
return $this->fail('分类不存在');
}
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $dir_title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $config->default;
$UploadsClassify->channels_uid = $this->channels_uid;
$UploadsClassify->save();
}
}
$channels = $request->post('channels');
if (!$channels) {
$channels = $UploadsClassify->channels;
}
$date_path = date('Ymd');
$files = $request->file();
try {
$Storage = new Storage($config->toArray());
$results = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->uploads($files);
} catch (\Throwable $th) {
return $this->exception($th);
}
$resData = [];
try {
foreach ($results as $result) {
$Uploads = new ModelUploads;
$Uploads->uid = $this->uid;
$Uploads->admin_uid = $this->admin_uid;
$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 = $this->channels_uid;
$Uploads->save();
$resData[] = [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
} catch (\Throwable $th) {
return $this->exception($th);
}
return $this->resData($resData);
}
public function deleteUploads(Request $request)
{
$ids = $request->post('ids');
$data = ModelUploads::whereIn('id', $ids)->where('channels_uid', $this->channels_uid)->select();
$config = new Config('filesystem', '', $this->channels_uid);
foreach ($data as $item) {
if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) {
continue;
}
try {
$filesystem = FilesystemFactory::get($item->channels, $config->toArray());
$filesystem->delete($item->path);
} catch (\Throwable $th) {
Log::error('删除文件"' . $item->path . '"失败:' . $th->getMessage(), $th->getTrace());
}
$item->delete();
}
return $this->success('删除成功');
}
public function updateUploads(Request $request)
{
$ids = $request->post('ids');
$dir_name = $request->post('dir_name');
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
return $this->fail('分类不存在');
}
$data = ModelUploads::whereIn('id', $ids)->where('channels_uid', $this->channels_uid)->select();
foreach ($data as $item) {
if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) {
continue;
}
$item->classify_id = $UploadsClassify->id;
$item->save();
}
return $this->success('修改成功');
}
public function getUploadClassify(Request $request)
{
$config = new Config('filesystem', '', $this->channels_uid);
if (!UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->count()) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = '默认分类';
$UploadsClassify->dir_name = 'uploads/default';
$UploadsClassify->channels = $config->default;
$UploadsClassify->channels_uid = $this->channels_uid;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$UploadsClassify = UploadsClassify::where(['channels_uid' => $this->channels_uid])->order('sort asc,id asc')->select()->toArray();
$all = [
'title' => '全部',
'dir_name' => 'all',
'is_system' => 1,
];
array_unshift($UploadsClassify, $all);
return $this->resData($UploadsClassify);
}
public function saveClassify(Request $request)
{
$data = $request->post();
if (empty($data['title'])) {
return $this->fail('标题不能为空');
}
if (empty($data['channels'])) {
return $this->fail('请选择储存渠道');
}
if (empty($data['dir_name'])) {
return $this->fail('目录名不能为空');
}
$UploadsClassify = UploadsClassify::where(['dir_name' => $data['dir_name'], 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->dir_name = 'uploads/' . $data['dir_name'];
$UploadsClassify->channels_uid = $this->channels_uid;
}
$UploadsClassify->title = $data['title'];
$UploadsClassify->channels = $data['channels'];
if (!empty($data['sort'])) {
$UploadsClassify->sort = $data['sort'];
}
if ($UploadsClassify->save()) {
return $this->success('创建成功');
} else {
return $this->fail('创建失败');
}
}
public function deleteClassify(Request $request)
{
$dir_name = $request->post('dir_name');
if (empty($dir_name)) {
return $this->fail('目录名不能为空');
}
if ($dir_name == 'all') {
return $this->fail('全部目录不能删除');
}
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find();
if (!$UploadsClassify) {
return $this->fail('目录不存在');
}
if ($UploadsClassify->is_system) {
return $this->fail('系统分类,无法删除');
}
if (ModelUploads::where(['classify_id' => $UploadsClassify->id])->count() > 0) {
return $this->fail('目录分类下有文件不能删除');
}
if ($UploadsClassify->delete()) {
return $this->success('删除成功');
} else {
return $this->fail('删除失败');
}
}
}
@@ -0,0 +1,9 @@
<?php
return [
'User' => 'User',
'User List' => 'User List',
'Create User' => 'Create User',
'Edit User' => 'Edit User',
'form_basic_title'=>'Basic',
];
@@ -0,0 +1,6 @@
<?php
return [
'success' => 'Success',
'fail' => 'Fail',
];
@@ -0,0 +1,89 @@
<?php
return [
'Login Title' => 'Account Password Login',
'Vcode Login' => 'SMS verification code Login',
'Register Title'=>'Register %web_name% Account',
'Qrcode Login'=>'Please use WeChat "Scan Code" to log in',
'toolbar Lock'=>'Lock',
'toolbar Search'=>'Search',
'toolbar Notification'=>'Notification',
'toolbar FullScreen'=>'FullScreen',
'Logout Success'=>'Logout Success',
'Captcha Incorrect'=>'Captcha Incorrect',
'Scene Cannot Be Empty'=>'Scene Cannot Be Empty',
'Vcode Send Success'=>'Vcode Send Success',
'Unlock Success'=>'Unlock Success',
'userDropdownMenu updateSelf'=>'User Center',
'Login Link Text'=>'Already have an account? Login now',
'Register Link Text'=>'No account yet? Click to register for free',
'Login User Agreement Title'=>'《Login Agreement》',
'Login User Agreement Label'=>'Click "Login" to represent that you have read and agree to',
'Register User Agreement Title'=>'《Register Agreement》',
'Register User Agreement Label'=>'Click "Register" to represent that you have read and agree to',
'Control Not Open'=>'Control Not Open',
'Dashboard' => 'Dashboard',
'Console' => 'Console',
'User' => 'User',
'User List' => 'User List',
'Create User' => 'Create User',
'Edit User' => 'Edit User',
'Update Status' => 'Update Status',
'Functional' => 'Functional',
'Administrator' => 'Administrator',
'Staff List' => 'Staff List',
'Create Administrator' => 'Create Administrator',
'Edit Administrator' => 'Edit Administrator',
'Delete Administrator' => 'Delete Administrator',
'Move Role' => 'Move Role',
'Role Management' => 'Role Management',
'Role List' => 'Role List',
'Create Role' => 'Create Role',
'Edit Role' => 'Edit Role',
'Delete Role' => 'Delete Role',
'Systems' => 'Systems',
'Basic Settings' => 'Basic Settings',
'Control Settings' => 'Control Settings',
'Upload Settings' => 'Upload Settings',
'SMS Settings' => 'SMS Settings',
'Payment Management' => 'Payment Management',
'Payment Settings' => 'Payment Settings',
'Payment Template' => 'Payment Template',
'Create Payment Template' => 'Create Payment Template',
'Edit Payment Template' => 'Edit Payment Template',
'Delete Payment Template' => 'Delete Payment Template',
'Platform Management' => 'Platform Management',
'WeChat Mini Program' => 'WeChat Mini Program',
'WeChat Official Account' => 'WeChat Official Account',
'Alipay Mini Program' => 'Alipay Mini Program',
'WeChat Open Platform' => 'WeChat Open Platform',
'App Platform' => 'App Platform',
'H5 Platform' => 'H5 Platform',
'PC Platform' => 'PC Platform',
'Public Permissions' => 'Public Permissions',
'Edit Profile' => 'Edit Profile',
'Get Profile' => 'Get Profile',
'Logout' => 'Logout',
'Get Upload Categories' => 'Get Upload Categories',
'Edit Upload Category' => 'Edit Upload Category',
'Delete Upload Category' => 'Delete Upload Category',
'Upload File' => 'Upload File',
'Delete Upload File' => 'Delete Upload File',
'Edit Upload File' => 'Edit Upload File',
'Get Upload File List' => 'Get Upload File List',
'Login Title'=>'Admin Login',
'toolbar Lock'=>'Lock',
'toolbar Search'=>'Search',
'toolbar Notification'=>'Notification',
'toolbar FullScreen'=>'FullScreen',
'toolbar House'=>'Home',
'toolbar Control'=>'Control',
'userDropdownMenu updateSelf'=>'Admin Info',
'Logout Success'=>'Logout Success',
'PIN Code Cannot Be Empty'=>'PIN Code Cannot Be Empty',
'Please Enter 6 Digits PIN Code'=>'Please Enter 6 Digits PIN Code',
'Lock Success'=>'Lock Success',
'Unlock Success'=>'Unlock Success',
'form_basic_title'=>'Basic',
'Settings_basic_web_name'=>'Apps Name'
];
@@ -0,0 +1,5 @@
<?php
return [
'success' => 'Success',
];
@@ -0,0 +1,9 @@
<?php
return [
'User' => '用户',
'User List' => '用户列表',
'Create User' => '创建用户',
'Edit User' => '编辑用户',
'form_basic_title'=>'基础信息',
];
@@ -0,0 +1,6 @@
<?php
return [
'success' => '成功',
'fail' => '失败',
];
@@ -0,0 +1,89 @@
<?php
return [
'Login Title' => '账号密码登录',
'Vcode Login' => '验证码登录',
'Register Title' => '注册%web_name%',
'Qrcode Login'=>'请使用微信“扫一扫”扫码登录',
'toolbar Lock'=>'锁屏',
'toolbar Search'=>'搜索菜单',
'toolbar Notification'=>'查看通知',
'toolbar FullScreen'=>'全屏/退出全屏',
'Logout Success'=>'退出成功',
'Captcha Incorrect'=>'验证码不正确',
'Scene Cannot Be Empty'=>'场景不能为空',
'Vcode Send Success'=>'验证码发送成功',
'Unlock Success'=>'解锁成功',
'userDropdownMenu updateSelf'=>'个人中心',
'Login Link Text'=>'已有账号?立即登录',
'Register Link Text'=>'还没有账号?点击免费注册',
'Login User Agreement Title'=>'《登录协议》',
'Login User Agreement Label'=>'点击"登录"即代表你已阅读并同意',
'Register User Agreement Title'=>'《注册协议》',
'Register User Agreement Label'=>'点击"注册"即代表你已阅读并同意',
'Control Not Open'=>'控制台未开启',
'Dashboard' => '首页',
'Console' => '控制台',
'User' => '用户',
'User List' => '用户列表',
'Create User' => '创建用户',
'Edit User' => '编辑用户',
'Update Status' => '更新状态',
'Functional' => '职能',
'Administrator' => '管理员',
'Staff List' => '人员列表',
'Create Administrator' => '创建管理员',
'Edit Administrator' => '编辑管理员',
'Delete Administrator' => '删除管理员',
'Move Role' => '移动角色',
'Role Management' => '角色',
'Role List' => '角色管理',
'Create Role' => '创建角色',
'Edit Role' => '编辑角色',
'Delete Role' => '删除角色',
'Systems' => '系统',
'Basic Settings' => '基本配置',
'Control Settings' => '控制台配置',
'Upload Settings' => '上传配置',
'SMS Settings' => '短信配置',
'Payment Management' => '支付管理',
'Payment Settings' => '支付配置',
'Payment Template' => '支付模板',
'Create Payment Template' => '新增支付模板',
'Edit Payment Template' => '编辑支付模板',
'Delete Payment Template' => '删除支付模板',
'Platform Management' => '平台管理',
'WeChat Mini Program' => '微信小程序',
'WeChat Official Account' => '微信公众号',
'Alipay Mini Program' => '支付宝小程序',
'WeChat Open Platform' => '微信开放平台',
'App Platform' => 'APP',
'H5 Platform' => 'H5',
'PC Platform' => 'PC',
'Public Permissions' => '公共权限',
'Edit Profile' => '修改自己的信息',
'Get Profile' => '获取自己的信息',
'Logout' => '退出登录',
'Get Upload Categories' => '获取上传分类',
'Edit Upload Category' => '编辑上传分类',
'Delete Upload Category' => '删除上传分类',
'Upload File' => '上传文件',
'Delete Upload File' => '删除上传文件',
'Edit Upload File' => '编辑上传文件',
'Get Upload File List' => '获取上传文件列表',
'Login Title'=>'管理员登录',
'toolbar Lock'=>'锁屏',
'toolbar Search'=>'搜索菜单',
'toolbar Notification'=>'查看通知',
'toolbar FullScreen'=>'全屏/退出全屏',
'toolbar House'=>'网站首页',
'toolbar Control'=>'网站前台控制台',
'userDropdownMenu updateSelf'=>'管理员信息',
'Logout Success'=>'退出成功',
'PIN Code Cannot Be Empty'=>'PIN码不能为空',
'Please Enter 6 Digits PIN Code'=>'请输入6位PIN码',
'Lock Success'=>'锁定成功',
'Unlock Success'=>'解锁成功',
'form_basic_title'=>'基础信息',
'Settings_basic_web_name'=>'应用名称'
];
@@ -0,0 +1,5 @@
<?php
return [
'success' => '成功',
];
@@ -0,0 +1,43 @@
<?php
namespace plugin\control\utils;
class LRUCache
{
private int $capacity;
private array $cache = [];
private array $order = [];
public function __construct($capacity = 2000)
{
$this->capacity = $capacity;
}
public function get($key)
{
if (!isset($this->cache[$key])) {
return null;
}
unset($this->order[$key]);
$this->order[$key] = true;
return $this->cache[$key];
}
public function set($key, $value)
{
if (isset($this->cache[$key])) {
$this->cache[$key] = $value;
unset($this->order[$key]);
$this->order[$key] = true;
return;
}
if (count($this->cache) >= $this->capacity) {
$oldestKey = array_key_first($this->order);
unset($this->cache[$oldestKey], $this->order[$oldestKey]);
}
$this->cache[$key] = $value;
$this->order[$key] = true;
}
}
@@ -0,0 +1,97 @@
<?php
namespace plugin\control\utils\yidevs;
use app\expose\helper\Config;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
use plugin\control\utils\yidevs\stream\Client as StreamClient;
use plugin\control\utils\yidevs\Exception\InvalidResultException;
use plugin\control\utils\yidevs\Exception\InvalidTokenException;
use support\Log;
class Client
{
protected $token;
// protected $domain = 'https://api.yidevs.com';
protected $domain = 'http://110.42.56.227:36999';
protected $HttpClient;
protected $channels_uid;
public function __construct()
{
if ($domain = getenv('YIDEVS_DOMAIN')) {
$this->domain = $domain;
}
}
public function setChannelsUid(int $channels_uid)
{
$this->channels_uid = $channels_uid;
$config = new Config('yidevs', 'control', $channels_uid);
if (!$config->token) {
throw new InvalidTokenException('YIDEVS_TOKEN is not set for channels_uid: ' . $channels_uid);
}
$this->token = $config->token;
}
public function client()
{
$this->HttpClient = new GuzzleHttpClient([
'base_uri' => $this->domain,
'timeout' => 600,
'verify' => false,
'proxy' => false
]);
}
public function request(string $method, string $url, array $options = [])
{
try {
$this->client();
$response = $this->HttpClient->request($method, $url, array_merge([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->token,
]
], $options));
$res = $response->getBody()->getContents();
} catch (ClientException | RequestException $e) {
Log::error($e->getMessage() . PHP_EOL . $e->getTraceAsString());
throw new \Exception($e->getMessage());
} catch (\Throwable $th) {
Log::error($th->getMessage() . PHP_EOL . $th->getTraceAsString());
throw new \Exception($th->getMessage());
}
return $res;
}
public function stream(string $url, array $data = [], ?callable $stream = null)
{
return new StreamClient($this->token, $this->domain . '/' . $url, $data, $stream);
}
public function get(string $url, array $query = [])
{
$res = $this->request('GET', $url, ['query' => $query]);
$result = json_decode($res, true);
if (!isset($result['code'])) {
throw new InvalidResultException($res);
}
if ($result['code'] != 200) {
throw new \Exception($result['msg']);
}
return $result['data'];
}
public function post(string $url, array $data = [], ?callable $stream = null)
{
if ($stream) {
return new StreamClient($this->token, $this->domain . '/' . $url, $data, $stream);
}
$res = $this->request('POST', $url, ['json' => $data]);
$result = json_decode($res, true);
if (!isset($result['code'])) {
throw new InvalidResultException($res);
}
if ($result['code'] != 200) {
throw new \Exception($result['msg']);
}
return $result['data'];
}
}
@@ -0,0 +1,7 @@
<?php
namespace plugin\control\utils\yidevs\Exception;
class HttpCurlException extends \Exception
{
}
@@ -0,0 +1,7 @@
<?php
namespace plugin\control\utils\yidevs\Exception;
class InvalidResultException extends \Exception
{
}
@@ -0,0 +1,7 @@
<?php
namespace plugin\control\utils\yidevs\Exception;
class InvalidTokenException extends \Exception
{
}
@@ -0,0 +1,17 @@
<?php
namespace plugin\control\utils\yidevs;
use plugin\control\utils\yidevs\trait\Audio;
use plugin\control\utils\yidevs\trait\AudioAssistant;
use plugin\control\utils\yidevs\trait\Chat;
use plugin\control\utils\yidevs\trait\ChatAssistant;
use plugin\control\utils\yidevs\trait\Draw;
use plugin\control\utils\yidevs\trait\DrawAssistant;
use plugin\control\utils\yidevs\trait\Video;
use plugin\control\utils\yidevs\trait\VideoAssistant;
class Yidevs
{
use Chat, ChatAssistant, Draw, DrawAssistant, Video, VideoAssistant, Audio, AudioAssistant;
}
@@ -0,0 +1,121 @@
<?php
namespace plugin\control\utils\yidevs\stream;
use plugin\control\utils\yidevs\Exception\HttpCurlException;
use support\Log;
class Client
{
protected $streamFunction;
public function __construct(string $token, string $url, array $data = [], ?callable $streamFunction = null)
{
try {
$this->streamFunction = $streamFunction;
$data['stream'] = true;
$curl_info = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 600,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type:application/json',
'Accept:application/json',
'Authorization:Bearer ' . $token,
],
CURLOPT_PROXY => false
];
$curl_info[CURLOPT_WRITEFUNCTION] = [$this, 'streamWriteFunction'];
$curl = curl_init();
curl_setopt_array($curl, $curl_info);
$response = curl_exec($curl);
// $info = curl_getinfo($curl);
$error = curl_error($curl);
curl_close($curl);
if (! $response) {
throw new HttpCurlException($error);
}
} catch (\Throwable $th) {
throw $th;
}
}
public function streamWriteFunction($ch, $data)
{
// data 必须是字符串
if (!is_string($data)) {
return strlen($data);
}
try {
// 用户处理
$this->parseResponse($data, function ($line) use ($ch) {
if (is_callable($this->streamFunction)) {
$line = ($this->streamFunction)($ch, $line);
}
if (is_string($line)) {
// 检查 DONE
if ($line === 'DONE') {
throw new \Exception('DONE');
}
} else {
if (isset($line['error'])) {
throw new \Exception('DONE');
}
}
});
} catch (\Throwable $th) {
if ($th->getMessage() !== 'DONE') {
Log::error($th->getMessage() . PHP_EOL . $th->getTraceAsString());
}
}
flush();
// 必须返回 exact byte count
return strlen($data);
}
public function parseResponse($data, callable $callback)
{
// 按换行分割,兼容 \n 和 \r\n
$lines = preg_split("/\r?\n/", $data);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') {
continue; // 跳过空行
}
// 必须以 data: 开头
if (strpos($line, 'data: ') === 0) {
// 截掉 data: 前缀
$json = trim(substr($line, 6));
} else {
$json = $line;
}
// SSE 结束标记:[DONE]
if ($json === '[DONE]') {
$callback('DONE');
continue;
}
try {
// 尝试解析 JSON
$payload = json_decode($json, true);
if (isset($payload['error'])) {
throw new \Exception("[{$payload['error']['code']}]" . $payload['error']['message']);
}
$line = $payload;
} catch (\Throwable $th) {
}
$callback($line);
}
}
}
@@ -0,0 +1,45 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait Audio
{
/**
* 获取壹定开放平台大模型-音频-模型列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function AudioModels(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/Audio/models', $data);
}
/**
* 调用壹定开放平台大模型-音频-生成
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function AudioTTS(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/Audio/tts', $data);
}
public static function AudioVoiceClone(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/Audio/cloneVoice', $data);
}
public static function AudioVoiceList(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/Audio/voiceList', $data);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait AudioAssistant
{
/**
* 获取壹定开放平台大模型-音频助手列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $query
* @return array
*/
public static function AudioAssistantlist(int $channels_uid, array $query = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/AudioAssistant/list', $query);
}
/**
* 调用壹定开放平台大模型-音频助手
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function AudioAssistantTTS(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/AudioAssistant/tts', $data);
}
}
@@ -0,0 +1,37 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait Chat
{
/**
* 获取壹定开放平台大模型-模型列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $query
* @return array
*/
public static function ChatModels(int $channels_uid, array $query = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/Chat/models', $query);
}
/**
* 调用壹定开放平台大模型-聊天-对话
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @param callable|null $stream
* @return mixed
*/
public static function ChatCompletions(int $channels_uid, array $data = [], ?callable $stream = null)
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
if ($stream) {
return $Client->stream('app/yimind/api/Chat/completions', $data, $stream);
}
return $Client->request('POST', 'app/yimind/api/Chat/completions', ['json' => $data]);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait ChatAssistant
{
/**
* 获取壹定开放平台大模型-助手列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $query
* @return array
*/
public static function ChatAssistantlist(int $channels_uid, array $query = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/ChatAssistant/list', $query);
}
/**
* 调用壹定开放平台大模型-助手
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function ChatAssistantCompletions(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/ChatAssistant/completions', $data);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait Draw
{
/**
* 获取壹定开放平台大模型-绘图-模型列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function DrawModels(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/Draw/models', $data);
}
/**
* 调用壹定开放平台大模型-绘图-生成
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function DrawTIGI(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/Draw/tigi', $data);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait DrawAssistant
{
/**
* 获取壹定开放平台大模型-绘图助手列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $query
* @return array
*/
public static function DrawAssistantlist(int $channels_uid, array $query = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/DrawAssistant/list', $query);
}
/**
* 调用壹定开放平台大模型-绘图助手
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function DrawAssistantTIGI(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/DrawAssistant/tigi', $data);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait Video
{
/**
* 获取壹定开放平台大模型-视频-模型列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function VideoModels(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/Video/models', $data);
}
/**
* 调用壹定开放平台大模型-视频-生成
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function VideoTITV(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/Video/titv', $data);
}
}
@@ -0,0 +1,33 @@
<?php
namespace plugin\control\utils\yidevs\trait;
use plugin\control\utils\yidevs\Client;
trait VideoAssistant
{
/**
* 获取壹定开放平台大模型-绘图助手列表
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $query
* @return array
*/
public static function VideoAssistantlist(int $channels_uid, array $query = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->get('app/yimind/api/VideoAssistant/list', $query);
}
/**
* 调用壹定开放平台大模型-绘图助手
* @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid
* @param array $data
* @return array
*/
public static function VideoAssistantTITV(int $channels_uid, array $data = [])
{
$Client = new Client();
$Client->setChannelsUid($channels_uid);
return $Client->post('app/yimind/api/VideoAssistant/titv', $data);
}
}