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

- 源码: xhadmincn/FastMovieAI (Apache 2.0)
- 二开: Docker部署, Swoole改Select, route.php修复, 补update/VERSION
- vendor/示例资源已gitignore
This commit is contained in:
李建琦
2026-08-26 18:37:36 +08:00
commit eae151fd57
888 changed files with 105571 additions and 0 deletions
@@ -0,0 +1,76 @@
<?php
namespace app\expose\api;
use support\Log;
trait Install
{
protected $path;
/**
* 多语言必须在构造函数中设置plugin值为插件名
*/
protected $plugin;
/**
* 安装
*
* @param $version
* @return void
*/
public static function install($version) {}
/**
* 卸载
*
* @param $version
* @return void
*/
public static function uninstall($version) {}
/**
* 更新
*
* @param $from_version
* @param $to_version
* @param $context
* @return void
*/
public static function update($from_version, $to_version, $context = null) {}
/**
* 更新前数据收集等
*
* @param $from_version
* @param $to_version
* @return array|array[]
*/
public static function beforeUpdate($from_version, $to_version)
{
// 在更新之前获得老菜单,通过context传递给 update
return [];
}
/**
* 获取菜单
*
* @return array|mixed
*/
public function getMenus()
{
clearstatcache();
# 当前类的运行目录
if (is_file($menu_file = $this->path . '/../admin.json')) {
$content = file_get_contents($menu_file);
if (!$content) {
return false;
}
$data = json_decode($content, true);
return $data;
}
return false;
}
public function getPlugin()
{
return $this->plugin;
}
}
@@ -0,0 +1,72 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class ActionBuilder extends DataModel
{
protected $data = [];
public function __construct($label = '', $props = [])
{
$this->data = [
'label' => $label,
'extra' => [
'group' => [],
'props' => $props,
]
];
}
/**
* 向操作按钮中添加字段
*
* @param string $label 操作按钮标题
* @param mixed $extra 额外参数
* @param string $extra.path 路径, 例如: '/app/pluginExample/admin/Form/index'
* @param string $extra.model 操作模式, 详见 \app\expose\enum\Action::class,例如:Action::COMFIRM['value']
* @param mixed $extra.where 显示条件, 例如: [['is_system', '!=', 1]]
* @param mixed $extra.params 额外传参, 例如: [['id' => 1]]
* @param string|array $extra.field 指定提交字段, 例如: *或['id'=>'uid']id:数据集中字段名,uid:提交数据中字段名
* @param mixed $extra.props 操作属性, 例如: ['type' => 'primary', 'title' => '编辑《{nickname}》']
* @param mixed $extra.component 操作展示组件
* @param string $extra.component.name 组件名, 例如: 'button'
* @param mixed $extra.component.props 组件属性, 例如: ['type' => 'primary', 'size' => 'small']
* @return ActionBuilder
*/
public function add(string $label, mixed $extra = [])
{
$this->data['extra']['group'][] = [
'label' => $label,
'extra' => array_merge([
'loading' => false,
], $extra)
];
return $this;
}
/**
* 设置数据
*
* @param array $data 数据
* @return ActionBuilder
*/
public function setData(array $data)
{
$this->data['data'] = $data;
return $this;
}
/**
* 设置数据操作
*
* @param string $action 操作,append:追加合并数据
* @return ActionBuilder
*/
public function setDataAction(string $action)
{
$this->data['callback'] = $action;
return $this;
}
public function builder()
{
return $this->data;
}
}
@@ -0,0 +1,48 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class AppsBuilder extends DataModel
{
protected $data = [];
public function __construct($props = [])
{
$this->data['props'] = array_merge([], $props);
$this->data['columns'] = [];
}
public function add(string $prop, string $label, mixed $extra = [])
{
$this->data['columns'][] = [
'prop' => $prop,
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addAction(string $label, mixed $extra = [])
{
$this->data['action'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addTabs(string $label, string $name, mixed $extra)
{
$this->data['tabs'][] = [
'label' => $label,
'name' => $name,
'extra' => $extra
];
return $this;
}
public function addScreen()
{
}
public function builder()
{
return $this->data;
}
}
@@ -0,0 +1,25 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class ComponentBuilder extends DataModel
{
protected $data = [];
public function add(string $component, mixed $children = null, array $props = [])
{
$this->data[] = [
'component' => $component,
'children' => $children,
'props' => $props
];
return $this;
}
public function builder()
{
$result = $this->data;
$this->data = [];
return $result;
}
}
@@ -0,0 +1,27 @@
<?php
namespace app\expose\build\builder;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\DataModel;
class DataboardBuilder extends DataModel
{
protected $data = [];
public function __construct($props = [])
{
$this->data['props'] = array_merge([
'gutter' => 0
], $props);
$this->data['rows'] = [];
}
public function add(Basic $component, array $props = [], array $row = [])
{
$this->data['rows'][] = [
'props' => $props,
'row' => $row,
'component' => $component
];
return $this;
}
}
@@ -0,0 +1,117 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
/**
* 审核表单构建器
*
* @package app\expose\build\builder
* @property string $formName 审核表单名
* @property string $formTitle 审核表单标题
* @property array $props Form Attributes
* @method ExamineBuilder add(string $field, string $title, string $component, string $value = null, mixed $extra = []) 向审核表单中添加字段
* @method ExamineBuilder remove(string $field) 从审核表单中移除字段
* @method ExamineBuilder setOldData(array $data) 设置审核原始对比数据
* @method ExamineBuilder setNewData(array $data) 设置审核最新对比数据
* @method FormBuilder addForm(FormBuilder $builder) 添加表单
*/
class ExamineBuilder extends DataModel
{
protected $data = [];
protected $translations=false;
public function __construct( $props = [])
{
if(isset($props['translations'])){
$this->translations=$props['translations'];
unset($props['translations']);
}
$this->data['props'] = $props;
$this->data['rule'] = [];
$this->data['form'] = [];
}
public function setTranslations(bool $state=true){
$this->translations=$state;
return $this;
}
/**
* 向审核表单中添加字段
*
* @param string $field 字段名
* @param string $title 字段标题
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
* @param mixed $extra 额外参数
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
* @return ExamineBuilder
*/
public function add(string $field, string $title, string $component, mixed $extra = []): ExamineBuilder
{
$title = $this->trans($title);
$this->data['rule'][] = [
'field' => $field,
'title' => $title,
'component' => $component,
'extra' => $extra
];
return $this;
}
/**
* 从审核表单中移除字段
*
* @param string $field 字段名
* @return ExamineBuilder
*/
public function remove(string $field): ExamineBuilder
{
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
return $item['field'] != $field;
});
$this->data['rule'] = array_values($rule);
return $this;
}
/**
* 设置审核原始对比数据
*
* @param array $data 数据
* @return ExamineBuilder
*/
public function setOldData(array $data): ExamineBuilder
{
$this->data['old']=$data;
return $this;
}
/**
* 设置审核最新对比数据
*
* @param array $data 数据
* @return ExamineBuilder
*/
public function setNewData(array $data): ExamineBuilder
{
$this->data['new']=$data;
return $this;
}
public function addForm(FormBuilder $builder)
{
$this->data['form']=$builder;
}
public function trans(string|null $val,array $parameters =[])
{
if(!$val||!$this->translations){
return $val;
}
$request=request();
$lang = '';
$domain=null;
if ($request && $request->lang) {
$lang = $request->lang;
$domain=$request->app;
}
return trans($val,$parameters,$domain,$lang);
}
}
@@ -0,0 +1,172 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
/**
* 表单构建器
*
* @package app\expose\build\builder
* @property string $formName 表单名
* @property string $formTitle 表单标题
* @property array $props Form Attributes
* @method FormBuilder add(string $field, string $title, string $component, string $value = null, mixed $extra = [],?ActionBuilder $action = null) 向表单中添加字段
* @method FormBuilder remove(string $field) 从表单中移除字段
* @method FormBuilder addValue(string $field, mixed $value) 向表单中添加数据
* @method FormBuilder setData(array $data) 设置表单数据
* @method string getFormName() 获取表单名
* @method string getFormTitle() 获取表单标题
* @method FormBuilder addGroupForm(FormBuilder $builder) 向表单中添加分组
* @method FormBuilder setUrl(string $url) 设置表单提交地址
*/
class FormBuilder extends DataModel
{
protected $data = [];
protected $url = '';
protected $translations=false;
public function __construct($formName = null, $formTitle = null, $props = [])
{
if(isset($props['translations'])){
$this->translations=$props['translations'];
unset($props['translations']);
}
$this->data['props'] = $props;
$this->data['name'] = $formName ?? 'basic';
$this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title');
$this->data['rule'] = [];
$this->data['form'] = [];
$this->data['group'] = [];
$this->data['url'] = $this->url;
}
public function setTranslations(bool $state=true){
$this->translations=$state;
return $this;
}
/**
* 向表单中添加字段
*
* @param string $field 字段名
* @param string $title 字段标题
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
* @param mixed|null $value 默认值
* @param mixed $extra 额外参数
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
* @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder
* @return FormBuilder
*/
public function add(string $field, string $title, string $component, mixed $value = null, mixed $extra = [],?ActionBuilder $action = null): FormBuilder
{
$title = $this->trans($title);
$this->data['rule'][] = [
'field' => $field,
'title' => $title,
'component' => $component,
'extra' => $extra,
'action' => $action ? $action->builder() : null
];
$this->addValue($field, $value);
return $this;
}
/**
* 从表单中移除字段
*
* @param string $field 字段名
* @return FormBuilder
*/
public function remove(string $field): FormBuilder
{
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
return $item['field'] != $field;
});
$this->data['rule'] = array_values($rule);
return $this;
}
/**
* 向表单中添加数据
*
* @param string $field 字段名
* @param mixed $value 默认值
* @return FormBuilder
*/
public function addValue(string $field, mixed $value): FormBuilder
{
$this->data['form'][$field] = $value;
return $this;
}
/**
* 设置表单数据
*
* @param array $data 数据
* @return FormBuilder
*/
public function setData(array $data): FormBuilder
{
foreach ($data as $key => $value) {
$this->addValue($key, $value);
}
return $this;
}
/**
* 获取表单名
*
* @return string
*/
public function getFormName(): string
{
return $this->data['name'];
}
/**
* 获取表单标题
*
* @return string
*/
public function getFormTitle(): string
{
return $this->data['title'];
}
/**
* 向表单中添加分组
*
* @param FormBuilder $builder 分组表单
* @return FormBuilder
*/
public function addGroupForm(FormBuilder $builder): FormBuilder
{
$formName = $builder->getFormName();
$temp = $builder->toArray();
$this->data['group'][] = $temp;
$this->addValue($formName, $temp['form']);
return $this;
}
/**
* 设置表单提交地址
*
* @param string $url 地址
* @return FormBuilder
*/
public function setUrl(string $url): FormBuilder
{
$this->url = $url;
$this->data['url'] = $url;
return $this;
}
public function trans(string|null $val,array $parameters =[])
{
if(!$val||!$this->translations){
return $val;
}
$request=request();
$lang = '';
$domain=null;
if ($request && $request->lang) {
$lang = $request->lang;
$domain=$request->app;
}
return trans($val,$parameters,$domain,$lang);
}
}
@@ -0,0 +1,96 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class ImagesBuilder extends DataModel
{
protected $data = [];
public function __construct($props = [])
{
$this->data['props'] = array_merge([
'image'=>'url',
'title'=>'title',
'info'=>'info'
], $props);
}
public function addSelection(string $prop = '', string $label = '', mixed $extra = [])
{
if (!isset($extra['props'])) {
$extra['props'] = [
'width' => '50px',
'type' => 'selection'
];
}
$extra['props']['type'] = 'selection';
$this->data['selection'] = true;
return $this;
}
public function addAction(string|ActionBuilder $label, mixed $extra = [])
{
if ($label instanceof ActionBuilder) {
$this->data['action'] = $label->builder();
return $this;
}
if (!isset($this->data['action'])) {
$this->data['action'] = [];
}
$this->data['action'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addFooter(mixed $props = [])
{
if (!isset($this->data['selection'])) {
$this->addSelection();
}
$this->data['footer'] = [
'extra' => [
'group' => [],
'props' => $props,
]
];
return $this;
}
public function addFooterAction(string $label, mixed $extra = [])
{
$this->data['footer']['extra']['group'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addHeader(array|ActionBuilder $props = [])
{
if ($props instanceof ActionBuilder) {
$this->data['header'] = $props->builder();
return $this;
}
$this->data['header'] = [
'extra' => [
'group' => [],
'props' => $props,
]
];
return $this;
}
public function addHeaderAction(string $label, mixed $extra = [])
{
$this->data['header']['extra']['group'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addScreen(FormBuilder $builder)
{
$this->data['screen'] = $builder;
}
public function builder()
{
return $this->data;
}
}
@@ -0,0 +1,154 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
/**
* 信息展示构建器
*
* @package app\expose\build\builder
* @property string $formName 信息展示名
* @property string $formTitle 信息展示标题
* @property array $props Form Attributes
* @method InfoBuilder add(string $field, string $title, string $component, mixed $extra = [],?ActionBuilder $action = null) 向信息展示中添加字段
* @method InfoBuilder remove(string $field) 从信息展示中移除字段
* @method InfoBuilder addValue(string $field, mixed $value) 向信息展示中添加数据
* @method InfoBuilder setData(array $data) 设置信息展示数据
* @method InfoBuilder addGroupInfo(InfoBuilder $builder) 向信息展示中添加分组
*/
class InfoBuilder extends DataModel
{
protected $data = [];
protected $translations = false;
public function __construct($formName = null, $formTitle = null, $props = [])
{
if (isset($props['translations'])) {
$this->translations = $props['translations'];
unset($props['translations']);
}
$this->data['props'] = $props;
$this->data['name'] = $formName ?? 'basic';
$this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title');
$this->data['rule'] = [];
$this->data['group'] = [];
$this->data['data'] = [];
}
public function setTranslations(bool $state = true)
{
$this->translations = $state;
return $this;
}
/**
* 向信息展示中添加字段
*
* @param string $field 字段名
* @param string $title 字段标题
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
* @param mixed $extra 额外参数
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
* @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder
* @return InfoBuilder
*/
public function add(string $field, string $title, string $component, mixed $extra = [], ?ActionBuilder $action = null): InfoBuilder
{
$title = $this->trans($title);
$this->data['rule'][] = [
'field' => $field,
'title' => $title,
'component' => $component,
'extra' => $extra,
'action' => $action ? $action->builder() : null
];
return $this;
}
/**
* 从信息展示中移除字段
*
* @param string $field 字段名
* @return InfoBuilder
*/
public function remove(string $field): InfoBuilder
{
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
return $item['field'] != $field;
});
$this->data['rule'] = array_values($rule);
return $this;
}
/**
* 向表单中添加数据
*
* @param string $field 字段名
* @param mixed $value 默认值
* @return InfoBuilder
*/
public function addValue(string $field, mixed $value): InfoBuilder
{
$this->data['data'][$field] = $value;
return $this;
}
/**
* 设置表单数据
*
* @param array $data 数据
* @return InfoBuilder
*/
public function setData(array $data): InfoBuilder
{
foreach ($data as $key => $value) {
$this->addValue($key, $value);
}
return $this;
}
/**
* 获取表单名
*
* @return string
*/
public function getInfoName(): string
{
return $this->data['name'];
}
/**
* 获取表单标题
*
* @return string
*/
public function getInfoTitle(): string
{
return $this->data['title'];
}
/**
* 向表单中添加分组
*
* @param InfoBuilder $builder 分组表单
* @return InfoBuilder
*/
public function addGroupInfo(InfoBuilder $builder): InfoBuilder
{
$infoName = $builder->getInfoName();
$temp = $builder->toArray();
$this->data['group'][] = $temp;
$this->addValue($infoName, $temp['data']);
return $this;
}
public function trans(string|null $val, array $parameters = [])
{
if (!$val || !$this->translations) {
return $val;
}
$request = request();
$lang = '';
$domain = null;
if ($request && $request->lang) {
$lang = $request->lang;
$domain = $request->app;
}
return trans($val, $parameters, $domain, $lang);
}
}
@@ -0,0 +1,60 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class RechargeBuilder extends DataModel
{
protected $data = [];
public function __construct($props = [])
{
$this->data['props'] = array_merge([], $props);
$this->data['list'] = [];
$this->data['prompt']=[];
}
public function addPayment(mixed $data)
{
$this->data['payment']=$data;
return $this;
}
public function addList(mixed $data)
{
$this->data['list']=$data;
return $this;
}
public function addQrcodePrompt(ComponentBuilder $builder)
{
$this->data['prompt']['qrcode'][] = $builder->builder();
return $this;
}
public function addAgreementPrompt(ComponentBuilder $builder)
{
$this->data['prompt']['agreement'][] = $builder->builder();
return $this;
}
public function addFooterPrompt(ComponentBuilder $builder)
{
$this->data['prompt']['footer'][] = $builder->builder();
return $this;
}
public function addSwiperPrompt(ComponentBuilder $builder)
{
$this->data['prompt']['swiper'][] = $builder->builder();
return $this;
}
public function setCreateOrdersApi(string $api)
{
$this->data['create_orders']=$api;
return $this;
}
public function setGetOrdersStateApi(string $api)
{
$this->data['get_orders_state']=$api;
return $this;
}
public function builder()
{
return $this->data;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\expose\build\builder;
class SoltBuilder
{
public $name = 'default';
public function __construct($name = 'default')
{
$this->name = $name;
}
public static function solt(string $solt = 'default')
{
$self = new self($solt);
return $self;
}
public function text(string $text)
{
return [$this->name => $text];
}
public function add(mixed $component, mixed $props = [], mixed $children = null)
{
return [
$this->name => [
'component' => $component,
'props' => $props,
'children' => $children
]
];
}
}
@@ -0,0 +1,118 @@
<?php
namespace app\expose\build\builder;
use app\expose\utils\DataModel;
class TableBuilder extends DataModel
{
protected $data = [];
public function __construct($props = [])
{
$this->data['props'] = array_merge([
'border' => true,
'stripe' => true,
'size' => 'large',
'header-row-style' => [
'--el-table-header-bg-color' => 'var(--el-bg-color-page)'
]
], $props);
$this->data['columns'] = [];
}
public function add(string $prop, string $label, mixed $extra = [])
{
$this->data['columns'][] = [
'prop' => $prop,
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addSelection(string $prop = '', string $label = '', mixed $extra = [])
{
if (!isset($extra['props'])) {
$extra['props'] = [
'width' => '50px',
'type' => 'selection'
];
}
$extra['props']['type'] = 'selection';
$this->add($prop, $label, $extra);
$this->data['selection'] = true;
return $this;
}
public function addAction(string|ActionBuilder $label, mixed $props = [])
{
if ($label instanceof ActionBuilder) {
$this->data['action'] = $label->builder();
return $this;
}
$this->data['action'] = [
'label' => $label,
'extra' => [
'group' => [],
'props' => $props,
]
];
return $this;
}
public function addTableAction(string $label, mixed $extra = [])
{
$this->data['action']['extra']['group'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addFooter(mixed $props = [])
{
if (!isset($this->data['selection'])) {
$this->addSelection();
}
$this->data['footer'] = [
'extra' => [
'group' => [],
'props' => $props,
]
];
return $this;
}
public function addFooterAction(string $label, mixed $extra = [])
{
$this->data['footer']['extra']['group'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addHeader(array|ActionBuilder $props = [])
{
if ($props instanceof ActionBuilder) {
$this->data['header'] = $props->builder();
return $this;
}
$this->data['header'] = [
'extra' => [
'group' => [],
'props' => $props,
]
];
return $this;
}
public function addHeaderAction(string $label, mixed $extra = [])
{
$this->data['header']['extra']['group'][] = [
'label' => $label,
'extra' => $extra
];
return $this;
}
public function addScreen(FormBuilder $builder)
{
$this->data['screen'] = $builder;
}
public function builder()
{
return $this->data;
}
}
@@ -0,0 +1,9 @@
<?php
namespace app\expose\build\builder\databoardBuilder;
use app\expose\utils\DataModel;
class Basic extends DataModel
{
}
@@ -0,0 +1,53 @@
<?php
namespace app\expose\build\builder\databoardBuilder\component;
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\Str;
/**
* 申请组件
*
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
*
* @method Apply setLabel(string $label) 设置标签
* @method Apply setTips(string $tips) 设置提示
* @method Apply setApply(bool $apply) 设置是否可以申请
* @method Apply setPrice(int $price) 设置价格
* @method Apply setAgreement(array $agreement) 设置协议
* @method Apply setClass(string $class) 设置class属性
* @method Apply setInterval(int $interval) 自动更新数据的时间间隔
*/
class Apply extends Basic implements DataboardBuilderInterface
{
protected $data = [];
public function __construct()
{
$this->data['name'] = 'apply';
$this->data['props'] = [];
}
public function __call($name, $arguments)
{
$name = lcfirst(str_replace('set', '', $name));
$name = Str::snake($name, '_');
$this->data['props'][$name] = $arguments[0];
return $this;
}
/**
* 设置数据
*
* @param mixed $data
* @return Apply
*/
public function setData(mixed $data): Apply
{
$this->data['props']['data'] = $data;
return $this;
}
public function setApi(string $api): Apply
{
$this->data['props']['api'] = $api;
return $this;
}
}
@@ -0,0 +1,50 @@
<?php
namespace app\expose\build\builder\databoardBuilder\component;
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\Str;
/**
* 申请组件
*
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
*
* @method Domain setLabel(string $label) 设置标签
* @method Domain setTips(string $tips) 设置提示
* @method Domain setClass(string $class) 设置class属性
* @method Domain setInterval(int $interval) 自动更新数据的时间间隔
*/
class Domain extends Basic implements DataboardBuilderInterface
{
protected $data = [];
public function __construct()
{
$this->data['name'] = 'domain';
$this->data['props'] = [];
}
public function __call($name, $arguments)
{
$name = lcfirst(str_replace('set', '', $name));
$name = Str::snake($name, '_');
$this->data['props'][$name] = $arguments[0];
return $this;
}
/**
* 设置数据
*
* @param mixed $data
* @return Domain
*/
public function setData(mixed $data): Domain
{
$this->data['props']['data'] = $data;
return $this;
}
public function setApi(string $api): Domain
{
$this->data['props']['api'] = $api;
return $this;
}
}
@@ -0,0 +1,40 @@
<?php
namespace app\expose\build\builder\databoardBuilder\component;
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\Str;
class Echarts extends Basic implements DataboardBuilderInterface
{
protected $data = [];
public function __construct()
{
$this->data['name'] = 'echarts';
$this->data['props'] = [];
}
public function __call($name, $arguments)
{
$name = lcfirst(str_replace('set', '', $name));
$name = Str::snake($name, '_');
$this->data['props'][$name] = $arguments[0];
return $this;
}
/**
* 设置数据
*
* @param mixed $data
* @return echarts
*/
public function setData(mixed $data): echarts
{
$this->data['props']['data'] = $data;
return $this;
}
public function setApi(string $api): echarts
{
$this->data['props']['api'] = $api;
return $this;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\expose\build\builder\databoardBuilder\component;
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\Str;
class Placeholder extends Basic implements DataboardBuilderInterface
{
protected $data = [];
public function __construct()
{
$this->data['name'] = 'div';
$this->data['props'] = [];
}
public function __call($name, $arguments)
{
$name = lcfirst(str_replace('set', '', $name));
$name = Str::snake($name, '_');
$this->data['props'][$name] = $arguments[0];
return $this;
}
public function setData(mixed $data): Placeholder
{
$this->data['props']['data'] = $data;
return $this;
}
public function setApi(string $api): Placeholder
{
$this->data['props']['api'] = $api;
return $this;
}
}
@@ -0,0 +1,56 @@
<?php
namespace app\expose\build\builder\databoardBuilder\component;
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
use app\expose\build\builder\databoardBuilder\Basic;
use app\expose\utils\Str;
/**
* 统计组件
*
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
*
* @method statistic setLabel(string $label) 设置标签
* @method statistic setTips(string $tips) 设置提示
* @method statistic setUnit(string $unit) 设置单位
* @method statistic setClass(string $class) 设置class属性
* @method statistic setInterval(int $interval) 自动更新数据的时间间隔
*/
class Statistic extends Basic implements DataboardBuilderInterface
{
protected $data = [];
public function __construct()
{
$this->data['name'] = 'statistic';
$this->data['props'] = [
'yesterday_label'=>'昨日',
];
}
public function __call($name, $arguments)
{
$name = lcfirst(str_replace('set', '', $name));
$name = Str::snake($name, '_');
$this->data['props'][$name] = $arguments[0];
return $this;
}
/**
* 设置数据
*
* @param mixed $data
* @param number $data.today 今日数据
* @param number $data.yesterday 昨日数据
* @param number $data.growth_rate 增长率
* @return statistic
*/
public function setData(mixed $data): statistic
{
$this->data['props']['data'] = $data;
return $this;
}
public function setApi(string $api): statistic
{
$this->data['props']['api'] = $api;
return $this;
}
}
@@ -0,0 +1,11 @@
<?php
namespace app\expose\build\builder\databoardBuilder\interface;
use app\expose\build\builder\databoardBuilder\Basic;
interface DataboardBuilderInterface
{
public function setData(mixed $data): Basic;
public function setApi(string $api): Basic;
}
@@ -0,0 +1,29 @@
<?php
namespace app\expose\build\config;
use app\expose\utils\DataModel;
class Action extends DataModel
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = array_merge($this->data, $data);
}
/**
* 添加一个操作
*
* @param string $model 查看 \app\expose\enum\Action::class, 例如 \app\expose\enum\Action::LOCK['value']
* @param array $props ['icon' => 'Lock','label' => '锁屏']
* @return Action
*/
public function add($model, $props = []): Action
{
$this->data[] = [
'model' => $model,
'props' => $props
];
return $this;
}
}
@@ -0,0 +1,16 @@
<?php
namespace app\expose\build\config;
use app\expose\utils\DataModel;
class Apis extends DataModel
{
protected $data = [
'userinfo' => 'User/getUserInfo',
];
public function __construct(array $data = [])
{
$this->data = array_merge($this->data, $data);
}
}
@@ -0,0 +1,38 @@
<?php
namespace app\expose\build\config;
use app\expose\utils\DataModel;
class Login extends DataModel
{
protected $data = [
# 是否启用登录
'enable' => true,
# 登录标题
'title' => '账号密码登录',
# 登录请求地址
'url' => 'Login/login',
# 检测登录状态地址
'check' => 'Login/check',
# 登录页背景,auto:自动,off:关闭,url:自定义,url[]:多张图片
'bg_image' => 'auto',
# 登录页展示图片
'image' => '',
# 开启图形验证码,在短信登录时默认开启
'captcha' => true,
# 跳转链接文案
'link_text' => '',
# 用户协议链接
'user_agreement' => '/article/user_agreement.html',
# 用户协议自定义配置
/* 'user_agreement_config'=>[
'title'=>'《用户协议》',
'label'=>'点击"登录"即代表你已阅读并同意'
] */
];
public function __construct(array $data = [])
{
$this->data = array_merge($this->data, $data);
}
}
@@ -0,0 +1,90 @@
<?php
namespace app\expose\build\config;
use app\expose\utils\DataModel;
class Web extends DataModel
{
protected $data = [
# 应用名称
'web_name' => 'Loong',
# LOGO展示方式
'logo_use' => 'svg',
# 应用浅色LOGO
'web_logo_light' => '',
# 应用深色LOGO
'web_logo_dark' => '',
# ICP备案
'web_icp' => '',
# 公安备案号
'web_mps' => '',
# 公安备案文案
'web_mps_text' => '',
# 网站标题
'web_title' => '',
# 版本名称
'version_name' => 'v1.0.0',
# 版本号
'version' => 10000,
# 版权信息
'copyright' => 'copyright © 2020 RenLoong All Rights Reserved',
# 微信客服链接
'wechat_url' => '',
# 客服二维码
'wechat_qrcode_url' => '',
# 页面布局,control: 控制台布局,admin: 后台布局
'layout' => 'control',
];
public function __construct(array $data = [])
{
$request = request();
$this->data['layout'] = $request->app;
$this->data = array_merge($this->data, $data);
}
public function useLogin(array $data = [])
{
$this->data['login'] = new Login($data);
return $this;
}
public function useVcode(array $data = [])
{
$this->data['vcode'] = new Login($data);
return $this;
}
public function useRegister(array $data = [])
{
$this->data['register'] = new Login($data);
return $this;
}
public function useQrcodeLogin(array $data = [])
{
$this->data['qrcode_login'] = new Login($data);
return $this;
}
public function useApis(array $data = [])
{
$apis = new Apis($data);
if (!isset($this->data['apis'])) {
$this->data['apis'] = [];
}
$this->data['apis'] = array_merge($this->data['apis'], $apis->toArray());
return $this;
}
public function useToolbar(array $data = [])
{
if (!isset($this->data['toolbar'])) {
$this->data['toolbar'] = [];
}
$this->data['toolbar'] = array_merge($this->data['toolbar'], $data);
return $this;
}
public function useUserDropdownMenu(array $data = [])
{
if (!isset($this->data['user_dropdown_menu'])) {
$this->data['user_dropdown_menu'] = [];
}
$this->data['user_dropdown_menu'] = array_merge($this->data['user_dropdown_menu'], $data);
return $this;
}
}
@@ -0,0 +1,53 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Action extends Enum
{
const DEFAULT = [
'label' => '页面跳转',
'value' => ''
];
const REDIRECT = [
'label' => '重定向',
'value' => 'redirect'
];
const LINK= [
'label' => '链接跳转',
'value' => 'link'
];
const COMFIRM = [
'label' => '确认操作',
'value' => 'comfirm'
];
const DIALOG = [
'label' => '弹窗操作',
'value' => 'dialog'
];
const REQUEST = [
'label' => '请求操作',
'value' => 'request'
];
const LOCK = [
'label' => '锁屏',
'value' => 'Lock'
];
const SEARCH = [
'label' => '搜索',
'value' => 'Search'
];
const NOTIFICATION = [
'label' => '通知',
'value' => 'Notification'
];
const FULL_SCREEN = [
'label' => '全屏',
'value' => 'FullScreen'
];
const OUT_LOGIN = [
'label' => '退出登录',
'value' => 'OutLogin'
];
}
@@ -0,0 +1,17 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class AlipayRsaModel extends Enum
{
const PUBLIC_CERT = [
'label' => '公钥证书',
'value' => 'public_cert',
];
const PUBLIC_KEY = [
'label' => '公钥',
'value' => 'public_key',
];
}
@@ -0,0 +1,114 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class EventName extends Enum
{
const USER_REGISTER = [
'label' => '注册',
'value' => 'USER.REGISTER',
'props' => [
'type' => 'primary'
]
];
const USER_LOGIN = [
'label' => '登录',
'value' => 'USER.LOGIN',
'props' => [
'type' => 'success'
]
];
const USER_LOGOUT = [
'label' => '退出',
'value' => 'USER.LOGOUT',
'props' => [
'type' => 'warning'
]
];
const USER_UPDATE = [
'label' => '更新用户信息',
'value' => 'USER.UPDATE',
'props' => [
'type' => 'info'
]
];
const ORDERS_CREATE = [
'label' => '创建订单',
'value' => 'ORDERS.CREATE',
'props' => [
'type' => 'info'
]
];
const ORDERS_PAY = [
'label' => '支付订单',
'value' => 'ORDERS.PAY',
'props' => [
'type' => 'danger'
]
];
const ORDERS_PAY_SUCCESS = [
'label' => '支付成功',
'value' => 'ORDERS.PAY_SUCCESS',
'props' => [
'type' => 'success'
]
];
const ORDERS_CANCEL = [
'label' => '取消订单',
'value' => 'ORDERS.CANCEL',
'props' => [
'type' => 'info'
]
];
const ORDERS_REFUND = [
'label' => '退款订单',
'value' => 'ORDERS.REFUND',
'props' => [
'type' => 'warning'
]
];
const ORDERS_FINISH = [
'label' => '完成订单',
'value' => 'ORDERS.FINISH',
'props' => [
'type' => 'success'
]
];
const WECHAT_OFFICIAL_ACCOUNT_SCAN = [
'label' => '微信公众号扫码',
'value' => 'WECHAT_OFFICIAL_ACCOUNT.SCAN',
'props' => [
'type' => 'primary'
]
];
const WECHAT_OFFICIAL_ACCOUNT_SUBSCRLBE=[
'label' => '微信公众号关注',
'value' => 'WECHAT_OFFICIAL_ACCOUNT.SUBSCRLBE',
'props' => [
'type' => 'primary'
]
];
const WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE=[
'label' => '微信公众号取关',
'value' => 'WECHAT_OFFICIAL_ACCOUNT.UNSUBSCRLBE',
'props' => [
'type' => 'primary'
]
];
const NOTIFICATION_LIMIT= [
'label' => '有限通知',
'value' => 'NOTIFICATION.LIMIT',
'props' => [
'type' => 'info'
]
];
const NOTIFICATION_UNLIMIT= [
'label' => '无限通知',
'value' => 'NOTIFICATION.UNLIMIT',
'props' => [
'type' => 'info'
]
];
}
@@ -0,0 +1,30 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Examine extends Enum
{
const WAIT = [
'label' => '待审核',
'value' => 0,
'props'=>[
'type'=>'primary'
]
];
const PASS = [
'label' => '通过',
'value' => 1,
'props'=>[
'type'=>'success'
]
];
const REJECT = [
'label' => '驳回',
'value' => 2,
'props'=>[
'type'=>'danger'
]
];
}
@@ -0,0 +1,41 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Filesystem extends Enum
{
const PUBLIC = [
'label' => '本地存储(外部)',
'value' => 'public'
];
const LOCAL = [
'label' => '本地存储(内部)',
'value' => 'local'
];
const OSS = [
'label' => '阿里云存储',
'value' => 'oss'
];
const COS = [
'label' => '腾讯云存储',
'value' => 'cos'
];
const QINIU = [
'label' => '七牛存储',
'value' => 'qiniu'
];
const FTP = [
'label' => 'FTP存储',
'value' => 'ftp'
];
const S3 = [
'label' => 'S3存储',
'value' => 's3'
];
const MINIO = [
'label' => 'Minio存储',
'value' => 'minio'
];
}
@@ -0,0 +1,29 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Methods extends Enum
{
const GET = [
'label' => 'GET',
'value' => 'GET'
];
const POST = [
'label' => 'POST',
'value' => 'POST'
];
const PUT = [
'label' => 'PUT',
'value' => 'PUT'
];
const DELETE = [
'label' => 'DELETE',
'value' => 'DELETE'
];
const OPTIONS = [
'label' => 'OPTIONS',
'value' => 'OPTIONS'
];
}
@@ -0,0 +1,73 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class PaymentChannels extends Enum
{
const WXPAY = [
'label' => '微信支付',
'value' => 'wxpay',
'icon' => '/static/image/wxpay.png',
'props' => [
'type' => 'success'
]
];
const ALIPAY = [
'label' => '支付宝支付',
'value' => 'alipay',
'icon' => '/static/image/alipay.png',
'props' => [
'type' => 'primary'
]
];
const BALANCE = [
'label' => '余额支付',
'value' => 'balance',
'icon' => '/static/image/balance.png',
'props' => [
'type' => 'info'
]
];
const INTEGRAL = [
'label' => '积分支付',
'value' => 'integral',
'icon' => '/static/image/integral.png',
'props' => [
'type' => 'warning'
]
];
const CASH = [
'label' => '现金支付',
'value' => 'cash',
'icon' => '/static/image/cash.png',
'props' => [
'type' => 'danger'
]
];
const COUPON = [
'label' => '优惠券支付',
'value' => 'coupon',
'icon' => '/static/image/coupon.png',
'props' => [
'type' => 'info'
]
];
const FREE = [
'label' => '免费',
'value' => 'free',
'icon' => '/static/image/free.png',
'props' => [
'type' => 'success'
]
];
const ADMIN = [
'label' => '管理员操作',
'value' => 'admin',
'icon' => '/static/image/admin.png',
'props' => [
'type' => 'warning'
]
];
}
@@ -0,0 +1,25 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Permission extends Enum
{
const CREATE = [
'label' => '创建',
'value' => 'Create'
];
const DELETE = [
'label' => '删除',
'value' => 'Delete'
];
const UPDATE = [
'label' => '更新',
'value' => 'Update'
];
const UPDATE_STATE = [
'label' => '更新状态',
'value' => 'UpdateState'
];
}
@@ -0,0 +1,29 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Platform extends Enum
{
const PC = [
'label' => 'PC',
'value' => 'pc'
];
const H5 = [
'label' => 'H5',
'value' => 'h5'
];
const WECHAT_MINIAPP = [
'label' => '微信小程序',
'value' => 'wechat_miniapp'
];
const WECHAT_OFFICIAL_ACCOUNT = [
'label' => '微信公众号',
'value' => 'wechat_official_account'
];
const APP = [
'label' => 'APP',
'value' => 'app'
];
}
@@ -0,0 +1,86 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
/**
* JSON响应Code静态常量
* Class ResponseCode
* @package app\expose\enum
*/
class ResponseCode extends Enum
{
/**
* 成功
*/
const SUCCESS = 200;
/**
* 等待
*/
const WAIT = 202;
/**
* 有事件通知
*/
const SUCCESS_EVENT_PUSH = 201;
/**
* 失败
*/
const FAIL = 404;
/**
* 需要登录
*/
const NEED_LOGIN = 12000;
/**
* 需要两步验证
*/
const NEED_TWOFA = 12001;
/**
* 验证码错误
*/
const CAPTCHA = 300;
/**
* 站点信息错误
*/
const SITE_ERROR = 400;
/**
* 需要支付
*/
const NEED_PAY = 9100;
/**
* 支付成功
*/
const PAY_SUCCESS = 9000;
/**
* 支付失败
*/
const PAY_FAIL = 9010;
/**
* 重定向
*/
const REDIRECT = 302;
/**
* 确认重定向
*/
const REDIRECT_CONFIRM = 303;
/**
* 打开新窗口
*/
const OPEN_NEW_WINDOW = 304;
/**
* 确认打开新窗口
*/
const OPEN_NEW_WINDOW_CONFIRM = 305;
/**
* 无权限
*/
const NO_PERMISSION = 403;
/**
* 需要绑定电话
*/
const NEED_BIND_MOBILE = 12002;
/**
* 锁定
*/
const LOCK = 423;
}
@@ -0,0 +1,17 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class ResponseEvent extends Enum
{
/**
* 通知客户端更新网站配置
*/
const UPDATE_WEBCONFIG = "UPDATE::WEBCONFIG";
/**
* 通知客户端更新用户信息
*/
const UPDATE_USERINFO = "UPDATE::USERINFO";
}
@@ -0,0 +1,21 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class SmsChannels extends Enum
{
const ALIYUN = [
'label' => '阿里云短信',
'value' => 'aliyun'
];
const TENCENT = [
'label' => '腾讯云短信',
'value' => 'tencent'
];
const SMSBAO = [
'label' => '短信宝',
'value' => 'smsbao'
];
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class State extends Enum
{
const YES = [
'label' => '正常',
'value' => 1
];
const NO = [
'label' => '禁用',
'value' => 0
];
}
@@ -0,0 +1,18 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
/**
* 表单提交事件
* 默认提交表单后重置表单
* @package app\expose\enum
*/
class SubmitEvent extends Enum
{
/**
* 提交表单后静默处理
*/
const SILENT = "SILENT";
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class Week extends Enum
{
const SUNDAY = [
'label' => '周日',
'value' => 0,
'props' => [
'type' => 'info'
]
];
const MONDAY = [
'label' => '周一',
'value' => 1,
'props' => [
'type' => 'success'
]
];
const TUESDAY = [
'label' => '周二',
'value' => 2,
'props' => [
'type' => 'success'
]
];
const WEDNESDAY = [
'label' => '周三',
'value' => 3,
'props' => [
'type' => 'success'
]
];
const THURSDAY = [
'label' => '周四',
'value' => 4,
'props' => [
'type' => 'success'
]
];
const FRIDAY = [
'label' => '周五',
'value' => 5,
'props' => [
'type' => 'success'
]
];
const SATURDAY = [
'label' => '周六',
'value' => 6,
'props' => [
'type' => 'info'
]
];
}
@@ -0,0 +1,17 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class WxpayMchType extends Enum
{
const NORMAL = [
'label' => '普通商户',
'value' => 'normal'
];
const SERVICE = [
'label' => '子商户 (服务商模式)',
'value' => 'service'
];
}
@@ -0,0 +1,17 @@
<?php
namespace app\expose\enum;
use app\expose\enum\builder\Enum;
class WxpayVersion extends Enum
{
const V3 = [
'label' => 'V3',
'value' => 'v3'
];
const V2 = [
'label' => 'V2',
'value' => 'v2'
];
}
@@ -0,0 +1,100 @@
<?php
namespace app\expose\enum\builder;
class Enum
{
/**
* 获取枚举选项
* @param callable|null $filter 过滤函数
* @return array 选项数组
*/
public static function getOptions(?callable $filter = null)
{
$class = new \ReflectionClass(static::class);
$options = [];
foreach ($class->getConstants() as $key => $value) {
if ($filter) {
if ($filter($value)) {
if (is_array($value)) {
$options[] = $value;
} else {
$options[] = [
'label' => $key,
'value' => $value
];
}
}
} else {
if (is_array($value)) {
$options[] = $value;
} else {
$options[] = [
'label' => $key,
'value' => $value
];
}
}
}
return $options;
}
/**
* 获取枚举文本
* @param mixed $value 值
* @return string 文本
*/
public static function getText($value)
{
$options = static::getOptions();
foreach ($options as $option) {
if ($option['value'] == $value) {
return $option['label'];
}
}
return '';
}
/**
* 获取枚举选项
* @param mixed $value 值
* @return array|null 选项
*/
public static function get($value, $key = null)
{
$options = static::getOptions();
foreach ($options as $option) {
if ($option['value'] == $value) {
return array_key_exists($key, $option) ? $option[$key] : $option;
}
}
return null;
}
/**
* 获取枚举值
* @param callable|null $filter 过滤函数
* @return array 值数组
*/
public static function getValues(?callable $filter = null)
{
$options = static::getOptions($filter);
$values = [];
foreach ($options as $option) {
$values[] = $option['value'];
}
return $values;
}
/**
* 判断是否存在
* @param mixed $value 值
* @return bool 是否存在
*/
public static function has($value)
{
$options = static::getOptions();
foreach ($options as $option) {
if ($option['value'] == $value) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,19 @@
<?php
namespace app\expose\exception;
class Exception extends \Exception
{
protected $data = [];
public function __construct($message = '', $code = 0, $data = [])
{
parent::__construct($message, $code);
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}
@@ -0,0 +1,109 @@
<?php
namespace app\expose\helper;
use Exception;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
/**
* 图像验证码助手类
* Class Captcha
* @package app\helper
*
* @method static Response captcha()
* @method static array captchaCode()
* @method static bool check(string $captcha)
*
*/
class Captcha
{
/**
* 检验图像验证码
* @param string $captcha
* @return boolean
*/
public static function check(string $captcha, ?string $token = null): bool
{
$request = request();
if ($token) {
$request->sessionId($token);
}
$captchaData = $request->session()->get('captcha');
if (!$captchaData) {
throw new Exception('图像验证码不存在');
}
if ($captchaData['expire'] < time()) {
throw new Exception('图像验证码已过期');
}
// 对比session中的captcha值
if (strtolower($captcha) !== $captchaData['captcha']) {
throw new Exception('图像验证码不正确');
}
return true;
}
public static function create()
{
$request = request();
$bg = $request->get('bg');
$length = $request->get('length', 4);
if ($length < 4) {
$length = 4;
}
if ($length > 6) {
$length = 6;
}
$defaultBg = '255,255,255';
if ($bg) {
$defaultBg = $bg;
}
$width = (int)$request->get('w', 150);
$height = (int)$request->get('h', 40);
// 初始化验证码类
$builder = new CaptchaBuilder((int)$length);
$bgArr = explode(',', $defaultBg);
$builder->setBackgroundColor($bgArr[0], $bgArr[1], $bgArr[2]);
$builder->setDistortion(false);
$builder->setInterpolation(false);
$builder->setTextColor(255 - $bgArr[0], 255 - $bgArr[1], 255 - $bgArr[2]);
// 生成验证码
$builder->build($width, $height);
return $builder;
}
/**
* 生成图像验证码
* @return Response
*/
public static function captcha(): Response
{
$request = request();
$builder = self::create();
$request->session()->set('captcha', [
'captcha' => strtolower($builder->getPhrase()),
'expire' => time() + 60 * 5
]);
$img_content = $builder->inline();
return response($img_content, 200);
}
/**
* 图像验证码
*/
public static function captchaCode(): array
{
$request = request();
$builder = self::create();
$request->session()->set('captcha', [
'captcha' => strtolower($builder->getPhrase()),
'expire' => time() + 60 * 5
]);
$img_content = $builder->inline();
return ['base64' => $img_content, 'token' => $request->sessionId()];
}
}
@@ -0,0 +1,142 @@
<?php
namespace app\expose\helper;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\SubmitEvent;
use app\model\Config as ModelConfig;
use app\expose\utils\DataModel;
class Config extends DataModel
{
protected $configData = [];
protected $groupData = [];
protected $data = [];
protected $group = '';
protected $channels_uid = null;
/**
* 获取配置
*
* @param string $group 配置分组,settings目录下的文件名
* @param string|null $plugin 如若要获取全局配置,请传入''
*/
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
{
$request = request();
if ($plugin === null) {
$plugin = $request->plugin;
}
if ($request && $request->channels_uid && $channels_uid === null) {
$this->channels_uid = $request->channels_uid;
} elseif ($channels_uid) {
$this->channels_uid = $channels_uid;
}
$this->group = $plugin ? $plugin . '.' . $group : $group;
$this->configData = config('settings');
$this->groupData = $this->configData[$this->group] ?? [];
$this->builder();
}
/**
* 获取当前应用指定配置,如需获取全局配置请new Config('group','');
*
* @param string $group 配置分组,settings目录下的文件名
* @param string|null $field 获取自定字段配置
* @param mixed $default 默认数据
* @return array|mixed
*/
public static function get(string $group, string|null $field = null, mixed $default = null)
{
$self = new self($group);
if ($field) {
return isset($self->{$field}) ? $self->{$field} : $default;
}
return $self->toArray();
}
public function builder()
{
$d = [];
$request = request();
$domain = '';
$host = '';
if ($request) {
$domain = $request->header('x-forwarded-proto') . '://' . $request->host();
$host = $request->host();
}
$replaceData = [
'{DOMAIN}' => $domain,
'{HOST}' => $host,
'{CHANNELS_UID}' => $this->channels_uid
];
if (!empty($this->groupData)) {
foreach ($this->groupData as $key => $item) {
$has = false;
if (is_string($item['value']) && strpos($item['value'], '{DOMAIN}') !== false && $domain) {
$has = true;
} elseif (is_string($item['value']) && strpos($item['value'], '{HOST}') !== false && $host) {
$has = true;
} elseif (is_string($item['value']) && strpos($item['value'], '{CHANNELS_UID}') !== false && $this->channels_uid) {
$has = true;
}
if ($has) {
$d[$item['field']] = str_replace(array_keys($replaceData), array_values($replaceData), $item['value']);
} else {
$d[$item['field']] = $item['value'];
}
}
}
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
if ($ConfigModel) {
foreach ($ConfigModel->value as $field => $value) {
$has = false;
if (is_string($value) && strpos($value, '{DOMAIN}') !== false && $domain) {
$has = true;
} elseif (is_string($value) && strpos($value, '{HOST}') !== false && $host) {
$has = true;
} elseif (is_string($value) && strpos($value, '{CHANNELS_UID}') !== false && $this->channels_uid) {
$has = true;
}
if ($has) {
$d[$field] = str_replace(array_keys($replaceData), array_values($replaceData), $value);
} else {
$d[$field] = $value;
}
}
}
$this->data = $d;
}
public function getGroupData()
{
return $this->groupData;
}
public function getGrop()
{
return $this->group;
}
public static function set($group, $field, $value)
{
$self = new self($group);
$self->{$field} = $value;
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
if (!$ConfigModel) {
$ConfigModel = new ModelConfig;
$ConfigModel->group = $group;
$ConfigModel->channels_uid = $self->channels_uid;
}
$ConfigModel->value = $self->toArray();
$ConfigModel->save();
}
public static function formBuilder($group, $plugin = null, $channels_uid = null)
{
$self = new self($group, $plugin, $channels_uid);
$builder = new FormBuilder(null, null, [
'submitEvent' => SubmitEvent::SILENT
]);
$builder->setTranslations();
$groupData = $self->getGroupData();
foreach ($groupData as $key => $item) {
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
}
$builder->setData($self->toArray());
return $builder;
}
}
@@ -0,0 +1,110 @@
<?php
namespace app\expose\helper;
use app\expose\build\builder\FormBuilder;
use app\expose\enum\SubmitEvent;
use app\model\Config as ModelConfig;
use app\expose\utils\DataModel;
class ConfigGroup extends DataModel
{
protected $configData = [];
protected $groupData = [];
protected $data = [];
protected $group = '';
protected $channels_uid = null;
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
{
$request = request();
if ($plugin === null) {
$plugin = $request->plugin;
}
if ($request && $request->channels_uid && $channels_uid === null) {
$this->channels_uid = $request->channels_uid;
} elseif ($channels_uid) {
$this->channels_uid = $channels_uid;
}
$this->group = $plugin ? $plugin . '.' . $group : $group;
$this->configData = config('settings-tabs');
$this->groupData = $this->configData[$this->group] ?? [];
$this->builder();
}
public function builder()
{
$d = [];
if (!empty($this->groupData)) {
foreach ($this->groupData as $key => $item) {
if ($key != 'group') {
$d[$item['field']] = $item['value'];
} else {
foreach ($item as $group) {
$d[$group['name']] = [];
foreach ($group['children'] as $child) {
$d[$group['name']][$child['field']] = $child['value'];
}
}
}
}
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
if ($ConfigModel) {
foreach ($ConfigModel->value as $field => $value) {
$d[$field] = $value;
}
}
}
$this->data = $d;
}
public function getGroupData()
{
return $this->groupData;
}
public function getGrop()
{
return $this->group;
}
public static function get($group, $field = null, $default = null)
{
$self = new self($group);
if ($field) {
return isset($self->{$field}) ? $self->{$field} : $default;
}
return $self->toArray();
}
public static function set($group, $field, $value)
{
$self = new self($group);
$self->{$field} = $value;
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
if (!$ConfigModel) {
$ConfigModel = new ModelConfig;
$ConfigModel->group = $group;
$ConfigModel->channels_uid = $self->channels_uid;
}
$ConfigModel->value = $self->toArray();
$ConfigModel->save();
}
public static function formBuilder($group, $plugin = null, $channels_uid = null)
{
$self = new self($group, $plugin, $channels_uid);
$builder = new FormBuilder(null, null, [
'translations' => true,
'submitEvent' => SubmitEvent::SILENT
]);
$groupData = $self->getGroupData();
foreach ($groupData as $key => $item) {
if ($key != 'group') {
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
}
}
foreach ($groupData['group'] as $key => $item) {
$subBuilder = new FormBuilder($item['name'], $item['title']);
foreach ($item['children'] as $child) {
$subBuilder->add($child['field'], $child['title'], $child['component'], $child['value'], $child['extra']);
}
$builder->addGroupForm($subBuilder);
}
$builder->setData($self->toArray());
return $builder;
}
}
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace app\expose\helper;
use app\expose\utils\DataModel;
class Menus extends DataModel
{
public $data = [];
public $itemData = [
# 菜单标题
"title" => "Title",
# 菜单图标,element-plus中的Icon名称,如:ElementPlus
"icon" => "",
# 路由路径
"path" => "",
# 使用组件,table:表格列表,form:表单,images:图片列表
"component" => "default",
# 请求方式,GET,POST,PUT,DELETE
"methods" => ["GET"],
# 是否显示,0:隐藏,1:显示
"show" => 1,
# 排序,正序
"sort" => 9999,
# 携带地址栏参数
"query" => [],
# 携带post参数
"params" => [],
# 路由meta
"meta" => [
"login" => true,
],
# 子级和父级参数一致
"children" => []
];
/**
* 构造函数
*
* @param object $Install 需实现 getMenus 方法的类返回菜单数据
*/
public function __construct($Install)
{
$request = request();
$lang = null;
if ($request && $request->lang) {
$lang = $request->lang;
}
$data = $Install->getMenus();
if ($lang) {
$this->translateChildren($data, $request->app,$lang);
}
foreach (glob(base_path('plugin/*')) as $path) {
$plugin_name = basename($path);
$class = 'plugin\\' . $plugin_name . '\\api\\Install';
$plugin = new $class;
$menus = $plugin->getMenus();
if ($menus) {
if ($lang&&$plugin->getPlugin()) {
$request->plugin = $plugin_name;
$this->translateChildren($menus, $request->app,$lang);
}
$data[] = $menus;
}
}
$this->builder($data);
}
public function translateChildren(&$data, $domain,$lang)
{
if (is_array($data) && isset($data[0])) {
foreach ($data as $key => &$item) {
if (isset($item['title'])) {
$data[$key]['title'] = trans($item['title'], [], $domain, $lang);
}
if (!empty($item['children'])) {
$this->translateChildren($item['children'],$domain, $lang);
}
}
} else {
if (isset($data['title'])) {
$data['title'] = trans($data['title'], [], $domain, $lang);
}
if (!empty($data['children'])) {
$this->translateChildren($data['children'],$domain, $lang);
}
}
}
/**
* 递归合并
*
* @param array $data
* @return $data
*/
public function mergeMenus(array $data): array
{
$result = [];
foreach ($data as $key => $item) {
$temp = array_merge($this->itemData, $item);
if (!empty($temp['children'])) {
$temp['children'] = $this->mergeMenus($temp['children']);
}
$result[] = $temp;
}
return $result;
}
/**
* 构建菜单
*
* @param array $data
* @return Menus
*/
public function builder(array $data)
{
$this->data = $this->mergeMenus($data);
return $this;
}
}
@@ -0,0 +1,188 @@
<?php
namespace app\expose\helper;
use app\expose\enum\PaymentChannels;
use app\expose\enum\Platform;
use app\expose\enum\State;
use app\expose\helper\Uploads as HelperUploads;
use app\model\PaymentConfig;
use app\model\PaymentTemplate;
use Exception;
use plugin\control\expose\helper\Uploads;
use Yansongda\Pay\Pay;
use support\Log;
class Payment
{
public static function get($platform, $channels, int|null $channels_uid = null)
{
$where = [];
$where[] = ['platform', '=', $platform];
$where[] = ['channels', '=', $channels];
if ($channels_uid) {
$where[] = ['channels_uid', '=', $channels_uid];
}
$PaymentConfig = PaymentConfig::where($where)->find();
if (!$PaymentConfig) {
throw new Exception('支付配置不存在');
}
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id, 'channels_uid' => $channels_uid])->find();
if (!$PaymentTemplate) {
throw new Exception('支付模板不存在');
}
return $PaymentTemplate->value;
}
public static function platform(int|null $channels_uid = null)
{
$platform = request()->platform;
$where = [];
$where[] = ['platform', '=', $platform];
$where[] = ['state', '=', State::YES['value']];
if ($channels_uid) {
$where[] = ['channels_uid', '=', $channels_uid];
}
$PaymentConfig = PaymentConfig::where($where)->select();
$data = [];
foreach ($PaymentConfig as $item) {
$enum = PaymentChannels::get($item->channels);
$temp = [
'id' => $item->id,
'label' => $enum['label'],
'enum' => $enum,
];
if ($item->is_default == State::YES['value']) {
$temp['default'] = 1;
}
$data[] = $temp;
}
return $data;
}
public static function getIntegralName()
{
$integral = '积分';
try {
$platform = request()->platform;
if (!$platform) {
$platform = Platform::PC['value'];
}
$Payment = self::get($platform, PaymentChannels::INTEGRAL['value']);
if (isset($Payment['display_name'])) {
$integral = $Payment['display_name'];
}
} catch (\Throwable $th) {
}
return $integral;
}
public static function createPayment($model, $id)
{
$PaymentConfig = PaymentConfig::where(['id' => $id])->find();
if (!$PaymentConfig) {
throw new Exception('支付配置不存在');
}
switch ($PaymentConfig->channels) {
case PaymentChannels::WXPAY['value']:
return self::wxPay($model, $PaymentConfig);
case PaymentChannels::ALIPAY['value']:
break;
case PaymentChannels::INTEGRAL['value']:
break;
case PaymentChannels::BALANCE['value']:
break;
}
throw new \Exception('未知支付方式');
}
public static function wxPay($model, $PaymentConfig)
{
switch ($PaymentConfig->platform) {
case Platform::PC['value']:
return self::wxPayPc($model, $PaymentConfig);
case Platform::H5['value']:
return self::wxPayH5($model, $PaymentConfig);
case Platform::WECHAT_OFFICIAL_ACCOUNT['value']:
return self::wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig);
case Platform::APP['value']:
return self::wxPayApp($model, $PaymentConfig);
case Platform::WECHAT_MINIAPP['value']:
return self::wxPayMiniapp($model, $PaymentConfig);
}
}
public static function wxPayPc($model, $PaymentConfig)
{
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id])->find();
if (!$PaymentTemplate) {
throw new Exception('支付模板不存在');
}
$Payment = $PaymentTemplate->value;
$notify_url = $Payment['notify_url'];
# 判断是否为“/”结尾
if (substr($notify_url, -1) == '/') {
$notify_url = substr($notify_url, 0, -1);
}
$notify_url .= '/notify/wechat/' . $model->plugin . '/' . $PaymentTemplate->id;
$mch_secret_cert = null;
if ($PaymentTemplate->channels_uid) {
$mch_secret_cert = Uploads::downloadTemp($Payment['ssl_key'], 'mch_secret_cert_' . $PaymentTemplate->id);
} else {
$mch_secret_cert = base_path(HelperUploads::path($Payment['ssl_key']));
}
$mch_public_cert_path = null;
if ($PaymentTemplate->channels_uid) {
$mch_public_cert_path = Uploads::downloadTemp($Payment['ssl_cert'], 'mch_public_cert_path_' . $PaymentTemplate->id);
} else {
$mch_public_cert_path = base_path(HelperUploads::path($Payment['ssl_cert']));
}
$config = [
'wechat' => [
'default' => [
// 必填-商户号
'mch_id' => $Payment['mch_id'],
// 选填-v2商户私钥
'mch_secret_key_v2' => '',
// 必填-v3商户秘钥
'mch_secret_key' => $Payment['mch_key'],
// 必填-商户私钥 字符串或路径
'mch_secret_cert' => $mch_secret_cert,
// 必填-商户公钥证书路径
'mch_public_cert_path' => $mch_public_cert_path,
// 必填
'notify_url' => $notify_url,
// 选填-公众号 的 app_id
'mp_app_id' => $Payment['appid'],
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
]
],
'logger' => [
'enable' => true,
'file' => runtime_path('logs/wechat-pay-' . date('Y-m-d') . '.log'),
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [
'timeout' => 5.0,
'connect_timeout' => 5.0,
],
];
$order = [
'out_trade_no' => $model->trade,
'amount' => [
'total' => getenv('DEV') === 'true' ? 1 : $model->price * 100,
],
'description' => $model->title,
'time_expire' => date('c', strtotime($model->expire_time)),
];
Log::info('wxPayPc', ['order' => $order, 'config' => $config]);
$Pay = Pay::wechat($config)->scan($order);
return [
'plugin' => $model->plugin,
'trade' => $model->trade,
'qrcode' => $Pay->code_url,
];
}
public static function wxPayH5($model, $PaymentConfig) {}
public static function wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig) {}
public static function wxPayApp($model, $PaymentConfig) {}
public static function wxPayMiniapp($model, $PaymentConfig) {}
}
@@ -0,0 +1,368 @@
<?php
namespace app\expose\helper;
use app\expose\enum\Filesystem;
use app\model\Uploads as ModelUploads;
use app\model\UploadsClassify;
use Exception;
use GuzzleHttp\Client;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Webman\Http\UploadFile;
class Uploads
{
/**
* 获取文件URL
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function url(string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::url($value);
}
return $data;
}
# 判断path是否以[?]开头,如果是则获取对应的key
if (strpos($path, '[') === 0) {
$key = substr($path, 1, strpos($path, ']') - 1);
$model = new \stdClass;
$model->channels = $key;
$model->path = substr($path, strpos($path, ']') + 1);
}else{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return $path;
}
}
return Storage::adapter($model->channels)->url($model->path);
}
/**
* 获取文件在本地存储的完整路径
* @param string|array|null $path 文件路径
* @return string|array
*/
public static function local(string|array|null $path)
{
if (empty($path)) {
return $path;
}
if (is_array($path)) {
$data = [];
foreach ($path as $value) {
$data[] = self::local($value);
}
return $data;
}
if (strpos($path, '[') === 0) {
$key = substr($path, 1, strpos($path, ']') - 1);
$model = new \stdClass;
$model->channels = $key;
$model->path = substr($path, strpos($path, ']') + 1);
}else{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return $path;
}
}
$filesystem = FilesystemFactory::get($model->channels);
$has = $filesystem->has($model->path);
if ($has) {
if (in_array($model->channels, [Filesystem::LOCAL['value'], Filesystem::PUBLIC['value']])) {
$config = config('plugin.shopwwi.filesystem.app.storage.' . $model->channels);
return $config['root'] . $model->path;
} else {
return self::downloadTemp(Storage::adapter($model->channels)->url($model->path));
}
}
return $path;
}
/**
* 获取文件路径
* @param string|array|null $url URL地址
* @return string|array
*/
public static function path(string|array|null $url)
{
if (empty($url)) {
return '';
}
if (is_array($url)) {
$data = [];
if (count($url) === 1) {
return self::path(current($url));
}
$config = config('plugin.shopwwi.filesystem.app.storage');
$urls = [];
foreach ($config as $key => $value) {
if (empty($value['url'])) {
continue;
}
$urls[$key] = $value['url'];
}
foreach ($url as $value) {
if (filter_var($value, FILTER_SANITIZE_URL) === false) {
throw new Exception('URL地址不合法');
}
$parseUrl = parse_url($value);
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
$key = array_search($domain, $urls);
if ($key) {
$data[] = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
} else {
$data[] = ltrim($parseUrl['path'], '/');
}
}
return $data;
} else {
if (filter_var($url, FILTER_SANITIZE_URL) === false) {
throw new Exception('URL地址不合法');
}
$parseUrl = parse_url($url);
$config = config('plugin.shopwwi.filesystem.app.storage');
$urls = [];
foreach ($config as $key => $value) {
if (empty($value['url'])) {
continue;
}
$urls[$key] = $value['url'];
}
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
$key = array_search($domain, $urls);
if ($key) {
$data = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
} else {
$data = ltrim($parseUrl['path'], '/');
}
return $data;
}
}
/**
* 保存文件
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @param string $title 文件分类标题
* @return array
*/
public static function save(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save', $title = '本地保存')
{
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = $title;
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$date_path = date('Ymd');
$originName = basename($path);
//单文件上传
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
$Uploads = new ModelUploads;
$Uploads->classify_id = $UploadsClassify->id;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
/**
* 远程下载文件
* @param string $url 文件URL
* @param string $channels 文件存储通道
* @return string
*/
public static function download(string $url, $channels = Filesystem::PUBLIC['value'])
{
$dir_name = 'uploads/remote';
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'is_system' => 1])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = '远程下载';
$UploadsClassify->dir_name = $dir_name;
$UploadsClassify->channels = $channels;
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$date_path = date('Ymd');
$client = new Client();
$response = $client->get($url);
$body = $response->getBody();
$file = $body->getContents();
$urlPath = parse_url($url, PHP_URL_PATH);
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
$fileName = uniqid() . '.' . $ext;
$temp = tempnam(sys_get_temp_dir(), '') . $fileName;
file_put_contents($temp, $file);
$file = new UploadFile($temp, $temp, $response->getHeaderLine('Content-Type'), 0);
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
\unlink($temp);
$Uploads = new ModelUploads;
$Uploads->classify_id = $UploadsClassify->id;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return $result->file_name;
}
/**
* 下载文件到临时文件
* @param string $url 文件URL
* @return string 临时文件路径
*/
public static function downloadTemp(string $url)
{
$client = new Client();
$response = $client->get($url);
$body = $response->getBody();
$file = $body->getContents();
$urlPath = parse_url($url, PHP_URL_PATH);
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
$fileName = uniqid() . '.' . $ext;
$temp = runtime_path('temp/' . $fileName);
file_put_contents($temp, $file);
return $temp;
}
/**
* 上传文件
* @param string $path 文件路径
* @param string $channels 文件存储通道
* @param string $dir_name 文件存储目录
* @return array
*/
public static function upload(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save')
{
$date_path = date('Ymd');
$originName = basename($path);
//单文件上传
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
$Uploads = new ModelUploads;
$Uploads->filename = $result->origin_name;
$Uploads->path = $result->file_name;
$Uploads->ext = $result->extension;
$Uploads->mime = $result->mime_type;
$Uploads->size = $result->size;
$Uploads->channels = $channels;
$Uploads->save();
return [
'id' => $Uploads->id,
'url' => $result->file_url,
'path' => $result->file_name,
'mime' => $result->mime_type,
'dir_name' => $dir_name
];
}
/**
* 删除文件
* @param string $path 文件路径
* @return bool
*/
public static function delete(string $path)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return true;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->delete($model->path);
$model->delete();
return true;
}
/**
* 重命名文件
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function rename(string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->move($model->path, $newName);
$model->path = $newName;
$model->save();
return true;
}
/**
* 判断文件是否存在
* @param string $path 文件路径
* @return bool
*/
public static function has(string $path)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
return $filesystem->has($model->path);
}
/**
* 复制文件
* @param string $path 文件路径
* @param string $newName 新文件名
* @return bool
*/
public static function copy(string $path, string $newName)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
$filesystem->copy($model->path, $newName);
$model = new ModelUploads;
$model->classify_id = $model->classify_id;
$model->filename = $model->filename;
$model->path = $newName;
$model->ext = $model->ext;
$model->mime = $model->mime;
$model->size = $model->size;
$model->path = $newName;
$model->save();
return true;
}
/**
* 获取文件列表
* @param string $path 文件路径
* @param bool $recursive 是否递归
* @return array
*/
public static function listContents(string $path, $recursive = false)
{
$model = ModelUploads::where(['path' => $path])->find();
if (!$model) {
return false;
}
$filesystem = FilesystemFactory::get($model->channels);
return $filesystem->listContents($path, $recursive);
}
}
@@ -0,0 +1,79 @@
<?php
namespace app\expose\helper;
use app\expose\enum\State;
use app\expose\template\email\Vcode as EmailVcode;
use app\expose\template\sms\Vcode as SmsVcode;
use app\expose\utils\Email;
use app\expose\utils\Sms;
use app\expose\utils\Str;
use support\Log;
class Vcode
{
public static function check($username, $vcode, $scene, $token = null)
{
$request = request();
if (!empty($token)) {
$request->sessionId($token);
}
$vcodeData = $request->session()->get('vcode:' . $scene . ':' . $username);
if (empty($vcodeData)) {
throw new \Exception('验证码不存在');
}
if ($vcodeData['vcode'] != $vcode) {
throw new \Exception('验证码错误');
}
if ($vcodeData['expire'] < time()) {
throw new \Exception('验证码已过期');
}
return true;
}
public static function send($username, $scene, $token = null)
{
$request = request();
if (!empty($token)) {
$request->sessionId($token);
}
$vcode = Str::random(6, 1);
if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
$Email = new Email;
$Email->toemail = $username;
$Email->setTemplate(EmailVcode::class);
$Email->setData(['vcode' => $vcode]);
$Email->send();
} else {
$request->session()->set('vcode:' . $scene . ':' . $username, [
'vcode' => $vcode,
'expire' => time() + 60 * 5
]);
$config = new ConfigGroup('sms', '');
foreach ($config['channels'] as $channel) {
$item = $config[$channel];
if ($item['enable'] == State::NO['value']) {
continue;
}
try {
if (empty($item['vcode_template_' . $scene])) {
continue;
}
$SmsVcode = new SmsVcode();
$SmsVcode->channel = $channel;
$SmsVcode->config = $item;
$SmsVcode->template_code = $item['vcode_template_' . $scene];
$Sms = new Sms;
$Sms->mobile = $username;
$Sms->setTemplate($SmsVcode);
$Sms->setData(['code' => $vcode]);
$Sms->send();
return true;
} catch (\Throwable $th) {
Log::error("发送短信失败:" . $th->getMessage(), $th->getTrace());
continue;
}
}
throw new \Exception('发送失败');
}
}
}
@@ -0,0 +1,126 @@
<?php
namespace app\expose\helper;
use GuzzleHttp\Client;
use support\Redis;
class Wechat
{
/**
* QR_SCENE
* 临时的整型参数值
* @var string
*/
const QR_SCENE = 'QR_SCENE';
/**
* QR_STR_SCENE
* 临时的字符串参数值
* @var string
*/
const QR_STR_SCENE = 'QR_STR_SCENE';
/**
* QR_LIMIT_SCENE
* 永久的整型参数值
* @var string
*/
const QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
/**
* QR_LIMIT_STR_SCENE
* 永久的字符串参数值
* @var string
*/
const QR_LIMIT_STR_SCENE = 'QR_LIMIT_STR_SCENE';
public static function getAccessToken()
{
$config = new Config('wechat_official_account', '');
$access_token = Redis::get('wechat_official_account_access_token');
if ($access_token) {
return $access_token;
}
if (!$config['state']) {
throw new \Exception('请先开启公众号');
}
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$config['app_id']}&secret={$config['app_secret']}";
try {
$client = new Client();
$response = $client->get($url);
$data = json_decode($response->getBody()->getContents(), true);
if (isset($data['errcode'])) {
throw new \Exception($data['errmsg']);
}
Redis::set('wechat_official_account_access_token', $data['access_token'], 'EX', $data['expires_in']);
return $data['access_token'];
} catch (\Throwable $th) {
throw $th;
}
}
/**
* 创建公众号二维码
*
* @param string $action_name QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
* @param int $expire 二维码有效时间,以秒为单位。最大不超过2592000(即30天)
* @param array $eventData 回调事件数据
* @param Class $eventData[0] 回调事件类
* @param string $eventData[1] 回调事件方法
* @param array $eventData[2] 回调事件参数,会在回调事件方法中$data.params原样传入
* @return array $data
* @return string $data['url'] 二维码图片地址
* @return string $data['id'] 二维码id,在回调事件中$data['EventKey']
* @return int $data['expire_seconds'] 二维码有效时间
* @throws \Exception
*/
public static function createQrCode($action_name, $expire, $eventData)
{
$request = request();
$id = $request->sessionId();
if(count($eventData)<2){
throw new \Exception('回调事件数据不完整');
}
$access_token = self::getAccessToken();
$url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $access_token;
$client = new Client();
$data = [
'expire_seconds' => $expire,
'action_name' => $action_name,
'action_info' => [
'scene' => []
]
];
if (in_array($action_name, [self::QR_SCENE, self::QR_LIMIT_SCENE])) {
$data['action_info']['scene'] = ['scene_id' => $id];
} else {
$data['action_info']['scene'] = ['scene_str' => $id];
}
Redis::set($id, json_encode($eventData, JSON_UNESCAPED_UNICODE), 'EX', $expire + 60);
$response = $client->post($url, [
'json' => $data
]);
$res = json_decode($response->getBody()->getContents(), true);
if (isset($res['errcode'])) {
throw new \Exception($res['errmsg']);
}
$res['id'] = $id;
return $res;
}
/**
* 发送模板消息
*
* @param mixed $params 模板消息参数
* @return string
*/
public static function sendTemplate($params)
{
$access_token = self::getAccessToken();
$url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token;
$client = new Client([
'content_type' => 'application/json',
]);
$response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]);
$data = json_decode($response->getBody()->getContents(), true);
if (empty($data['msgid'])) {
throw new \Exception("[{$data['errcode']}]" . $data['errmsg']);
}
return $data['msgid'];
}
}
@@ -0,0 +1,142 @@
<?php
namespace app\expose\middleware;
use app\expose\enum\ResponseCode;
use app\expose\enum\State;
use app\expose\trait\Json;
use app\model\Admin;
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 AdminAuth implements MiddlewareInterface
{
use Json;
public function process(Request $request, callable $next): Response
{
if ($request->method() == 'OPTIONS') {
return response('', 204);
}
# 微信浏览器
$this->isWehcatBrowser($request);
// 鉴权检测
try {
$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 isWehcatBrowser($request)
{
$request->isWehcatBrowser = strpos(strtolower($request->header('user-agent') ?? ''), 'micromessenger') !== false;
}
/**
* 业务逻辑
* @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'] ?? [];
# 无需鉴权方法
$notNeedAuth = $properties['notNeedAuth'] ?? [];
# 是否强制登录
$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('ADMIN')->decrypt($token);
if (!$user) {
throw new TokenExpireException();
}
$request->admin_uid = $user['uid'];
} 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 ($request->admin_uid) {
if ($user['is_system']) {
return true;
} else {
$now = date('H:i:s');
if (!($now >= $user['allow_time_start'] && $now <= $user['allow_time_end'])) {
throw new Exception("当前不在工作时间");
}
if (!in_array(date('w'), $user['allow_week'])) {
throw new Exception("今日因该是休息日哦");
}
$Admin = Admin::where('id', $user['uid'])->find();
if ($Admin->state != State::YES['value']) {
throw new Exception("您的账号已被禁用", ResponseCode::NEED_LOGIN);
}
}
}
# 不需要鉴权
if (in_array($request->action, $notNeedAuth)) {
return true;
}
if (empty($user)) {
throw new Exception('请先登录', ResponseCode::NEED_LOGIN);
}
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,59 @@
<?php
namespace app\expose\middleware;
use app\expose\enum\Platform as EnumPlatform;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* 用到支付必须引用继承这个中间件
*/
class Platform implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
$platform = $request->header('x-platform');
if ($platform &&EnumPlatform::has($platform)) {
$request->platform = $platform;
}
$this->isMobile($request);
if ($request->isMobile) {
$request->platform = EnumPlatform::H5['value'];
}
# 微信浏览器
$this->isWehcatBrowser($request);
# 检测多语言
$this->languare($request);
$response = $next($request);
return $response;
}
public function isWehcatBrowser($request)
{
$request->isWehcatBrowser = strpos(strtolower($request->header('user-agent')??''), 'micromessenger') !== false;
if($request->isWehcatBrowser&&$request->platform==EnumPlatform::H5['value']){
$request->platform = EnumPlatform::WECHAT_OFFICIAL_ACCOUNT['value'];
}
}
public function isMobile($request)
{
$request->isMobile = strpos(strtolower($request->header('user-agent')??''), 'mobile') !== false;
}
public function languare($request)
{
$lang=$request->header('lang');
if(!$lang){
$AcceptLanguage=$request->header('accept-language');
if($AcceptLanguage){
$AcceptLanguageArr=explode(',',$AcceptLanguage);
if(isset($AcceptLanguageArr[0])){
$lang=$AcceptLanguageArr[0];
}
}
}
$request->lang='zh-CN';
if($lang){
$request->lang=$lang;
}
}
}
@@ -0,0 +1,27 @@
<?php
namespace app\expose\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
use support\View;
/**
* 如需要使用模板引擎,可以继承此中间件
*/
class Template implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
$webInfo = [
'title' => 'Webman',
'keywords' => 'Webman,PHP',
'description' => 'Webman is a high-performance, component-based PHP framework for web applications.',
];
View::assign('web', $webInfo);
/** @var Response $response */
$response = $next($request);
return $response;
}
}
@@ -0,0 +1,13 @@
<?php
namespace app\expose\template\email;
use app\expose\utils\DataModel;
class Vcode extends DataModel
{
public function builder($username)
{
return "您的验证码是:1234";
}
}
@@ -0,0 +1,19 @@
<?php
namespace app\expose\template\sms;
use app\expose\helper\ConfigGroup;
use app\expose\utils\DataModel;
class Basic extends DataModel
{
protected $data;
public function __construct($channels = null)
{
if ($channels) {
$config = new ConfigGroup('sms', '');
$this->data['channels'] = $channels;
$this->data['config'] = $config[$channels];
}
}
}
@@ -0,0 +1,10 @@
<?php
namespace app\expose\template\sms;
class Vcode extends Basic
{
public function builder()
{
}
}
@@ -0,0 +1,75 @@
<?php
namespace app\expose\trait;
use app\expose\helper\Config as HelperConfig;
use app\model\Config as ModelConfig;
use app\expose\enum\ResponseEvent;
use app\expose\helper\ConfigGroup;
/**
* 使用该抽象类需要在控制器中引入以下代码
*
* use app\expose\trait\Config;
* use app\expose\trait\Json;
*
* Class Config
* {
* use Config;
* use Json;
* }
*/
trait Config
{
public $channels_uid=null;
/**
* 配置管理
* @return mixed
*/
private function builder($callback = null)
{
$request = request();
if ($request->method() === 'POST') {
return $this->update($callback);
}
$builder = HelperConfig::formBuilder($request->action, null, $this->channels_uid);
return $this->resData($builder);
}
private function tabsBuilder($callback = null)
{
$request = request();
if ($request->method() === 'POST') {
return $this->update($callback);
}
$builder = ConfigGroup::formBuilder($request->action, null, $this->channels_uid);
return $this->resData($builder);
}
private function update($callback = null)
{
$request = request();
$HelperConfig = new HelperConfig($request->action, null, $this->channels_uid);
$group = $HelperConfig->getGrop();
$D = $request->post();
$where = [];
$where[] = ['group', '=', $group];
if ($this->channels_uid) {
$where[] = ['channels_uid', '=', $this->channels_uid];
}
$ConfigModel = ModelConfig::where($where)->find();
if (!$ConfigModel) {
$ConfigModel = new ModelConfig;
$ConfigModel->group = $group;
if ($this->channels_uid) {
$ConfigModel->channels_uid = $this->channels_uid;
}
}
$ConfigModel->value = $D;
if ($ConfigModel->save()) {
if ($callback) {
$callback($D);
}
return $this->event(ResponseEvent::UPDATE_WEBCONFIG, '保存成功');
}
return $this->fail('保存失败');
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace app\expose\trait;
use app\expose\enum\ResponseCode;
use app\expose\exception\Exception;
use support\Response;
/**
* JSON响应工具
*/
trait Json
{
/**
* 返回成功JSON
*
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function success($msg = 'success', $data = [])
{
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回失败JSON
*
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function fail(string $msg = 'fail', $data = [])
{
return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回数据JSON
*
* @param mixed $data 数据
* @return Response
*/
protected static function resData($data)
{
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]);
}
protected static function event($event, $msg = 'success')
{
return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [
'event' => $event
]);
}
/**
* 返回自定义JSON
*
* @param int $code 状态码
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function code($code, $msg = null, $data = [])
{
return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回异常消息
*
* @param \Throwable $th 捕获的异常
* @return Response
*/
protected static function exception($th)
{
$data = [];
if (config('app.debug')) {
$data = [
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace(),
];
}
if ($th instanceof Exception) {
$data = array_merge($data, $th->getData());
}
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]);
}
/**
* 响应失败
*
* @param \Throwable $th 捕获的异常
* @return Response
*/
protected static function server($th, $http_code = 500)
{
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code);
}
/**
* 返回JSON
*
* @param mixed $data JSON数据
* @param int|null $options JSON编码
* @param int $http_code 服务器响应代码
* @return Response
*/
protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200)
{
$request = request();
if ($request && $request->lang) {
$data['msg'] = trans($data['msg'], [], null, $request->lang);
}
return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options));
}
}
@@ -0,0 +1,277 @@
<?php
namespace app\expose\trait;
use app\expose\enum\Filesystem;
use app\model\Uploads as ModelUploads;
use app\model\UploadsClassify;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use support\Log;
use support\Request;
trait Uploads
{
protected $admin_uid;
protected $uid;
public function getList(Request $request)
{
$limit = $request->get('limit', 10);
$dir_name = $request->get('dir_name', 'all');
$where = [];
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 = Storage::adapter($item->channels)->url($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');
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
if (!$UploadsClassify) {
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->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('plugin.shortplay.filesystem.app.default');
$UploadsClassify->save();
}
}
$channels = $request->post('channels');
if (!$channels) {
$channels = $UploadsClassify->channels;
}
$date_path = date('Ymd');
//单文件上传
$file = $request->file('file');
try {
$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->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');
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
if (!$UploadsClassify) {
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->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('plugin.shortplay.filesystem.app.default');
$UploadsClassify->save();
}
}
$channels = $request->post('channels');
if (!$channels) {
$channels = $UploadsClassify->channels;
}
$date_path = date('Ymd');
$files = $request->file();
try {
$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->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)->select();
foreach ($data as $item) {
if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) {
continue;
}
try {
$filesystem = FilesystemFactory::get($item->channels);
$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)->find();
if (!$UploadsClassify) {
return $this->fail('分类不存在');
}
$data = ModelUploads::whereIn('id', $ids)->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)
{
if (!UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->count()) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->title = '默认分类';
$UploadsClassify->dir_name = 'uploads/default';
$UploadsClassify->channels = Filesystem::PUBLIC['value'];
$UploadsClassify->sort = 0;
$UploadsClassify->is_system = 1;
$UploadsClassify->save();
}
$UploadsClassify = UploadsClassify::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'])->find();
if (!$UploadsClassify) {
$UploadsClassify = new UploadsClassify;
$UploadsClassify->dir_name = 'uploads/' . $data['dir_name'];
}
$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)->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,86 @@
<?php
namespace app\expose\utils;
use ArrayAccess;
use JsonSerializable;
/**
* 数据序列化模型
*
* 继承DataModel必须实现属性$data
* @package app\utils
* @property mixed $data
* @method mixed __get(string $name)
* @method void __set(string $name, mixed $value)
* @method bool __isset(string $name)
* @method void __unset(string $name)
* @method string __toString()
* @method mixed offsetGet(mixed $offset)
* @method void offsetSet(mixed $offset, mixed $value)
* @method bool offsetExists(mixed $offset)
* @method void offsetUnset(mixed $offset)
* @method array toArray()
* @method string toJson()
* @method array jsonSerialize()
*/
class DataModel implements ArrayAccess, JsonSerializable
{
protected $data = [];
public function __construct($data=[])
{
$this->data=$data;
}
public function __get($name)
{
return $this->data[$name] ?? null;
}
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __isset($name)
{
return isset($this->data[$name]);
}
public function __unset($name)
{
unset($this->data[$name]);
}
public function __toString()
{
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
}
public function offsetGet(mixed $offset):mixed
{
return $this->data[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function offsetExists(mixed $offset): bool
{
return $this->__isset($offset);
}
public function offsetUnset(mixed $offset): void
{
$this->__unset($offset);
}
public function toArray()
{
return $this->data;
}
public function toJson()
{
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
}
public function jsonSerialize(): array
{
return $this->data;
}
}
@@ -0,0 +1,27 @@
<?php
namespace app\expose\utils;
class Email
{
public $toemail;
protected $template;
protected $data;
public function setTemplate($template)
{
$this->template = new $template;
return $this;
}
public function setData($data)
{
$this->data = $data;
return $this;
}
public function send()
{
$content = $this->template->builder($this->toemail, $this->data);
p($content);
return true;
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace app\expose\utils;
use app\expose\enum\ResponseCode;
use support\Response;
/**
* JSON响应工具
* @deprecated 请使用app\expose\trait\Json替代
*/
trait Json
{
/**
* 返回成功JSON
*
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function success($msg = 'success', $data = [])
{
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回失败JSON
*
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function fail(string $msg = 'fail', $data = [])
{
return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回数据JSON
*
* @param mixed $data 数据
* @return Response
*/
protected static function resData($data)
{
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]);
}
protected static function event($event, $msg = 'success')
{
return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [
'event' => $event
]);
}
/**
* 返回自定义JSON
*
* @param int $code 状态码
* @param string $msg 消息
* @param mixed $data 数据
* @return Response
*/
protected static function code($code, $msg = null, $data = [])
{
return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]);
}
/**
* 返回异常消息
*
* @param \Throwable $th 捕获的异常
* @return Response
*/
protected static function exception($th)
{
$data = [];
if (config('app.debug')) {
$data = [
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace(),
];
}
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]);
}
/**
* 响应失败
*
* @param \Throwable $th 捕获的异常
* @return Response
*/
protected static function server($th, $http_code = 500)
{
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code);
}
/**
* 返回JSON
*
* @param mixed $data JSON数据
* @param int|null $options JSON编码
* @param int $http_code 服务器响应代码
* @return Response
*/
protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200)
{
$request=request();
if($request&&$request->lang){
$data['msg']=trans($data['msg'],[], null, $request->lang);
}
return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options));
}
}
@@ -0,0 +1,30 @@
<?php
namespace app\expose\utils;
class Password
{
/**
* 获取密码散列值
*
* @param string $password
* @param [type] $algo
* @return string
*/
public static function encrypt(string $password, string $algo = PASSWORD_DEFAULT): string
{
return password_hash($password, $algo);
}
/**
* 验证密码
*
* @param string $password
* @param string $hash
* @return boolean
*/
public static function verify(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
}
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace app\expose\utils;
use \Exception;
/**
* RAS加解密
*/
class Rsa
{
/**
* 解密
*
* @param string 加密后的字符串
* @param string 私钥,可以是文件路径
* @return object
*/
public static function decrypt($token, $rsa_privatekey)
{
if (file_exists($rsa_privatekey)) {
$rsa_privatekey = @file_get_contents($rsa_privatekey);
}
if (empty($rsa_privatekey)) {
throw new Exception('私钥为空');
}
$split = str_split($token, 172); // 1024 bit 固定172
$crypto = '';
foreach ($split as $chunk) {
$isOkay = openssl_private_decrypt(base64_decode($chunk), $decryptData, $rsa_privatekey); // base64在这里使用,因为172字节是一组,是encode来的
if (!$isOkay) {
throw new Exception("解密失败");
}
$crypto .= $decryptData;
}
if (!$crypto) {
throw new Exception("解密失败");
}
return json_decode(base64_decode($crypto));
}
/**
* 加密
*
* @param mixed 可被json序列化的数据
* @param string 公钥,可以是文件路径
* @return string
*/
public static function encrypt(mixed $data, string $rsa_publickey)
{
if (file_exists($rsa_publickey)) {
$rsa_publickey = @file_get_contents($rsa_publickey);
}
if (empty($rsa_publickey)) {
throw new Exception('公钥为空');
}
$data = base64_encode(json_encode($data));
$split = str_split($data, 117); // 1024 bit && OPENSSL_PKCS1_PADDING 不大于117即可
$crypto = '';
foreach ($split as $chunk) {
$isOkay = openssl_public_encrypt($chunk, $encryptData, $rsa_publickey);
if (!$isOkay) {
throw new Exception("加密失败");
}
$crypto .= base64_encode($encryptData);
}
return $crypto;
}
/**
* 创建一对公钥私钥
*
* @return array
*/
public static function createKey()
{
$res = openssl_pkey_new([
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA
]);
openssl_pkey_export($res, $privKey);
$pubKey = openssl_pkey_get_details($res);
return [
'privatekey' => $privKey,
'publickey' => $pubKey["key"]
];
}
# 以下是数字加密解密
const PASSWORD = 'MAW512P89WIAUW9G7OHQWVGAPX51WPSN';
/**
* 数字加密函数
*
* @param int $number
* @param string $password
* @return string
*/
public static function encryptNumber(int $number, string $password = '')
{
// 生成随机的初始向量
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$password = $password ?: self::PASSWORD;
// 加密数字
$encrypted = openssl_encrypt($number, 'aes-256-cbc', $password, 0, $iv);
// 将初始向量和加密后的数据进行编码并拼接起来
$encoded = base64_encode($iv . $encrypted);
return $encoded;
}
/**
* 数字解密函数
*
* @param string $encrypted
* @param string $password
* @return int
*/
public static function decryptNumber(string $encrypted, string $password = '')
{
// 解码加密后的数据
$decoded = base64_decode($encrypted);
// 提取初始向量和加密后的数据
$ivLength = openssl_cipher_iv_length('aes-256-cbc');
$iv = substr($decoded, 0, $ivLength);
$encryptedData = substr($decoded, $ivLength);
$password = $password ?: self::PASSWORD;
// 解密数据
$decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $password, 0, $iv);
return $decrypted;
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace app\expose\utils;
use app\expose\enum\SmsChannels;
use Exception;
use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi;
use Darabonba\OpenApi\Models\Config;
use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
class Sms
{
public $mobile;
protected $data;
protected $template;
public function setTemplate($template)
{
# 判断$template是否已经new过
if (is_object($template)) {
$this->template = $template;
return $this;
}
$this->template = new $template;
return $this;
}
public function setData($data)
{
$this->data = $data;
return $this;
}
public function send()
{
$this->template->builder($this->mobile, $this->data);
switch ($this->template->channel) {
case SmsChannels::ALIYUN['value']:
$this->sendAliyun();
break;
case SmsChannels::TENCENT['value']:
$this->sendTencent();
break;
case SmsChannels::SMSBAO['value']:
$this->sendSmsbao();
break;
default:
throw new Exception("未知的短信服务商");
}
}
public function sendAliyun()
{
if (empty($this->template->config)) {
throw new Exception("阿里云短信服务商配置不完整");
}
$config = new Config([
"accessKeyId" => $this->template->config['access_key_id'],
"accessKeySecret" => $this->template->config['access_secret'],
]);
$config->endpoint = "dysmsapi.aliyuncs.com";
$client = new Dysmsapi($config);
$request = new SendSmsRequest([
"phoneNumbers" => $this->mobile,
"templateCode" => $this->template->template_code,
"templateParam" => json_encode($this->data, JSON_UNESCAPED_UNICODE),
"signName" => $this->template->config['sign_name']
]);
$runtime = new RuntimeOptions();
$runtime->maxIdleConns = 3;
$runtime->connectTimeout = 10000;
$runtime->readTimeout = 10000;
// 复制代码运行请自行打印 API 的返回值
$res = $client->sendSms($request, $runtime);
if ($res->body->code == 'OK') {
return true;
}
if ($res->body->message) {
throw new Exception($res->body->message);
}
throw new Exception("发送失败");
}
public function sendTencent()
{
throw new Exception("error");
}
public function sendSmsbao()
{
throw new Exception("error");
}
}
+262
View File
@@ -0,0 +1,262 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace app\expose\utils;
class Str
{
protected static $snakeCache = [];
protected static $camelCache = [];
protected static $studlyCache = [];
/**
* 检查字符串中是否包含某些字符串
* @param string $haystack
* @param string|array $needles
* @return bool
*/
public static function contains(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if ('' != $needle && mb_strpos($haystack, $needle) !== false) {
return true;
}
}
return false;
}
/**
* 检查字符串是否以某些字符串结尾
*
* @param string $haystack
* @param string|array $needles
* @return bool
*/
public static function endsWith(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if ((string) $needle === static::substr($haystack, -static::length($needle))) {
return true;
}
}
return false;
}
/**
* 检查字符串是否以某些字符串开头
*
* @param string $haystack
* @param string|array $needles
* @return bool
*/
public static function startsWith(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if ('' != $needle && mb_strpos($haystack, $needle) === 0) {
return true;
}
}
return false;
}
/**
* 获取指定长度的随机字母数字组合的字符串
*
* @param int $length
* @param int $type
* @param string $addChars
* @return string
*/
public static function random(int $length = 6, int $type = null, string $addChars = ''): string
{
$str = '';
switch ($type) {
case 0:
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
break;
case 1:
$chars = str_repeat('0123456789', 3);
break;
case 2:
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
break;
case 3:
$chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
break;
case 4:
$chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars;
break;
default:
$chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
break;
}
if ($length > 10) {
$chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5);
}
if ($type != 4) {
$chars = str_shuffle($chars);
$str = substr($chars, 0, $length);
} else {
for ($i = 0; $i < $length; $i++) {
$str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1);
}
}
return $str;
}
/**
* 字符串转小写
*
* @param string $value
* @return string
*/
public static function lower(string $value): string
{
return mb_strtolower($value, 'UTF-8');
}
/**
* 字符串转大写
*
* @param string $value
* @return string
*/
public static function upper(string $value): string
{
return mb_strtoupper($value, 'UTF-8');
}
/**
* 获取字符串的长度
*
* @param string $value
* @return int
*/
public static function length(string $value): int
{
return mb_strlen($value);
}
/**
* 截取字符串
*
* @param string $string
* @param int $start
* @param int|null $length
* @return string
*/
public static function substr(string $string, int $start, int $length = null): string
{
return mb_substr($string, $start, $length, 'UTF-8');
}
/**
* 驼峰转下划线
*
* @param string $value
* @param string $delimiter
* @return string
*/
public static function snake(string $value, string $delimiter = '_'): string
{
$key = $value;
if (isset(static::$snakeCache[$key][$delimiter])) {
return static::$snakeCache[$key][$delimiter];
}
if (!ctype_lower($value)) {
$value = preg_replace('/\s+/u', '', ucwords($value));
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
}
return static::$snakeCache[$key][$delimiter] = $value;
}
/**
* 下划线转驼峰(首字母小写)
*
* @param string $value
* @return string
*/
public static function camel(string $value): string
{
if (isset(static::$camelCache[$value])) {
return static::$camelCache[$value];
}
return static::$camelCache[$value] = lcfirst(static::studly($value));
}
/**
* 下划线转驼峰(首字母大写)
*
* @param string $value
* @return string
*/
public static function studly(string $value): string
{
$key = $value;
if (isset(static::$studlyCache[$key])) {
return static::$studlyCache[$key];
}
$value = ucwords(str_replace(['-', '_'], ' ', $value));
return static::$studlyCache[$key] = str_replace(' ', '', $value);
}
/**
* 转为首字母大写的标题格式
*
* @param string $value
* @return string
*/
public static function title(string $value): string
{
return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
}
/**
* 手机号,邮箱,脱敏
* @param string|null $value
* @return string|null
*/
public static function mask(?string $value): ?string
{
if (empty($value)) {
return null;
}
if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
$arr = explode('@', $value);
$name = $arr[0];
$domain = $arr[1];
// 如果长度小于等于4,就直接显示
if (strlen($name) <= 4) {
$masked = $name;
} else {
$masked = substr($name, 0, 2) . '****' . substr($name, -2);
}
return $masked . '@' . $domain;
} else {
return substr($value, 0, 3) . '****' . substr($value, -4);
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace app\expose\utils;
use app\expose\utils\DataModel;
class TreeModel extends DataModel
{
private function _organizeRecords($regions,$idKey)
{
$organizedRegions = [];
foreach ($regions as $region) {
$organizedRegions[$region[$idKey]] = $region;
$organizedRegions[$region[$idKey]]['children'] = [];
}
return $organizedRegions;
}
private function _buildTree($organized,$pidKey)
{
$tree = [];
foreach ($organized as $id => $record) {
if ($record[$pidKey]) {
$organized[$record[$pidKey]]['children'][] = &$organized[$id];
} else {
$tree[] = &$organized[$id];
}
}
return $tree;
}
public function toTree($idKey='id',$pidKey='pid')
{
$organized=$this->_organizeRecords($this->data,$idKey);
$data=$this->_buildTree($organized,$pidKey);
return $data;
}
}
@@ -0,0 +1,251 @@
<?php
namespace app\expose\utils;
use Exception;
/**
* PHP Class for handling Google Authenticator 2-factor authentication.
*
* @author Michael Kliewe
* @copyright 2012 Michael Kliewe
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*
* @link http://www.phpgangsta.de/
*/
class TwofaAuthenticator
{
protected $_codeLength = 6;
/**
* Create new secret.
* 16 characters, randomly chosen from the allowed base32 characters.
*
* @param int $secretLength
*
* @return string
*/
public function createSecret($secretLength = 16)
{
$validChars = $this->_getBase32LookupTable();
// Valid secret lengths are 80 to 640 bits
if ($secretLength < 16 || $secretLength > 128) {
throw new Exception('Bad secret length');
}
$secret = '';
$rnd = false;
if (function_exists('random_bytes')) {
$rnd = random_bytes($secretLength);
} elseif (function_exists('mcrypt_create_iv')) {
$rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
if (!$cryptoStrong) {
$rnd = false;
}
}
if ($rnd !== false) {
for ($i = 0; $i < $secretLength; ++$i) {
$secret .= $validChars[ord($rnd[$i]) & 31];
}
} else {
throw new Exception('No source of secure random');
}
return $secret;
}
/**
* Calculate the code, with given secret and point in time.
*
* @param string $secret
* @param int|null $timeSlice
*
* @return string
*/
public function getCode($secret, $timeSlice = null)
{
if ($timeSlice === null) {
$timeSlice = floor(time() / 30);
}
$secretkey = $this->_base32Decode($secret);
// Pack time into binary string
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
// Hash it with users secret key
$hm = hash_hmac('SHA1', $time, $secretkey, true);
// Use last nipple of result as index/offset
$offset = ord(substr($hm, -1)) & 0x0F;
// grab 4 bytes of the result
$hashpart = substr($hm, $offset, 4);
// Unpak binary value
$value = unpack('N', $hashpart);
$value = $value[1];
// Only 32 bits
$value = $value & 0x7FFFFFFF;
$modulo = pow(10, $this->_codeLength);
return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
}
/**
* Get QR-Code URL for image, from google charts.
*
* @param string $name
* @param string $secret
* @param string $title
* @param array $params
*
* @return string
*/
public function getQRCode($name, $secret, $title = null)
{
$urlencoded = 'otpauth://totp/'.$name.'?secret='.$secret;
if (isset($title)) {
$urlencoded .= '&issuer='.urlencode($title);
}
return $urlencoded;
}
/**
* Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now.
*
* @param string $secret
* @param string $code
* @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
* @param int|null $currentTimeSlice time slice if we want use other that time()
*
* @return bool
*/
public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
{
if ($currentTimeSlice === null) {
$currentTimeSlice = floor(time() / 30);
}
if (strlen($code) != 6) {
return false;
}
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
if ($this->timingSafeEquals($calculatedCode, $code)) {
return true;
}
}
return false;
}
/**
* Set the code length, should be >=6.
*
* @param int $length
*
* @return PHPGangsta_GoogleAuthenticator
*/
public function setCodeLength($length)
{
$this->_codeLength = $length;
return $this;
}
/**
* Helper class to decode base32.
*
* @param $secret
*
* @return bool|string
*/
protected function _base32Decode($secret)
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$base32charsFlipped = array_flip($base32chars);
$paddingCharCount = substr_count($secret, $base32chars[32]);
$allowedValues = array(6, 4, 3, 1, 0);
if (!in_array($paddingCharCount, $allowedValues)) {
return false;
}
for ($i = 0; $i < 4; ++$i) {
if ($paddingCharCount == $allowedValues[$i] &&
substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) {
return false;
}
}
$secret = str_replace('=', '', $secret);
$secret = str_split($secret);
$binaryString = '';
for ($i = 0; $i < count($secret); $i = $i + 8) {
$x = '';
if (!in_array($secret[$i], $base32chars)) {
return false;
}
for ($j = 0; $j < 8; ++$j) {
$x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
for ($z = 0; $z < count($eightBits); ++$z) {
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : '';
}
}
return $binaryString;
}
/**
* Get array with all 32 characters for decoding from/encoding to base32.
*
* @return array
*/
protected function _getBase32LookupTable()
{
return array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
'=', // padding char
);
}
/**
* A timing safe equals comparison
* more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
*
* @param string $safeString The internal (safe) value to be checked
* @param string $userString The user submitted (unsafe) value
*
* @return bool True if the two strings are identical
*/
private function timingSafeEquals($safeString, $userString)
{
if (function_exists('hash_equals')) {
return hash_equals($safeString, $userString);
}
$safeLen = strlen($safeString);
$userLen = strlen($userString);
if ($userLen != $safeLen) {
return false;
}
$result = 0;
for ($i = 0; $i < $userLen; ++$i) {
$result |= (ord($safeString[$i]) ^ ord($userString[$i]));
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
}
@@ -0,0 +1,57 @@
<?php
namespace app\expose\utils\wechat;
use app\expose\utils\wechat\modules\Event;
use app\expose\utils\wechat\modules\Reply;
use support\Log;
class OfficialAccount
{
use Reply;
use Event;
public function handle($request, $config)
{
$data = $this->getData($request, $config);
if ($data['MsgType'] === 'event') {
return $this->handleEvent($data);
} else {
return $this->replyText($data, 'hello world');
}
}
public function checkSignature($request, $config)
{
$signature = $request->get("signature");
$timestamp = $request->get("timestamp");
$nonce = $request->get("nonce");
$token = $config['token'];
$tmpArr = [$token, $timestamp, $nonce];
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);
if ($tmpStr == $signature) {
return true;
}
throw new \Exception('signature error');
}
private function getData($request, $config)
{
$xml = $request->rawBody();
$json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$data = json_decode(json_encode($json), true);
if ($request->get('encrypt_type') === 'aes') {
$Encrypt = base64_decode($data['Encrypt']);
$aes_key = $config['aes_key'];
$aes_key = base64_decode($aes_key . '=');
$iv = substr($aes_key, 0, 16);
$xml = openssl_decrypt($Encrypt, 'aes-256-cbc', $aes_key, OPENSSL_RAW_DATA, $iv);
$filterHeader = substr($xml, 20);
$xml = preg_replace('/' . $config['app_id'] . '/', '', $filterHeader);
$json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$data = json_decode(json_encode($json), true);
return $data;
} else {
return $data;
}
}
}
@@ -0,0 +1,73 @@
<?php
namespace app\expose\utils\wechat\modules;
use app\expose\enum\EventName;
use support\Redis;
use Webman\Event\Event as EventEvent;
trait Event
{
private function handleEvent($data)
{
switch ($data['Event']) {
// case 'subscribe':
// EventEvent::emit(EventName::WECHAT_OFFICIAL_ACCOUNT_SUBSCRLBE['value'],$data);
// return $this->replyText($data,'欢迎关注');
// case 'unsubscribe':
// EventEvent::emit(EventName::WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE['value'],$data);
// return $this->replyText($data,'路远,再会。');
case 'subscribe':
case 'unsubscribe':
case 'SCAN':
$scene = str_replace('qrscene_', '', $data['EventKey']);
$Scene = Redis::get($scene);
if ($Scene) {
$Scene = json_decode($Scene, true);
if (count($Scene) < 2) {
return $this->replyText($data, '二维码参数错误');
}
$class = $Scene[0];
$method = $Scene[1];
if (class_exists($class)) {
$class = new $class();
$class->FromUserName = $data['FromUserName'];
$class->ToUserName = $data['ToUserName'];
$class->MsgType = $data['MsgType'];
$class->Event = $data['Event'];
$class->EventKey = $scene;
$class->data = $data;
if (method_exists($class, $method)) {
$params = null;
if (isset($Scene[2])) {
$params = $Scene[2];
}
return $this->replyText($data, $class->$method($params));
}
}
return $this->replyText($data, '扫码成功,但未找到对应处理类或方法');
}
return $this->replyText($data, '二维码已过期');
case 'LOCATION':
return $this->replyText($data, '上报地理位置');
case 'CLICK':
return $this->replyText($data, '点击菜单');
case 'VIEW':
return $this->replyText($data, '点击菜单跳转链接');
case 'scancode_push':
return $this->replyText($data, '扫码推事件');
case 'scancode_waitmsg':
return $this->replyText($data, '扫码推事件且弹出“消息接收中”提示框');
case 'pic_sysphoto':
return $this->replyText($data, '弹出系统拍照发图');
case 'pic_photo_or_album':
return $this->replyText($data, '弹出拍照或者相册发图');
case 'pic_weixin':
return $this->replyText($data, '弹出微信相册发图器');
case 'location_select':
return $this->replyText($data, '弹出地理位置选择器');
case 'view_miniprogram':
return $this->replyText($data, '点击菜单跳转小程序');
}
}
}
@@ -0,0 +1,94 @@
<?php
namespace app\expose\utils\wechat\modules;
trait Reply
{
private function replyText($data,$content)
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[text]]>',
'Content' => "<![CDATA[{$content}]]>"
];
return xml(arrayToXml($result));
}
private function replyImage($data,$media_id)
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[image]]>',
'Image' => [
'MediaId' => "<![CDATA[{$media_id}]]>"
]
];
return xml(arrayToXml($result));
}
private function replyVoice($data,$media_id)
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[voice]]>',
'Voice' => [
'MediaId' => "<![CDATA[{$media_id}]]>"
]
];
return xml(arrayToXml($result));
}
private function replyVideo($data,$media_id,$title='',$description='')
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[video]]>',
'Video' => [
'MediaId' => "<![CDATA[{$media_id}]]>",
'Title' => "<![CDATA[{$title}]]>",
'Description' => "<![CDATA[{$description}]]>"
]
];
return xml(arrayToXml($result));
}
private function replyMusic($data,$thumb_media_id,$title='',$description='',$music_url='',$hq_music_url='')
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[music]]>',
'Music' => [
'Title' => "<![CDATA[{$title}]]>",
'Description' => "<![CDATA[{$description}]]>",
'MusicUrl' => "<![CDATA[{$music_url}]]>",
'HQMusicUrl' => "<![CDATA[{$hq_music_url}]]>",
'ThumbMediaId' => "<![CDATA[{$thumb_media_id}]]>"
]
];
return xml(arrayToXml($result));
}
private function replyNews($data,$articles)
{
$result=[
'ToUserName' => "<![CDATA[{$data['FromUserName']}]]>",
'FromUserName' => "<![CDATA[{$data['ToUserName']}]]>",
'CreateTime' => time(),
'MsgType' => '<![CDATA[news]]>',
'ArticleCount' => count($articles),
'Articles' => []
];
foreach ($articles as $article) {
$result['Articles'][]=[
'Title' => "<![CDATA[{$article['title']}]]>",
'Description' => "<![CDATA[{$article['description']}]]>",
'PicUrl' => "<![CDATA[{$article['picurl']}]]>",
'Url' => "<![CDATA[{$article['url']}]]>"
];
}
return xml(arrayToXml($result));
}
}
@@ -0,0 +1,64 @@
<?php
namespace app\expose\validate;
use think\facade\Db;
use think\Validate as ThinkValidate;
class Validate extends ThinkValidate
{
protected $failException = true;
protected $db;
/**
* 设置Db对象
* @access public
* @param Db $db Db对象
* @return void
*/
public function setDb($db)
{
$this->db = $db;
}
public function unique($value, $rule, array $data = [], string $field = ''): bool
{
if (is_string($rule)) {
$rule = explode(',', $rule);
}
$this->setDb(new Db); //设置Db对象
if (false !== strpos($rule[0], '\\')) {
// 指定模型类
$db = new $rule[0];
} else {
$db = $this->db::name($rule[0]);
}
$map = [];
if (isset($data[$field])) {
$map[] = [$field, '=', $data[$field]];
}
if (!empty($rule[1]) && strpos($rule[1], '^')) {
// 支持多个字段验证
$fields = explode('^', $rule[1]);
foreach ($fields as $key) {
if (isset($data[$key])) {
$map[] = [$key, '=', $data[$key]];
}
}
}
$pk = !empty($rule[3]) ? $rule[3] : $db->getPk();
if (is_string($pk)) {
if (isset($rule[2])) {
$map[] = [$pk, '<>', $rule[2]];
} elseif (isset($data[$pk])) {
$map[] = [$pk, '<>', $data[$pk]];
}
}
if ($db->where($map)->field($pk)->count()) {
return false;
}
return true;
}
}