增加 Github Actions 脚本

This commit is contained in:
邹景立 2024-08-02 00:55:31 +08:00
commit ce7375ce87
719 changed files with 74292 additions and 0 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.js linguist-language=php
*.css linguist-language=php
*.html linguist-language=php

26
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,26 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
permissions: write-all
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

27
.github/workflows/split.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Split Repos
on: [ workflow_dispatch ]
jobs:
split:
if: github.repository == 'zoujingli/think-admin-developer'
runs-on: ubuntu-latest
env:
SSH_PRIVATE_KEY: ${{ secrets.SPLIT_PRIVATE_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Private Key
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
echo "StrictHostKeyChecking no" >> ~/.ssh/config
- name: Split And Push
run: |
git config pull.rebase true
git config --global user.name "Anyon"
git config --global user.email "zoujingli@qq.com"
./bin/split-linux.sh

49
.gitignore vendored Normal file
View File

@ -0,0 +1,49 @@
.env
.git
.svn
.idea
.fleet
.vscode
.DS_Store
/vendor
/runtime
/safefile
/nbproject
/composer.lock
!composer.json
### 屏蔽环境文件
/404.html
/.user.ini
/index.html
/public/upload
/public/404.html
/public/.htaccess
/public/.user.ini
/public/index.html
/public/favicon.ico
/database/sqlite.db
### 屏蔽插件文件
/app/admin
/app/wechat
### 屏蔽配置文件
/config/app.php
/config/cookie.php
/config/lang.php
/config/log.php
/config/route.php
/config/session.php
/config/view.php
/public/static/plugs
/public/static/theme
/public/static/admin.js
/public/static/login.js
### 屏蔽数据库脚本
/database/migrations/*_install_*.php
!database/migrations/*_install_*_table.php
!database/migrations/*_install_package.php

View File

@ -0,0 +1,27 @@
<?php
// +----------------------------------------------------------------------
// | Static Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-static
// | github 代码仓库https://github.com/zoujingli/think-plugs-static
// +----------------------------------------------------------------------
namespace app\index\controller;
use think\admin\Controller;
class Index extends Controller
{
public function index()
{
$this->redirect(sysuri('admin/login/index'));
}
}

54
bin/release.sh Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -e
if (( "$#" == 0 ))
then
echo "Tag has to be provided"
exit 1
fi
NOW=$(date +%s)
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
VERSION=$1
BASEPATH=$(cd `dirname $0`; cd ../plugin/; pwd)
# Always prepend with "v"
#if [[ $VERSION != v* ]]
#then
# VERSION="v$VERSION"
#fi
if [ -z $2 ] ; then
repos=$(ls $BASEPATH)
else
repos=${@:2}
fi
for REMOTE in $repos
do
echo ""
echo ""
echo "Cloning $REMOTE";
TMP_DIR="/tmp/ThinkAdminSplit"
REMOTE_URL="git@github.com:zoujingli/$REMOTE.git"
rm -rf $TMP_DIR;
mkdir $TMP_DIR;
(
cd $TMP_DIR;
git clone $REMOTE_URL .
git checkout "$CURRENT_BRANCH";
if [[ $(git log --pretty="%d" -n 1 | grep tag --count) -eq 0 ]]; then
echo "Releasing $REMOTE"
git tag $VERSION
echo "Git Push Origin --tags $VERSION"
# git push origin --tags
fi
)
done
TIME=$(echo "$(date +%s) - $NOW" | bc)
printf "Execution time: %f seconds" $TIME

27
bin/split-linux.sh Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
set -x
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
BASEPATH=$(cd `dirname $0`; cd ../plugin/; pwd)
REPOS=$@
function split()
{
SHA1=`./bin/splitsh-lite-linux --prefix=$1`
git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
}
function remote()
{
git remote add $1 $2 || true
}
git pull origin $CURRENT_BRANCH
if [[ $# -eq 0 ]]; then
REPOS=$(ls $BASEPATH)
fi
for REPO in $REPOS ; do
remote $REPO git@github.com:zoujingli/$REPO.git
split "plugin/$REPO" $REPO
done

30
bin/split.sh Executable file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -e
set -x
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
BASEPATH=$(cd `dirname $0`; cd ../plugin/; pwd)
REPOS=$@
function split()
{
SHA1=`./bin/splitsh-lite --prefix=$1`
git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
}
function remote()
{
git remote add $1 $2 || true
}
git pull origin $CURRENT_BRANCH
if [[ $# -eq 0 ]]; then
REPOS=$(ls $BASEPATH)
fi
for REPO in $REPOS ; do
remote $REPO git@github.com:zoujingli/$REPO.git
split "plugin/$REPO" $REPO
done

BIN
bin/splitsh-lite Executable file

Binary file not shown.

BIN
bin/splitsh-lite-linux Executable file

Binary file not shown.

78
composer.json Normal file
View File

@ -0,0 +1,78 @@
{
"type": "project",
"name": "zoujingli/thinkadmin",
"license": "MIT",
"homepage": "https://thinkadmin.top",
"description": "Application Development Framework",
"keywords": [
"ThinkAdmin",
"ThinkLibrary",
"WeChatDeveloper"
],
"authors": [
{
"name": "Anyon",
"email": "zoujingli@qq.com"
}
],
"require": {
"php": ">7.1",
"ext-gd": "*",
"ext-json": "*",
"ext-openssl": "*",
"zoujingli/think-plugs-wuma": "^1.0|@dev",
"zoujingli/think-plugs-admin": "^1.0|@dev",
"zoujingli/think-plugs-worker": "^1.0|@dev",
"zoujingli/think-plugs-center": "^1.0|@dev",
"zoujingli/think-plugs-wechat": "^1.0|@dev",
"zoujingli/think-plugs-wemall": "^1.0|@dev",
"zoujingli/think-plugs-account": "^1.0|@dev",
"zoujingli/think-plugs-payment": "^1.0|@dev",
"zoujingli/think-plugs-wechat-service": "^1.0|@dev"
},
"repositories": [
{
"type": "path",
"url": "plugin/think-plugs-wuma"
},
{
"type": "path",
"url": "plugin/think-plugs-worker"
},
{
"type": "path",
"url": "plugin/think-plugs-wechat"
},
{
"type": "path",
"url": "plugin/think-plugs-wechat-service"
},
{
"type": "path",
"url": "plugin/think-plugs-admin"
},
{
"type": "path",
"url": "plugin/think-plugs-center"
},
{
"type": "path",
"url": "plugin/think-plugs-account"
},
{
"type": "path",
"url": "plugin/think-plugs-payment"
},
{
"type": "path",
"url": "plugin/think-plugs-wemall"
}
],
"minimum-stability": "dev",
"config": {
"sort-packages": true,
"allow-plugins": {
"zoujingli/think-install": true
}
}
}

51
config/cache.php Normal file
View File

@ -0,0 +1,51 @@
<?php
// +----------------------------------------------------------------------
// | Static Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-static
// | github 代码仓库https://github.com/zoujingli/think-plugs-static
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => 'file',
// 缓存连接配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存名称前缀
'prefix' => '',
// 缓存有效期 0 表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制
'serialize' => [],
],
'safe' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => syspath('safefile/cache/'),
// 缓存名称前缀
'prefix' => '',
// 缓存有效期 0 表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制
'serialize' => [],
],
],
];

82
config/database.php Normal file
View File

@ -0,0 +1,82 @@
<?php
// +----------------------------------------------------------------------
// | Static Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-static
// | github 代码仓库https://github.com/zoujingli/think-plugs-static
// +----------------------------------------------------------------------
return [
// 默认使用的数据库连接配置
'default' => 'mysql',
// 自定义时间查询规则
'time_query_rule' => [],
// 自动写入时间戳字段
'auto_timestamp' => true,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 数据库连接配置信息
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '106.55.25.32',
// 数据库名
'database' => 'admin_dv',
// 用户名
'username' => 'admin_dv',
// 密码
'password' => '3twr7kNZLXeSGtc7',
// 端口
'hostport' => '3306',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用 utf8
'charset' => 'utf8mb4',
// 数据库表前缀
'prefix' => '',
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 检查字段是否存在
'fields_strict' => true,
// 是否需要断线重连
'break_reconnect' => false,
// 监听SQL执行日志
'trigger_sql' => true,
// 开启字段类型缓存
'fields_cache' => isOnline(),
],
'sqlite' => [
'charset' => 'utf8',
// 数据库类型
'type' => 'sqlite',
// 数据库文件
'database' => syspath('database/sqlite.db'),
// 监听执行日志
'trigger_sql' => true,
// 其他参数字段
'deploy' => 0,
'suffix' => '',
'prefix' => '',
'hostname' => '',
'hostport' => '',
'username' => '',
'password' => '',
],
],
];

24
config/phinx.php Normal file
View File

@ -0,0 +1,24 @@
<?php
// +----------------------------------------------------------------------
// | Static Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-static
// | github 代码仓库https://github.com/zoujingli/think-plugs-static
// +----------------------------------------------------------------------
return [
// 忽略数据表,填写表名
'ignore' => [],
// 创建数据表,填写表名
'tables' => [],
// 备份数据表,填写表名
'backup' => [],
];

57
config/worker.php Normal file
View File

@ -0,0 +1,57 @@
<?php
// +----------------------------------------------------------------------
// | Worker Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
// | 配置参考 ( https://www.workerman.net/doc/workerman/worker/properties.html )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-worker
// | github 代码仓库https://github.com/zoujingli/think-plugs-worker
// +----------------------------------------------------------------------
// | 配置参数参数https://www.workerman.net/doc/workerman/worker/properties.html
// +----------------------------------------------------------------------
return [
// 服务监听地址
'host' => '127.0.0.1',
// 服务监听端口
'port' => 2346,
// 套接字上下文选项
'context' => [],
// 高级自定义服务类
'classes' => '',
// 消息请求回调处理
'callable' => null,
// 服务进程参数配置
'worker' => [
'name' => 'ThinkAdmin',
'count' => 4,
],
// 监控文件变更重载
'files' => [
// 监控检测间隔(单位秒,零不监控)
'time' => 3,
// 文件监控目录(默认监控 app+config 目录)
'path' => [],
// 文件监控后缀(默认监控 所有 文件)
'exts' => ['*']
],
// 监控内存超限重载
'memory' => [
// 监控检测间隔(单位秒,零不监控)
'time' => 60,
// 限制内存大小(可选单位有 G M K
'limit' => '1G'
],
'customs' => [
'test' => [
'classes' => \plugin\worker\support\HttpServer::class
]
]
];

View File

@ -0,0 +1,441 @@
<?php
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallWechatTable extends Migrator {
/**
* 创建数据库
*/
public function change() {
$this->_create_wechat_auth();
$this->_create_wechat_auto();
$this->_create_wechat_fans();
$this->_create_wechat_fans_tags();
$this->_create_wechat_keys();
$this->_create_wechat_media();
$this->_create_wechat_news();
$this->_create_wechat_news_article();
$this->_create_wechat_payment_record();
$this->_create_wechat_payment_refund();
}
/**
* 创建数据对象
* @class WechatAuth
* @table wechat_auth
* @return void
*/
private function _create_wechat_auth() {
// 当前数据表
$table = 'wechat_auth';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-授权',
])
->addColumn('authorizer_appid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '微信APPID'])
->addColumn('authorizer_access_token','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '授权Token'])
->addColumn('authorizer_refresh_token','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '刷新Token'])
->addColumn('expires_in','integer',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => 'Token时限'])
->addColumn('user_alias','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号别名'])
->addColumn('user_name','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '众众号原账号'])
->addColumn('user_nickname','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号昵称'])
->addColumn('user_headimg','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '公众号头像'])
->addColumn('user_signature','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '公众号描述'])
->addColumn('user_company','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '公众号公司'])
->addColumn('func_info','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号集权'])
->addColumn('service_type','string',['limit' => 10, 'default' => '', 'null' => true, 'comment' => '公众号类型'])
->addColumn('service_verify','string',['limit' => 10, 'default' => '', 'null' => true, 'comment' => '公众号认证'])
->addColumn('qrcode_url','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '公众号二维码'])
->addColumn('businessinfo','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '业务序列内容'])
->addColumn('miniprograminfo','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '小程序序列内容'])
->addColumn('total','integer',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '统计调用次数'])
->addColumn('appkey','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '应用接口KEY'])
->addColumn('appuri','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '应用接口URI'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '授权状态(0已取消,1已授权)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删除,1已删除)'])
->addColumn('auth_time','integer',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '授权时间'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('status', ['name' => 'ia87695d1d_status'])
->addIndex('deleted', ['name' => 'ia87695d1d_deleted'])
->addIndex('authorizer_appid', ['name' => 'ia87695d1d_authorizer_appid'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatAuto
* @table wechat_auto
* @return void
*/
private function _create_wechat_auto() {
// 当前数据表
$table = 'wechat_auto';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-回复',
])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '类型(text,image,news)'])
->addColumn('time','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '延迟时间'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '消息编号'])
->addColumn('appid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号APPID'])
->addColumn('content','text',['default' => NULL, 'null' => true, 'comment' => '文本内容'])
->addColumn('image_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '图片链接'])
->addColumn('voice_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '语音链接'])
->addColumn('music_title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '音乐标题'])
->addColumn('music_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '音乐链接'])
->addColumn('music_image','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '缩略图片'])
->addColumn('music_desc','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '音乐描述'])
->addColumn('video_title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '视频标题'])
->addColumn('video_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '视频URL'])
->addColumn('video_desc','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '视频描述'])
->addColumn('news_id','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '图文ID'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '状态(0禁用,1启用)'])
->addColumn('create_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '创建人'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('code', ['name' => 'i15cee0aa7_code'])
->addIndex('type', ['name' => 'i15cee0aa7_type'])
->addIndex('time', ['name' => 'i15cee0aa7_time'])
->addIndex('appid', ['name' => 'i15cee0aa7_appid'])
->addIndex('status', ['name' => 'i15cee0aa7_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatFans
* @table wechat_fans
* @return void
*/
private function _create_wechat_fans() {
// 当前数据表
$table = 'wechat_fans';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-粉丝',
])
->addColumn('appid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '公众号APPID'])
->addColumn('unionid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '粉丝unionid'])
->addColumn('openid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '粉丝openid'])
->addColumn('tagid_list','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '粉丝标签id'])
->addColumn('is_black','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '是否为黑名单状态'])
->addColumn('subscribe','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '关注状态(0未关注,1已关注)'])
->addColumn('nickname','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('sex','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '用户性别(1男性,2女性,0未知)'])
->addColumn('country','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户所在国家'])
->addColumn('province','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户所在省份'])
->addColumn('city','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户所在城市'])
->addColumn('language','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户的语言(zh_CN)'])
->addColumn('headimgurl','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '用户头像'])
->addColumn('subscribe_time','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '关注时间'])
->addColumn('subscribe_at','datetime',['default' => NULL, 'null' => true, 'comment' => '关注时间'])
->addColumn('remark','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '备注'])
->addColumn('subscribe_scene','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '扫码关注场景'])
->addColumn('qr_scene','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '二维码场景值'])
->addColumn('qr_scene_str','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '二维码场景内容'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('appid', ['name' => 'ic99bc7baf_appid'])
->addIndex('openid', ['name' => 'ic99bc7baf_openid'])
->addIndex('unionid', ['name' => 'ic99bc7baf_unionid'])
->addIndex('is_black', ['name' => 'ic99bc7baf_is_black'])
->addIndex('subscribe', ['name' => 'ic99bc7baf_subscribe'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatFansTags
* @table wechat_fans_tags
* @return void
*/
private function _create_wechat_fans_tags() {
// 当前数据表
$table = 'wechat_fans_tags';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-标签',
])
->addColumn('appid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '公众号APPID'])
->addColumn('name','string',['limit' => 35, 'default' => '', 'null' => true, 'comment' => '标签名称'])
->addColumn('count','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '粉丝总数'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建日期'])
->addIndex('id', ['name' => 'i1e2a8a9a3_id'])
->addIndex('appid', ['name' => 'i1e2a8a9a3_appid'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatKeys
* @table wechat_keys
* @return void
*/
private function _create_wechat_keys() {
// 当前数据表
$table = 'wechat_keys';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-规则',
])
->addColumn('appid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号APPID'])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '类型(text,image,news)'])
->addColumn('keys','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '关键字'])
->addColumn('content','text',['default' => NULL, 'null' => true, 'comment' => '文本内容'])
->addColumn('image_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '图片链接'])
->addColumn('voice_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '语音链接'])
->addColumn('music_title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '音乐标题'])
->addColumn('music_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '音乐链接'])
->addColumn('music_image','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '缩略图片'])
->addColumn('music_desc','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '音乐描述'])
->addColumn('video_title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '视频标题'])
->addColumn('video_url','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '视频URL'])
->addColumn('video_desc','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '视频描述'])
->addColumn('news_id','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '图文ID'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序字段'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '状态(0禁用,1启用)'])
->addColumn('create_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '创建人'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('type', ['name' => 'i23d2c7f47_type'])
->addIndex('keys', ['name' => 'i23d2c7f47_keys'])
->addIndex('sort', ['name' => 'i23d2c7f47_sort'])
->addIndex('appid', ['name' => 'i23d2c7f47_appid'])
->addIndex('status', ['name' => 'i23d2c7f47_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatMedia
* @table wechat_media
* @return void
*/
private function _create_wechat_media() {
// 当前数据表
$table = 'wechat_media';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-素材',
])
->addColumn('md5','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '文件哈希'])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '媒体类型'])
->addColumn('appid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '公众号ID'])
->addColumn('media_id','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '永久素材MediaID'])
->addColumn('local_url','string',['limit' => 300, 'default' => '', 'null' => true, 'comment' => '本地文件链接'])
->addColumn('media_url','string',['limit' => 300, 'default' => '', 'null' => true, 'comment' => '远程图片链接'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('md5', ['name' => 'i7f6418618_md5'])
->addIndex('type', ['name' => 'i7f6418618_type'])
->addIndex('appid', ['name' => 'i7f6418618_appid'])
->addIndex('media_id', ['name' => 'i7f6418618_media_id'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatNews
* @table wechat_news
* @return void
*/
private function _create_wechat_news() {
// 当前数据表
$table = 'wechat_news';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-图文',
])
->addColumn('media_id','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '永久素材MediaID'])
->addColumn('local_url','string',['limit' => 300, 'default' => '', 'null' => true, 'comment' => '永久素材外网URL'])
->addColumn('article_id','string',['limit' => 60, 'default' => '', 'null' => true, 'comment' => '关联图文ID(用英文逗号做分割)'])
->addColumn('is_deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删除,1已删除)'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addColumn('create_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '创建人'])
->addIndex('media_id', ['name' => 'ib3c69027e_media_id'])
->addIndex('article_id', ['name' => 'ib3c69027e_article_id'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatNewsArticle
* @table wechat_news_article
* @return void
*/
private function _create_wechat_news_article() {
// 当前数据表
$table = 'wechat_news_article';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-文章',
])
->addColumn('title','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '素材标题'])
->addColumn('local_url','string',['limit' => 300, 'default' => '', 'null' => true, 'comment' => '永久素材URL'])
->addColumn('show_cover_pic','integer',['limit' => 4, 'default' => 0, 'null' => true, 'comment' => '显示封面(0不显示,1显示)'])
->addColumn('author','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '文章作者'])
->addColumn('digest','string',['limit' => 300, 'default' => '', 'null' => true, 'comment' => '摘要内容'])
->addColumn('content','text',['default' => NULL, 'null' => true, 'comment' => '图文内容'])
->addColumn('content_source_url','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '原文地址'])
->addColumn('read_num','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '阅读数量'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatPaymentRecord
* @table wechat_payment_record
* @return void
*/
private function _create_wechat_payment_record() {
// 当前数据表
$table = 'wechat_payment_record';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-支付-行为',
])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '交易方式'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '发起支付号'])
->addColumn('appid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '发起APPID'])
->addColumn('openid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户OPENID'])
->addColumn('order_code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '原订单编号'])
->addColumn('order_name','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '原订单标题'])
->addColumn('order_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '原订单金额'])
->addColumn('payment_time','datetime',['default' => NULL, 'null' => true, 'comment' => '支付完成时间'])
->addColumn('payment_trade','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '平台交易编号'])
->addColumn('payment_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '支付状态(0未付,1已付,2取消)'])
->addColumn('payment_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '实际到账金额'])
->addColumn('payment_bank','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '支付银行类型'])
->addColumn('payment_notify','text',['default' => NULL, 'null' => true, 'comment' => '支付结果通知'])
->addColumn('payment_remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '支付状态备注'])
->addColumn('refund_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '退款状态(0未退,1已退)'])
->addColumn('refund_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退款金额'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i43926536a_type'])
->addIndex('code', ['name' => 'i43926536a_code'])
->addIndex('appid', ['name' => 'i43926536a_appid'])
->addIndex('openid', ['name' => 'i43926536a_openid'])
->addIndex('order_code', ['name' => 'i43926536a_order_code'])
->addIndex('create_time', ['name' => 'i43926536a_create_time'])
->addIndex('payment_trade', ['name' => 'i43926536a_payment_trade'])
->addIndex('payment_status', ['name' => 'i43926536a_payment_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class WechatPaymentRefund
* @table wechat_payment_refund
* @return void
*/
private function _create_wechat_payment_refund() {
// 当前数据表
$table = 'wechat_payment_refund';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '微信-支付-退款',
])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '发起支付号'])
->addColumn('record_code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '子支付编号'])
->addColumn('refund_time','datetime',['default' => NULL, 'null' => true, 'comment' => '支付完成时间'])
->addColumn('refund_trade','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '平台交易编号'])
->addColumn('refund_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '支付状态(0未付,1已付,2取消)'])
->addColumn('refund_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '实际到账金额'])
->addColumn('refund_account','string',['limit' => 180, 'default' => '', 'null' => true, 'comment' => '退款目标账号'])
->addColumn('refund_scode','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '退款状态码'])
->addColumn('refund_remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '支付状态备注'])
->addColumn('refund_notify','text',['default' => NULL, 'null' => true, 'comment' => '退款交易通知'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('code', ['name' => 'i5a815074f_code'])
->addIndex('record_code', ['name' => 'i5a815074f_record_code'])
->addIndex('create_time', ['name' => 'i5a815074f_create_time'])
->addIndex('refund_trade', ['name' => 'i5a815074f_refund_trade'])
->addIndex('refund_status', ['name' => 'i5a815074f_refund_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

View File

@ -0,0 +1,396 @@
<?php
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallSystemTable extends Migrator {
/**
* 创建数据库
*/
public function change() {
$this->_create_system_auth();
$this->_create_system_auth_node();
$this->_create_system_base();
$this->_create_system_config();
$this->_create_system_data();
$this->_create_system_file();
$this->_create_system_menu();
$this->_create_system_oplog();
$this->_create_system_queue();
$this->_create_system_user();
}
/**
* 创建数据对象
* @class SystemAuth
* @table system_auth
* @return void
*/
private function _create_system_auth() {
// 当前数据表
$table = 'system_auth';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-权限',
])
->addColumn('title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '权限名称'])
->addColumn('utype','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '身份权限'])
->addColumn('desc','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '备注说明'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '权限状态(1使用,0禁用)'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('sort', ['name' => 'i73a781d61_sort'])
->addIndex('title', ['name' => 'i73a781d61_title'])
->addIndex('status', ['name' => 'i73a781d61_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemAuthNode
* @table system_auth_node
* @return void
*/
private function _create_system_auth_node() {
// 当前数据表
$table = 'system_auth_node';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-授权',
])
->addColumn('auth','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '角色'])
->addColumn('node','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '节点'])
->addIndex('auth', ['name' => 'i4cd9aaff6_auth'])
->addIndex('node', ['name' => 'i4cd9aaff6_node'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemBase
* @table system_base
* @return void
*/
private function _create_system_base() {
// 当前数据表
$table = 'system_base';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-字典',
])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '数据类型'])
->addColumn('code','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '数据代码'])
->addColumn('name','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '数据名称'])
->addColumn('content','text',['default' => NULL, 'null' => true, 'comment' => '数据内容'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '数据状态(0禁用,1启动)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0正常,1已删)'])
->addColumn('deleted_at','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '删除时间'])
->addColumn('deleted_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '删除用户'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('type', ['name' => 'i2a29c450f_type'])
->addIndex('code', ['name' => 'i2a29c450f_code'])
->addIndex('name', ['name' => 'i2a29c450f_name'])
->addIndex('sort', ['name' => 'i2a29c450f_sort'])
->addIndex('status', ['name' => 'i2a29c450f_status'])
->addIndex('deleted', ['name' => 'i2a29c450f_deleted'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemConfig
* @table system_config
* @return void
*/
private function _create_system_config() {
// 当前数据表
$table = 'system_config';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-配置',
])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '配置分类'])
->addColumn('name','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '配置名称'])
->addColumn('value','string',['limit' => 2048, 'default' => '', 'null' => true, 'comment' => '配置内容'])
->addIndex('type', ['name' => 'i48e345b98_type'])
->addIndex('name', ['name' => 'i48e345b98_name'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemData
* @table system_data
* @return void
*/
private function _create_system_data() {
// 当前数据表
$table = 'system_data';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-数据',
])
->addColumn('name','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '配置名'])
->addColumn('value','text',['default' => NULL, 'null' => true, 'comment' => '配置值'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('name', ['name' => 'icbccedc16_name'])
->addIndex('create_time', ['name' => 'icbccedc16_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemFile
* @table system_file
* @return void
*/
private function _create_system_file() {
// 当前数据表
$table = 'system_file';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-文件',
])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '上传类型'])
->addColumn('hash','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '文件哈希'])
->addColumn('tags','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '文件标签'])
->addColumn('name','string',['limit' => 180, 'default' => '', 'null' => true, 'comment' => '文件名称'])
->addColumn('xext','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '文件后缀'])
->addColumn('xurl','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '访问链接'])
->addColumn('xkey','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '文件路径'])
->addColumn('mime','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '文件类型'])
->addColumn('size','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '文件大小'])
->addColumn('uuid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '用户编号'])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '会员编号'])
->addColumn('isfast','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '是否秒传'])
->addColumn('issafe','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '安全模式'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '上传状态(1悬空,2落地)'])
->addColumn('create_at','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_at','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i738a363ca_type'])
->addIndex('hash', ['name' => 'i738a363ca_hash'])
->addIndex('uuid', ['name' => 'i738a363ca_uuid'])
->addIndex('xext', ['name' => 'i738a363ca_xext'])
->addIndex('unid', ['name' => 'i738a363ca_unid'])
->addIndex('tags', ['name' => 'i738a363ca_tags'])
->addIndex('name', ['name' => 'i738a363ca_name'])
->addIndex('status', ['name' => 'i738a363ca_status'])
->addIndex('issafe', ['name' => 'i738a363ca_issafe'])
->addIndex('isfast', ['name' => 'i738a363ca_isfast'])
->addIndex('create_at', ['name' => 'i738a363ca_create_at'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemMenu
* @table system_menu
* @return void
*/
private function _create_system_menu() {
// 当前数据表
$table = 'system_menu';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-菜单',
])
->addColumn('pid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '上级ID'])
->addColumn('title','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '菜单名称'])
->addColumn('icon','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '菜单图标'])
->addColumn('node','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '节点代码'])
->addColumn('url','string',['limit' => 400, 'default' => '', 'null' => true, 'comment' => '链接节点'])
->addColumn('params','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '链接参数'])
->addColumn('target','string',['limit' => 20, 'default' => '_self', 'null' => true, 'comment' => '打开方式'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '状态(0:禁用,1:启用)'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('pid', ['name' => 'i29b9da675_pid'])
->addIndex('sort', ['name' => 'i29b9da675_sort'])
->addIndex('status', ['name' => 'i29b9da675_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemOplog
* @table system_oplog
* @return void
*/
private function _create_system_oplog() {
// 当前数据表
$table = 'system_oplog';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-日志',
])
->addColumn('node','string',['limit' => 200, 'default' => '', 'null' => false, 'comment' => '当前操作节点'])
->addColumn('geoip','string',['limit' => 15, 'default' => '', 'null' => false, 'comment' => '操作者IP地址'])
->addColumn('action','string',['limit' => 200, 'default' => '', 'null' => false, 'comment' => '操作行为名称'])
->addColumn('content','string',['limit' => 1024, 'default' => '', 'null' => false, 'comment' => '操作内容描述'])
->addColumn('username','string',['limit' => 50, 'default' => '', 'null' => false, 'comment' => '操作人用户名'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => false, 'comment' => '创建时间'])
->addIndex('create_at', ['name' => 'id7cb1c775_create_at'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemQueue
* @table system_queue
* @return void
*/
private function _create_system_queue() {
// 当前数据表
$table = 'system_queue';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-任务',
])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => false, 'comment' => '任务编号'])
->addColumn('title','string',['limit' => 100, 'default' => '', 'null' => false, 'comment' => '任务名称'])
->addColumn('command','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '执行指令'])
->addColumn('exec_pid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '执行进程'])
->addColumn('exec_data','text',['default' => NULL, 'null' => true, 'comment' => '执行参数'])
->addColumn('exec_time','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '执行时间'])
->addColumn('exec_desc','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '执行描述'])
->addColumn('enter_time','decimal',['precision' => 20, 'scale' => 4, 'default' => '0.0000', 'null' => true, 'comment' => '开始时间'])
->addColumn('outer_time','decimal',['precision' => 20, 'scale' => 4, 'default' => '0.0000', 'null' => true, 'comment' => '结束时间'])
->addColumn('loops_time','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '循环时间'])
->addColumn('attempts','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '执行次数'])
->addColumn('message','text',['default' => NULL, 'null' => true, 'comment' => '最新消息'])
->addColumn('rscript','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '任务类型(0单例,1多例)'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '任务状态(1新任务,2处理中,3成功,4失败)'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => false, 'comment' => '创建时间'])
->addIndex('code', ['name' => 'if64376974_code'])
->addIndex('title', ['name' => 'if64376974_title'])
->addIndex('status', ['name' => 'if64376974_status'])
->addIndex('rscript', ['name' => 'if64376974_rscript'])
->addIndex('create_at', ['name' => 'if64376974_create_at'])
->addIndex('exec_time', ['name' => 'if64376974_exec_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class SystemUser
* @table system_user
* @return void
*/
private function _create_system_user() {
// 当前数据表
$table = 'system_user';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '系统-用户',
])
->addColumn('usertype','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '用户类型'])
->addColumn('username','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户账号'])
->addColumn('password','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '用户密码'])
->addColumn('nickname','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('headimg','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '头像地址'])
->addColumn('authorize','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '权限授权'])
->addColumn('contact_qq','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '联系QQ'])
->addColumn('contact_mail','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '联系邮箱'])
->addColumn('contact_phone','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '联系手机'])
->addColumn('login_ip','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '登录地址'])
->addColumn('login_at','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '登录时间'])
->addColumn('login_num','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '登录次数'])
->addColumn('describe','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '备注说明'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '状态(0禁用,1启用)'])
->addColumn('is_deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除(1删除,0未删)'])
->addColumn('create_at','timestamp',['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间'])
->addIndex('sort', ['name' => 'i34b957835_sort'])
->addIndex('status', ['name' => 'i34b957835_status'])
->addIndex('username', ['name' => 'i34b957835_username'])
->addIndex('is_deleted', ['name' => 'i34b957835_is_deleted'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,315 @@
<?php
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallPaymentTable extends Migrator {
/**
* 创建数据库
*/
public function change() {
$this->_create_plugin_payment_address();
$this->_create_plugin_payment_balance();
$this->_create_plugin_payment_config();
$this->_create_plugin_payment_integral();
$this->_create_plugin_payment_record();
$this->_create_plugin_payment_refund();
}
/**
* 创建数据对象
* @class PluginPaymentAddress
* @table plugin_payment_address
* @return void
*/
private function _create_plugin_payment_address() {
// 当前数据表
$table = 'plugin_payment_address';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-地址',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '主账号ID'])
->addColumn('type','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '默认状态(0普通,1默认)'])
->addColumn('idcode','string',['limit' => 180, 'default' => '', 'null' => true, 'comment' => '身体证证号'])
->addColumn('idimg1','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '身份证正面'])
->addColumn('idimg2','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '身份证反面'])
->addColumn('user_name','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '收货人姓名'])
->addColumn('user_phone','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '收货人手机'])
->addColumn('region_prov','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '地址-省份'])
->addColumn('region_city','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '地址-城市'])
->addColumn('region_area','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '地址-区域'])
->addColumn('region_addr','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '地址-详情'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(1已删,0未删)'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i368e636cc_type'])
->addIndex('unid', ['name' => 'i368e636cc_unid'])
->addIndex('deleted', ['name' => 'i368e636cc_deleted'])
->addIndex('user_phone', ['name' => 'i368e636cc_user_phone'])
->addIndex('create_time', ['name' => 'i368e636cc_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginPaymentBalance
* @table plugin_payment_balance
* @return void
*/
private function _create_plugin_payment_balance() {
// 当前数据表
$table = 'plugin_payment_balance';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-余额',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '账号编号'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '操作编号'])
->addColumn('name','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '操作名称'])
->addColumn('remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '操作备注'])
->addColumn('amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作金额'])
->addColumn('amount_prev','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作前金额'])
->addColumn('amount_next','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作后金额'])
->addColumn('cancel','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '作废状态(0未作废,1已作废)'])
->addColumn('unlock','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '解锁状态(0锁定中,1已生效)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删除,1已删除)'])
->addColumn('create_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '系统用户'])
->addColumn('cancel_time','datetime',['default' => NULL, 'null' => true, 'comment' => '作废时间'])
->addColumn('unlock_time','datetime',['default' => NULL, 'null' => true, 'comment' => '解锁时间'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('deleted_time','datetime',['default' => NULL, 'null' => true, 'comment' => '删除时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('unid', ['name' => 'i8a8f8318f_unid'])
->addIndex('code', ['name' => 'i8a8f8318f_code'])
->addIndex('cancel', ['name' => 'i8a8f8318f_cancel'])
->addIndex('unlock', ['name' => 'i8a8f8318f_unlock'])
->addIndex('deleted', ['name' => 'i8a8f8318f_deleted'])
->addIndex('create_time', ['name' => 'i8a8f8318f_create_time'])
->addIndex('deleted_time', ['name' => 'i8a8f8318f_deleted_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginPaymentConfig
* @table plugin_payment_config
* @return void
*/
private function _create_plugin_payment_config() {
// 当前数据表
$table = 'plugin_payment_config';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-配置',
])
->addColumn('type','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '支付类型'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '通道编号'])
->addColumn('name','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '支付名称'])
->addColumn('cover','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '支付图标'])
->addColumn('remark','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '支付说明'])
->addColumn('content','text',['default' => NULL, 'null' => true, 'comment' => '支付参数'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '支付状态(1使用,0禁用)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'if27d5755e_type'])
->addIndex('code', ['name' => 'if27d5755e_code'])
->addIndex('sort', ['name' => 'if27d5755e_sort'])
->addIndex('status', ['name' => 'if27d5755e_status'])
->addIndex('deleted', ['name' => 'if27d5755e_deleted'])
->addIndex('create_time', ['name' => 'if27d5755e_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginPaymentIntegral
* @table plugin_payment_integral
* @return void
*/
private function _create_plugin_payment_integral() {
// 当前数据表
$table = 'plugin_payment_integral';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-积分',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '账号编号'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '操作编号'])
->addColumn('name','string',['limit' => 200, 'default' => '', 'null' => true, 'comment' => '操作名称'])
->addColumn('remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '操作备注'])
->addColumn('amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作金额'])
->addColumn('amount_prev','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作前金额'])
->addColumn('amount_next','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '操作后金额'])
->addColumn('cancel','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '作废状态(0未作废,1已作废)'])
->addColumn('unlock','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '解锁状态(0锁定中,1已生效)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删除,1已删除)'])
->addColumn('create_by','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '系统用户'])
->addColumn('cancel_time','datetime',['default' => NULL, 'null' => true, 'comment' => '作废时间'])
->addColumn('unlock_time','datetime',['default' => NULL, 'null' => true, 'comment' => '解锁时间'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('deleted_time','datetime',['default' => NULL, 'null' => true, 'comment' => '删除时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('unid', ['name' => 'ie9553d71a_unid'])
->addIndex('code', ['name' => 'ie9553d71a_code'])
->addIndex('cancel', ['name' => 'ie9553d71a_cancel'])
->addIndex('unlock', ['name' => 'ie9553d71a_unlock'])
->addIndex('deleted', ['name' => 'ie9553d71a_deleted'])
->addIndex('create_time', ['name' => 'ie9553d71a_create_time'])
->addIndex('deleted_time', ['name' => 'ie9553d71a_deleted_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginPaymentRecord
* @table plugin_payment_record
* @return void
*/
private function _create_plugin_payment_record() {
// 当前数据表
$table = 'plugin_payment_record';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-行为',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '主账号编号'])
->addColumn('usid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '子账号编号'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '发起支付号'])
->addColumn('order_no','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '原订单编号'])
->addColumn('order_name','string',['limit' => 255, 'default' => '', 'null' => true, 'comment' => '原订单标题'])
->addColumn('order_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '原订单金额'])
->addColumn('channel_type','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '支付通道类型'])
->addColumn('channel_code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '支付通道编号'])
->addColumn('payment_time','datetime',['default' => NULL, 'null' => true, 'comment' => '支付生效时间'])
->addColumn('payment_trade','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '平台交易编号'])
->addColumn('payment_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '支付状态(0未付,1已付,2取消)'])
->addColumn('payment_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '实际支付金额'])
->addColumn('payment_coupon','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '平台优惠券金额'])
->addColumn('payment_images','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '凭证支付图片'])
->addColumn('payment_remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '支付状态备注'])
->addColumn('payment_notify','text',['default' => NULL, 'null' => true, 'comment' => '支付通知内容'])
->addColumn('audit_user','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '审核用户(系统用户ID)'])
->addColumn('audit_time','datetime',['default' => NULL, 'null' => true, 'comment' => '审核时间'])
->addColumn('audit_status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '审核状态(0已拒,1待审,2已审)'])
->addColumn('audit_remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '审核描述'])
->addColumn('refund_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '退款状态(0未退,1已退)'])
->addColumn('refund_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '累计退款'])
->addColumn('refund_payment','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回金额'])
->addColumn('refund_balance','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回余额'])
->addColumn('refund_integral','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回积分'])
->addColumn('used_payment','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '支付金额'])
->addColumn('used_balance','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '扣除余额'])
->addColumn('used_integral','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '扣除积分'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('unid', ['name' => 'id72e373f8_unid'])
->addIndex('usid', ['name' => 'id72e373f8_usid'])
->addIndex('code', ['name' => 'id72e373f8_code'])
->addIndex('order_no', ['name' => 'id72e373f8_order_no'])
->addIndex('create_time', ['name' => 'id72e373f8_create_time'])
->addIndex('audit_status', ['name' => 'id72e373f8_audit_status'])
->addIndex('channel_type', ['name' => 'id72e373f8_channel_type'])
->addIndex('channel_code', ['name' => 'id72e373f8_channel_code'])
->addIndex('payment_trade', ['name' => 'id72e373f8_payment_trade'])
->addIndex('refund_status', ['name' => 'id72e373f8_refund_status'])
->addIndex('payment_status', ['name' => 'id72e373f8_payment_status'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginPaymentRefund
* @table plugin_payment_refund
* @return void
*/
private function _create_plugin_payment_refund() {
// 当前数据表
$table = 'plugin_payment_refund';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-支付-退款',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '主账号编号'])
->addColumn('usid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '子账号编号'])
->addColumn('code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '发起支付号'])
->addColumn('record_code','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '子支付编号'])
->addColumn('refund_time','datetime',['default' => NULL, 'null' => true, 'comment' => '完成时间'])
->addColumn('refund_trade','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '交易编号'])
->addColumn('refund_status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '支付状态(0未付,1已付,2取消)'])
->addColumn('refund_amount','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退款金额'])
->addColumn('refund_account','string',['limit' => 180, 'default' => '', 'null' => true, 'comment' => '退回账号'])
->addColumn('refund_scode','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '状态编码'])
->addColumn('refund_remark','string',['limit' => 999, 'default' => '', 'null' => true, 'comment' => '退款备注'])
->addColumn('refund_notify','text',['default' => NULL, 'null' => true, 'comment' => '通知内容'])
->addColumn('used_payment','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回金额'])
->addColumn('used_balance','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回余额'])
->addColumn('used_integral','decimal',['precision' => 20, 'scale' => 2, 'default' => '0.00', 'null' => true, 'comment' => '退回积分'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('unid', ['name' => 'icef9ec8c0_unid'])
->addIndex('usid', ['name' => 'icef9ec8c0_usid'])
->addIndex('code', ['name' => 'icef9ec8c0_code'])
->addIndex('record_code', ['name' => 'icef9ec8c0_record_code'])
->addIndex('create_time', ['name' => 'icef9ec8c0_create_time'])
->addIndex('refund_trade', ['name' => 'icef9ec8c0_refund_trade'])
->addIndex('refund_status', ['name' => 'icef9ec8c0_refund_status'])
->addIndex('refund_account', ['name' => 'icef9ec8c0_refund_account'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

View File

@ -0,0 +1,96 @@
<?php
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallOldTable extends Migrator {
/**
* 创建数据库
*/
public function change() {
$this->_create_plugin_old_user();
$this->_create_plugin_old_user2();
}
/**
* 创建数据对象
* @class PluginOldUser
* @table plugin_old_user
* @return void
*/
private function _create_plugin_old_user() {
// 当前数据表
$table = 'plugin_old_user';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '',
])
->addColumn('nickname','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '昵称'])
->addColumn('phone','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '绑定手机号'])
->addColumn('concat','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '联系方式'])
->addColumn('remark','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '备注'])
->addColumn('remark2','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '备注名'])
->addColumn('create_time','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '加入时间'])
->addColumn('role','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '会员身份'])
->addColumn('订单数','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('优惠券总数','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('卡券总数','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('integral','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '积分'])
->addColumn('balance','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '余额'])
->addColumn('总消费','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('spread','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '用户推荐人'])
->addColumn('用户标签','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('所属平台(平台标识ID)','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginOldUser2
* @table plugin_old_user2
* @return void
*/
private function _create_plugin_old_user2() {
// 当前数据表
$table = 'plugin_old_user2';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '',
])
->addColumn('所属平台','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('openid','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('nickname','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '昵称'])
->addColumn('username','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '姓名'])
->addColumn('phone','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '手机号'])
->addColumn('create_time','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '申请时间'])
->addColumn('审核状态','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('rebate','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '累计佣金'])
->addColumn('usable','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '可提现佣金'])
->addColumn('订单数','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('下级用户','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->addColumn('spread','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => '推荐人'])
->addColumn('备注信息','string',['limit' => 255, 'default' => NULL, 'null' => true, 'comment' => ''])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

View File

@ -0,0 +1,204 @@
<?php
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallAccountTable extends Migrator {
/**
* 创建数据库
*/
public function change() {
$this->_create_plugin_account_auth();
$this->_create_plugin_account_bind();
$this->_create_plugin_account_msms();
$this->_create_plugin_account_user();
}
/**
* 创建数据对象
* @class PluginAccountAuth
* @table plugin_account_auth
* @return void
*/
private function _create_plugin_account_auth() {
// 当前数据表
$table = 'plugin_account_auth';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-授权',
])
->addColumn('usid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '终端账号'])
->addColumn('time','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '有效时间'])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '授权类型'])
->addColumn('token','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '授权令牌'])
->addColumn('tokenv','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '授权验证'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('usid', ['name' => 'i8a91c286f_usid'])
->addIndex('type', ['name' => 'i8a91c286f_type'])
->addIndex('time', ['name' => 'i8a91c286f_time'])
->addIndex('token', ['name' => 'i8a91c286f_token'])
->addIndex('create_time', ['name' => 'i8a91c286f_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountBind
* @table plugin_account_bind
* @return void
*/
private function _create_plugin_account_bind() {
// 当前数据表
$table = 'plugin_account_bind';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-终端',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '会员编号'])
->addColumn('type','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '终端类型'])
->addColumn('phone','string',['limit' => 30, 'default' => '', 'null' => true, 'comment' => '绑定手机'])
->addColumn('appid','string',['limit' => 30, 'default' => '', 'null' => true, 'comment' => 'APPID'])
->addColumn('openid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'OPENID'])
->addColumn('unionid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'UnionID'])
->addColumn('headimg','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '用户头像'])
->addColumn('nickname','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('password','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '登录密码'])
->addColumn('extra','text',['default' => NULL, 'null' => true, 'comment' => '扩展数据'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '账号状态'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删,1已删)'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '注册时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i4ec9ee5c7_type'])
->addIndex('unid', ['name' => 'i4ec9ee5c7_unid'])
->addIndex('sort', ['name' => 'i4ec9ee5c7_sort'])
->addIndex('phone', ['name' => 'i4ec9ee5c7_phone'])
->addIndex('appid', ['name' => 'i4ec9ee5c7_appid'])
->addIndex('status', ['name' => 'i4ec9ee5c7_status'])
->addIndex('openid', ['name' => 'i4ec9ee5c7_openid'])
->addIndex('unionid', ['name' => 'i4ec9ee5c7_unionid'])
->addIndex('deleted', ['name' => 'i4ec9ee5c7_deleted'])
->addIndex('create_time', ['name' => 'i4ec9ee5c7_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountMsms
* @table plugin_account_msms
* @return void
*/
private function _create_plugin_account_msms() {
// 当前数据表
$table = 'plugin_account_msms';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-短信',
])
->addColumn('unid','biginteger',['limit' => 20, 'default' => 0, 'null' => false, 'comment' => '账号编号'])
->addColumn('usid','biginteger',['limit' => 20, 'default' => 0, 'null' => false, 'comment' => '终端编号'])
->addColumn('type','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '短信类型'])
->addColumn('scene','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '业务场景'])
->addColumn('smsid','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '消息编号'])
->addColumn('phone','string',['limit' => 100, 'default' => '', 'null' => true, 'comment' => '目标手机'])
->addColumn('result','string',['limit' => 512, 'default' => '', 'null' => true, 'comment' => '返回结果'])
->addColumn('params','string',['limit' => 512, 'default' => '', 'null' => true, 'comment' => '短信内容'])
->addColumn('status','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '短信状态(0失败,1成功)'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i66baec398_type'])
->addIndex('usid', ['name' => 'i66baec398_usid'])
->addIndex('unid', ['name' => 'i66baec398_unid'])
->addIndex('phone', ['name' => 'i66baec398_phone'])
->addIndex('smsid', ['name' => 'i66baec398_smsid'])
->addIndex('scene', ['name' => 'i66baec398_scene'])
->addIndex('status', ['name' => 'i66baec398_status'])
->addIndex('create_time', ['name' => 'i66baec398_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountUser
* @table plugin_account_user
* @return void
*/
private function _create_plugin_account_user() {
// 当前数据表
$table = 'plugin_account_user';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-资料',
])
->addColumn('code','string',['limit' => 16, 'default' => '', 'null' => true, 'comment' => '用户编号'])
->addColumn('phone','string',['limit' => 20, 'default' => '', 'null' => true, 'comment' => '用户手机'])
->addColumn('email','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户邮箱'])
->addColumn('unionid','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'UnionID'])
->addColumn('username','string',['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户姓名'])
->addColumn('nickname','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('password','string',['limit' => 32, 'default' => '', 'null' => true, 'comment' => '认证密码'])
->addColumn('headimg','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '用户头像'])
->addColumn('region_prov','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在省份'])
->addColumn('region_city','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在城市'])
->addColumn('region_area','string',['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在区域'])
->addColumn('remark','string',['limit' => 500, 'default' => '', 'null' => true, 'comment' => '备注(内部使用)'])
->addColumn('extra','text',['default' => NULL, 'null' => true, 'comment' => '扩展数据'])
->addColumn('sort','biginteger',['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status','integer',['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '用户状态(0拉黑,1正常)'])
->addColumn('deleted','integer',['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删,1已删)'])
->addColumn('create_time','datetime',['default' => NULL, 'null' => true, 'comment' => '注册时间'])
->addColumn('update_time','datetime',['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('code', ['name' => 'iddb76b051_code'])
->addIndex('sort', ['name' => 'iddb76b051_sort'])
->addIndex('phone', ['name' => 'iddb76b051_phone'])
->addIndex('email', ['name' => 'iddb76b051_email'])
->addIndex('status', ['name' => 'iddb76b051_status'])
->addIndex('unionid', ['name' => 'iddb76b051_unionid'])
->addIndex('deleted', ['name' => 'iddb76b051_deleted'])
->addIndex('username', ['name' => 'iddb76b051_username'])
->addIndex('nickname', ['name' => 'iddb76b051_nickname'])
->addIndex('region_prov', ['name' => 'iddb76b051_region_prov'])
->addIndex('region_city', ['name' => 'iddb76b051_region_city'])
->addIndex('region_area', ['name' => 'iddb76b051_region_area'])
->addIndex('create_time', ['name' => 'iddb76b051_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

View File

@ -0,0 +1,300 @@
<?php
use think\admin\extend\PhinxExtend;
use think\admin\model\SystemConfig;
use think\admin\model\SystemMenu;
use think\admin\model\SystemUser;
use think\admin\service\ProcessService;
use think\helper\Str;
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
/**
* 数据安装包
* @class InstallPackage
*/
class InstallPackage extends Migrator
{
/**
* 数据库初始化
* @return void
* @throws \think\db\exception\DbException
*/
public function change()
{
$this->inserData();
$this->insertConf();
$this->insertUser();
$this->insertMenu();
}
/**
* 安装扩展数据
* @return void
* @throws \think\db\exception\DbException
*/
private function inserData()
{
// 待解析处理数据
$json = '[]';
// 解析并写入扩展数据
if (is_array($tables = json_decode($json, true)) && count($tables) > 0) {
foreach ($tables as $table => $path) if (($model = m($table))->count() < 1) {
$name = Str::studly($table);
ProcessService::message(" -- Starting write {$table} table ..." . PHP_EOL);
[$ls, $rs, $fp] = [0, [], fopen(__DIR__ . DIRECTORY_SEPARATOR . $path, 'r+')];
while (!feof($fp)) {
if (empty($text = trim(fgets($fp)))) continue; else $ls++;
if (is_array($rw = json_decode($text, true))) $rs[] = $rw;
if (count($rs) > 100) [, $rs] = [$model->strict(false)->insertAll($rs), []];
ProcessService::message(" -- -- {$name}:{$ls}", 1);
}
count($rs) > 0 && $model->strict(false)->insertAll($rs);
ProcessService::message(" -- Finished write {$table} table, Total {$ls} rows.", 2);
}
}
}
/**
* 初始化配置参数
* @return void
*/
private function insertConf()
{
$modal = SystemConfig::mk()->whereRaw('1=1')->findOrEmpty();
$modal->isEmpty() && $modal->insertAll([
['type' => 'base', 'name' => 'app_name', 'value' => 'ThinkAdmin'],
['type' => 'base', 'name' => 'app_version', 'value' => 'v6'],
['type' => 'base', 'name' => 'editor', 'value' => 'ckeditor5'],
['type' => 'base', 'name' => 'login_name', 'value' => '系统管理'],
['type' => 'base', 'name' => 'site_copy', 'value' => '©版权所有 2014-' . date('Y') . ' ThinkAdmin'],
['type' => 'base', 'name' => 'site_icon', 'value' => 'https://thinkadmin.top/static/img/logo.png'],
['type' => 'base', 'name' => 'site_name', 'value' => 'ThinkAdmin'],
['type' => 'base', 'name' => 'site_theme', 'value' => 'default'],
['type' => 'wechat', 'name' => 'type', 'value' => 'api'],
['type' => 'storage', 'name' => 'type', 'value' => 'local'],
['type' => 'storage', 'name' => 'allow_exts', 'value' => 'doc,gif,ico,jpg,mp3,mp4,p12,pem,png,zip,rar,xls,xlsx'],
]);
}
/**
* 初始化用户数据
* @return void
*/
private function insertUser()
{
$modal = SystemUser::mk()->whereRaw('1=1')->findOrEmpty();
$modal->isEmpty() && $modal->insert([
'id' => '10000',
'username' => 'admin',
'nickname' => '超级管理员',
'password' => '21232f297a57a5a743894a0e4a801fc3',
'headimg' => 'https://thinkadmin.top/static/img/head.png',
]);
}
/**
* 初始化系统菜单
* @return void
*/
private function insertMenu()
{
if (SystemMenu::mk()->whereRaw('1=1')->findOrEmpty()->isEmpty()) {
// 解析并初始化菜单数据
$json = '[
{
"name": "插件中心",
"icon": "",
"url": "plugin-center/index/index",
"node": "plugin-center/index/index",
"params": ""
},
{
"name": "微信管理",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "微信管理",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "微信接口配置",
"url": "wechat/config/options",
"node": "wechat/config/options",
"icon": "layui-icon layui-icon-set",
"params": ""
},
{
"name": "微信支付配置",
"url": "wechat/config/payment",
"node": "wechat/config/payment",
"icon": "layui-icon layui-icon-rmb",
"params": ""
}
]
},
{
"name": "微信定制",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "微信粉丝管理",
"url": "wechat/fans/index",
"node": "wechat/fans/index",
"icon": "layui-icon layui-icon-username",
"params": ""
},
{
"name": "微信图文管理",
"url": "wechat/news/index",
"node": "wechat/news/index",
"icon": "layui-icon layui-icon-template-1",
"params": ""
},
{
"name": "微信菜单配置",
"url": "wechat/menu/index",
"node": "wechat/menu/index",
"icon": "layui-icon layui-icon-cellphone",
"params": ""
},
{
"name": "回复规则管理",
"url": "wechat/keys/index",
"node": "wechat/keys/index",
"icon": "layui-icon layui-icon-engine",
"params": ""
},
{
"name": "关注自动回复",
"url": "wechat/auto/index",
"node": "wechat/auto/index",
"icon": "layui-icon layui-icon-release",
"params": ""
}
]
},
{
"name": "微信支付",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "支付行为管理",
"url": "wechat/payment.record/index",
"node": "wechat/payment.record/index",
"icon": "layui-icon layui-icon-rmb",
"params": ""
},
{
"name": "支付退款管理",
"url": "wechat/payment.refund/index",
"node": "wechat/payment.refund/index",
"icon": "layui-icon layui-icon-engine",
"params": ""
}
]
}
]
},
{
"name": "系统管理",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "系统配置",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "系统参数配置",
"url": "admin/config/index",
"node": "admin/config/index",
"icon": "layui-icon layui-icon-set",
"params": ""
},
{
"name": "系统任务管理",
"url": "admin/queue/index",
"node": "admin/queue/index",
"icon": "layui-icon layui-icon-log",
"params": ""
},
{
"name": "系统日志管理",
"url": "admin/oplog/index",
"node": "admin/oplog/index",
"icon": "layui-icon layui-icon-form",
"params": ""
},
{
"name": "数据字典管理",
"url": "admin/base/index",
"node": "admin/base/index",
"icon": "layui-icon layui-icon-code-circle",
"params": ""
},
{
"name": "系统文件管理",
"url": "admin/file/index",
"node": "admin/file/index",
"icon": "layui-icon layui-icon-carousel",
"params": ""
},
{
"name": "系统菜单管理",
"url": "admin/menu/index",
"node": "admin/menu/index",
"icon": "layui-icon layui-icon-layouts",
"params": ""
}
]
},
{
"name": "权限管理",
"icon": "",
"url": "#",
"node": "",
"params": "",
"subs": [
{
"name": "访问权限管理",
"url": "admin/auth/index",
"node": "admin/auth/index",
"icon": "layui-icon layui-icon-vercode",
"params": ""
},
{
"name": "系统用户管理",
"url": "admin/user/index",
"node": "admin/user/index",
"icon": "layui-icon layui-icon-username",
"params": ""
}
]
}
]
}
]';
PhinxExtend::write2menu(json_decode($json, true) ?: []);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
*.js linguist-language=php
*.css linguist-language=php
*.html linguist-language=php

View File

@ -0,0 +1,26 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
permissions: write-all
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

12
plugin/think-plugs-account/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.env
.git
.svn
.idea
.fleet
.vscode
.DS_Store
*.log
*.zip
*.cache
/vendor
/composer.lock

View File

@ -0,0 +1,59 @@
{
"type": "think-admin-plugin",
"name": "zoujingli/think-plugs-account",
"homepage": "https://thinkadmin.top",
"description": "Account Plugin for ThinkAdmin",
"authors": [
{
"name": "Anyon",
"email": "zoujingli@qq.com"
}
],
"require": {
"php": ">7.1",
"ext-gd": "*",
"ext-curl": "*",
"ext-json": "*",
"zoujingli/think-install": "^1.0|@dev",
"zoujingli/think-library": "^6.1|@dev"
},
"require-dev": {
"phpunit/phpunit": "^7.0|*"
},
"autoload": {
"psr-4": {
"plugin\\account\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"think\\admin\\tests\\": "tests/"
}
},
"extra": {
"think": {
"services": [
"plugin\\account\\Service"
]
},
"plugin": {
"copy": {
"stc/database": "database/migrations"
}
},
"config": {
"type": "plugin",
"name": "用户账号管理",
"document": "https://thinkadmin.top/plugin/think-plugs-account.html",
"license": [
"VIP"
]
}
},
"minimum-stability": "dev",
"config": {
"allow-plugins": {
"zoujingli/think-install": true
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
colors="true"
bootstrap="tests/bootstrap.php"
stopOnError="false"
backupGlobals="false"
stopOnFailure="false"
processIsolation="false"
backupStaticProperties="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
cacheDirectory=".phpunit.cache"
beStrictAboutTestsThatDoNotTestAnything="false"
>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="ThinkAdmin Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,170 @@
# ThinkPlugsAccount for ThinkAdmin
[![Latest Stable Version](https://poser.pugx.org/zoujingli/think-plugs-account/v/stable)](https://packagist.org/packages/zoujingli/think-plugs-account)
[![Latest Unstable Version](https://poser.pugx.org/zoujingli/think-plugs-account/v/unstable)](https://packagist.org/packages/zoujingli/think-plugs-account)
[![Total Downloads](https://poser.pugx.org/zoujingli/think-plugs-account/downloads)](https://packagist.org/packages/zoujingli/think-plugs-account)
[![Monthly Downloads](https://poser.pugx.org/zoujingli/think-plugs-account/d/monthly)](https://packagist.org/packages/zoujingli/think-plugs-account)
[![Daily Downloads](https://poser.pugx.org/zoujingli/think-plugs-account/d/daily)](https://packagist.org/packages/zoujingli/think-plugs-account)
[![PHP Version](https://thinkadmin.top/static/icon/php-7.1.svg)](https://thinkadmin.top)
[![License](https://thinkadmin.top/static/icon/license-vip.svg)](https://thinkadmin.top/vip-introduce)
**ThinkPlugsAccount** 是 **ThinkAdmin** 的多端账号插件,作为一套通用基础用户数据管理解决方案,支持多客户端登录绑定功能。本插件属于[会员尊享插件](https://thinkadmin.top/vip-introduce),未经授权不得用于商业用途。
目前,我们已提供丰富的数据接口,支持 **微信服务号**、**微信小程序**、**手机短信验证** 三种登录授权方式,以满足不同用户的登录需求。对于其他登录方式,您可以选择使用短信验证登录,确保用户账号的安全与便捷。
在账号逻辑数据方面,我们已全面支持**微信服务号**、**微信小程序**、**安卓APP程序**、**苹果IOS程序**、**手机网页端**、**电脑网页端**以及**自定义方式**。无论用户从哪个平台或设备登录,都能享受到流畅、统一的账号体验。
请注意,通过 **微信服务号****微信小程序** 等授权方式登录的用户,初始状态为临时用户。为了保障账号的正式性和安全性,我们要求用户通过手机号短信验证并绑定手机号,完成这一过程后,用户将升级为正式用户,享受更多会员权益和服务。
我们致力于为用户提供更加便捷、安全的账号管理体验,不断优化和完善多端账号中心的功能与服务。
**数据关联模型:**
`临时用户(usid)` `<->` `绑定手机(bind)` `<->` `正式用户(unid)`
### 话术解析
- **账号调度器 - Account**:这是一个用于创建账号管理实例对象的工具,同时也负责处理部分基础数据。它使得账号管理变得更加便捷和高效。
- **账号接口类型 - Account::TYPE**:这是终端账号请求的特定通道标识。在请求过程中,通常通过传递字段 **type** 作为参数来指定该接口类型,确保请求能够准确地被识别和处理。
- **账号实例接口 - AccountInterface**:这个接口涵盖了用户账号编号和终端账号编号的数据,以及与之相关的操作,如接口授权等。它提供了丰富的功能,使得开发者能够灵活地管理和操作账号数据。
- **用户账号编号 - unid**:这是用户的唯一账号标识,与数据表 **PlugsUser****id** 字段相对应。通过绑定和解绑操作,可以方便地将用户账号与不同的终端账号进行关联,实现跨平台登录和账号同步。
- **终端账号编号 - usid**:这代表了用户的其中一种登录账号,是用户在特定终端上的身份标识。它只能与一个用户账号进行绑定,确保了账号的唯一性和安全性。在数据表 **PlugsBind** 中,该编号与 **id** 字段相对应,方便进行数据存储和查询。
**注意事项:**
- 用户账号编号 `unid` 的获取流程如下:终端账号登录后,通过调用 `$account->bind()` 方法来创建或绑定用户账号。成功绑定后,系统将返回用户账号编号 `unid` 的值,作为该用户账号的唯一标识。
- 若需取消终端账号与用户账号的关联,可调用 `$account->unbind()` 方法。一旦关联被取消,该终端账号便可重新绑定其他用户账号,实现灵活的用户账号管理。
### 开放接口
通过用户登录接口,换取 **JWT-TOKEN** 内容,之后接口需要在每次请求的头部 **header** 加上 **Api-Token** 字段并带上之后获取到的值。
**接口文档:** https://thinkadmin.apifox.cn
**特别注意:** 调用接口时后台接口未启动 `Session` 中间键,建议使用 `Cache & usid``Cache & unid` 作为`key`值来缓存数据。
### 接口状态
* `code`:`0` 操作失败,稍候重试
* `code`:`1` 操作成功,正常操作
* `code`:`401` 无效令牌,需要重新登录
* `code`:`402` 资料不全,需要补全资料
* `code`:`403` 认证超时,需要重新登录
### 安装插件
```shell
### 安装前建议尝试更新所有组件
composer update --optimize-autoloader
### 安装稳定版本 ( 插件仅支持在 ThinkAdmin v6.1 中使用 )
composer require zoujingli/think-plugs-account --optimize-autoloader
### 安装测试版本( 插件仅支持在 ThinkAdmin v6.1 中使用
composer require zoujingli/think-plugs-account dev-master --optimize-autoloader
```
### 卸载插件
```shell
### 注意,插件卸载不会删除数据表,需要手动删除
composer remove zoujingli/think-plugs-account
```
### 调用案例
```php
// 账号管理调度器
use plugin\account\service\Account;
// @ 注册一个新用户( 微信小程序标识字段为 openid 字段
// 不传 TOKEN 的情况下并存在 openid 时会主动通过 openid 查询用户信息
// 如果传 TOKEN 的情况下且 opneid 与原 openid 不匹配会报错,用 try 捕获异常
// 注意,每次调用 Account::mk() 都会创建新的调度器,设置 set 和 get 方法的 rejwt 参数可返回接口令牌
$account = Account::mk(Account::WXAPP, TOKEN='');
$user = $account->set(['openid'=>'OPENID', 'phone'=>'13888888888']);
var_dump($user);
// 列如更新用户手机号,通过上面的操作已绑定账号,可以直接设置
$account->set(['phone'=>'1399999999']);
// 设置额外的扩展数据,数据库没有字段,不需要做为查询条件的字段
$account->set(['extra'=>['desc'=>'用户描述', 'sex'=>'男']]);
// 获取用户资料,无账号返回空数组
$user = $account->get();
var_dump($user);
// 以上插件仅仅是注册终端账号,也就是临时账号
// 下面我们通过 bind 操作,绑定或创建用户账号( 主账号
$user = $account->bind(['phone'=>'1399999999'],['uesrname'=>"会员用户"]);
var_dump($user); // $user['user'] 是主账号信息
// 解除该终端账号关联主账号
$user = $account->unBind();
var_dump($user); // 此处不会再有 $user['user'] 信息
// 判断终端账号是否为空,也就是还没有调用 set 访问或 init 失败
$user = $account->isNull();
// 获取接口 Token 信息
$user = $account->get(true);
var_dump($user); // $user['token'] 即为 JwtToken 值,接口 header 传 api-token 字段
// 判断终端账号是否已经关联主账号
$is = $account->isBind();
var_dump($is);
// 获取主账号关联的所有终端账号
$binds = $account->allBind();
// 通过终端USID取消其关联主账号
$binds = $account->delBind($usid);
// 动态注册接口通道,由插件服务类或模块 sys.php 执行注册
Account::add('diy', '自定义通道名称', '终端账号编号验证字段');
// 通道状态 - 禁用接口,将禁止该方式访问数据
Account::set('diy', 0);
// 通道状态 - 启用接口,将启用该方式访问数据
Account::set('diy', 1);
// 保存通道状态,下次访问也同样生效
Account::save();
// 获取接口认证字段以及检查接口是否有效
$field = Account::field('diy');
if($field)// 接口有效
else //接口无效
// 获取全部接口
$types = Account::types();
var_dump($types);
```
### 功能节点
可根据下面的功能节点配置菜单及访问权限,按钮操作级别的节点未展示!
* 用户账号管理:`plugin-account/master/index`
* 终端账号管理:`plugin-account/device/index`
* 手机短信管理:`plugin-account/message/index`
### 插件数据
本插件涉及数据表有:
* 插件-账号-授权 `plugin_account_auth`
* 插件-账号-终端 `plugin_account_bind`
* 插件-账号-短信 `plugin_account_msms`
* 插件-账号-资料 `plugin_account_user`
### 版权说明
**ThinkPlugsAccount** 为 **ThinkAdmin** 会员插件。
未获得此插件授权时仅供参考学习不可商用,了解商用授权请阅读 [《会员授权》](https://thinkadmin.top/vip-introduce)。
版权所有 Copyright © 2014-2024 by ThinkAdmin (https://thinkadmin.top) All rights reserved。

View File

@ -0,0 +1,60 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account;
use think\admin\Plugin;
/**
* 插件注册服务
* @class Service
* @package plugin\account
*/
class Service extends Plugin
{
/**
* 定义插件名称
* @var string
*/
protected $appName = '账号管理';
/**
* 定义安装包名
* @var string
*/
protected $package = 'zoujingli/think-plugs-account';
/**
* 定义插件菜单
* @return array[]
*/
public static function menu(): array
{
$code = app(static::class)->appCode;
return [
[
'name' => '用户管理',
'subs' => [
['name' => '用户账号管理', 'icon' => 'layui-icon layui-icon-user', 'node' => "{$code}/master/index"],
['name' => '终端账号管理', 'icon' => 'layui-icon layui-icon-cellphone', 'node' => "{$code}/device/index"],
['name' => '手机短信管理', 'icon' => 'layui-icon layui-icon-email', 'node' => "{$code}/message/index"],
],
],
];
}
}

View File

@ -0,0 +1,106 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller;
use plugin\account\model\PluginAccountBind;
use plugin\account\service\Account;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
/**
* 终端账号管理
* @class Device
* @package plugin\account\controller\user
*/
class Device extends Controller
{
/**
* 终端账号管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->type = $this->get['type'] ?? 'index';
PluginAccountBind::mQuery()->layTable(function () {
$this->title = '终端账号管理';
$this->types = Account::types(1);
}, function (QueryHelper $query) {
$query->where(['deleted' => 0, 'status' => intval($this->type === 'index')]);
$query->with('user')->equal('type#utype')->like('phone,nickname,username,create_time');
});
}
/**
* 账号接口配置
* @auth true
* @return void
* @throws \think\admin\Exception
*/
public function config()
{
$this->types = Account::types();
if ($this->request->isGet()) {
$this->data = Account::config();
$this->data['headimg'] = Account::headimg();
$this->fetch();
} else {
// 保存当前参数
Account::config($this->request->post());
// 设置接口有效时间及默认头像
$expire = $this->request->post('expire');
$headimg = $this->request->post('headimg');
Account::expire($expire ?: 0, $headimg ?: null);
// 设置开放接口通道状态
$types = $this->request->post('types', []);
foreach ($this->types as $k => $v) {
Account::set($k, intval(in_array($k, $types)));
}
if (Account::save()) {
$this->success('配置保存成功!');
} else {
$this->error('配置保存失败!');
}
}
}
/**
* 修改用户状态
* @auth true
*/
public function state()
{
PluginAccountBind::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除终端账号
* @auth true
*/
public function remove()
{
PluginAccountBind::mDelete();
}
}

View File

@ -0,0 +1,71 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller;
use plugin\account\model\PluginAccountUser;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
/**
* 用户账号管理
* @class Master
* @package plugin\account\controller\user
*/
class Master extends Controller
{
/**
* 用户账号管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->type = $this->get['type'] ?? 'index';
PluginAccountUser::mQuery()->layTable(function () {
$this->title = '用户账号管理';
}, function (QueryHelper $query) {
$query->where(['deleted' => 0, 'status' => intval($this->type === 'index')]);
$query->like('code,phone,email,username,nickname')->dateBetween('create_time');
});
}
/**
* 修改主账号状态
* @auth true
*/
public function state()
{
PluginAccountUser::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除主账号
* @auth true
*/
public function remove()
{
PluginAccountUser::mDelete();
}
}

View File

@ -0,0 +1,88 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller;
use plugin\account\model\PluginAccountMsms;
use plugin\account\service\Message as MessageService;
use plugin\account\service\message\Alisms;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
/**
* 手机短信管理
* @class Message
* @package plugin\account\controller
*/
class Message extends Controller
{
/**
* 缓存配置名称
* @var string
*/
protected $smskey;
/**
* 初始化控制器
* @return void
*/
protected function initialize()
{
parent::initialize();
$this->smskey = 'plugin.account.smscfg';
}
/**
* 手机短信管理
* @auth true
* @menu true
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
PluginAccountMsms::mQuery()->layTable(function () {
$this->title = '手机短信管理';
$this->scenes = MessageService::$scenes;
}, static function (QueryHelper $query) {
$query->equal('status')->like('smsid,scene,phone')->dateBetween('create_time');
});
}
/**
* 修改短信配置
* @auth true
* @return void
* @throws \think\admin\Exception
*/
public function config()
{
if ($this->request->isGet()) {
$this->vo = sysdata($this->smskey);
$this->scenes = MessageService::$scenes;
$this->regions = Alisms::regions();
$this->fetch();
} else {
sysdata($this->smskey, $this->request->post());
$this->success('修改配置成功!');
}
}
}

View File

@ -0,0 +1,112 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller\api;
use plugin\account\service\Account;
use think\admin\Controller;
use think\exception\HttpResponseException;
/**
* 接口授权抽象类
* @class Auth
* @package plugin\account\controller\api
*/
abstract class Auth extends Controller
{
/**
* 接口类型
* @var string
*/
protected $type;
/**
* 主账号编号
* @var integer
*/
protected $unid;
/**
* 子账号编号
* @var integer
*/
protected $usid;
/**
* 终端账号接口
* @var \plugin\account\service\contract\AccountInterface
*/
protected $account;
/**
* 控制器初始化
*/
protected function initialize()
{
try {
// 获取请求令牌内容
// 优先识别 Bearer Token 机制,再识别 api-token 字段
$token = $this->request->header('Authorization', '');
if (!empty($token) && stripos($token, 'Bearer ') === 0) {
$token = substr($token, 7);
}
if (empty($token)) {
$token = $this->request->header('api-token', '');
}
if (empty($token)) $this->error('需要登录授权', [], 401);
// 读取用户账号数据
$this->account = Account::mk('', $token);
$login = $this->account->check();
$this->usid = intval($login['id'] ?? 0);
$this->unid = intval($login['unid'] ?? 0);
$this->type = strval($login['type'] ?? '');
// 临时缓存登录数据
sysvar('plugin_account_object', $this->account);
sysvar('plugin_account_user_type', $this->type);
sysvar('plugin_account_user_usid', $this->usid);
sysvar('plugin_account_user_unid', $this->unid);
sysvar('plugin_account_user_code', $this->account->getCode());
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage(), [], $exception->getCode());
}
}
/**
* 检查用户状态
* @param boolean $isBind
* @return $this
*/
protected function checkUserStatus(bool $isBind = true): Auth
{
$login = $this->account->get();
if (empty($login['status'])) {
$this->error('终端已冻结', $login, 403);
} elseif ($isBind) {
if (empty($login['user'])) {
$this->error('请绑定账号', $login, 402);
}
if (empty($login['user']['status'])) {
$this->error('账号已冻结', $login, 403);
}
}
return $this;
}
}

View File

@ -0,0 +1,273 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller\api;
use plugin\account\model\PluginAccountUser;
use plugin\account\service\Account;
use plugin\account\service\Message;
use think\admin\Controller;
use think\admin\extend\CodeExtend;
use think\admin\extend\ImageVerify;
use think\admin\extend\JwtExtend;
use think\exception\HttpResponseException;
/**
* 通用登录注册接口
* @class Login
* @package plugin\account\controller\api
*/
class Login extends Controller
{
/**
* 通过手机号登录
* @return void
*/
public function in()
{
try {
$data = $this->_vali([
'type.require' => '类型为空',
'phone.mobile' => '手机号错误',
'phone.require' => '手机号为空',
'verify.require' => '验证码为空'
]);
if (Account::field($data['type']) !== 'phone') {
$this->error('不支持登录');
}
if (Message::checkVerifyCode($data['verify'], $data['phone'])) {
Message::clearVerifyCode($data['phone']);
$inset = ['phone' => $data['phone'], 'deleted' => 0];
if (Account::enableAutoReigster()) {
$account = Account::mk($data['type']);
$account->set($inset);
} else {
// 通过手机查询所有终端
$account = Account::mk('', $inset);
if ($account->isNull()) $this->error('手机未注册');
// 如果当前终端账号不存在则创建
if ($account->getType() !== $data['type']) {
$account = Account::mk($data['type'], $inset);
$account->isNull() && $account->set($inset);
}
}
$account->isBind() || $account->bind($inset, $inset);
$this->success('登录成功', $account->expire()->get(true));
} else {
$this->error('短信验证失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 自动授权登录
* @return void
*/
public function auto()
{
try {
$data = $this->_vali(['code.require' => '授权编号为空!']);
$vars = CodeExtend::decrypt($data['code'], JwtExtend::jwtkey());
if (is_array($vars) && isset($vars['unid'])) {
$user = PluginAccountUser::mk()->findOrEmpty($vars['unid']);
if ($user->isEmpty()) $this->error('无效账号!');
$inset = ['phone' => $user->getAttr('phone')];
$account = Account::mk(Account::WAP, $inset);
$account->set(['unid' => $user->getAttr('id')] + $inset);
$this->success('登录成功!', $account->token()->get(true));
} else {
$this->error('解密失败!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 通过密码登录
* @return void
*/
public function pass()
{
try {
$data = $this->_vali([
'type.require' => '接口类型为空',
'phone.mobile' => '登录手机错误',
'phone.require' => '登录手机为空',
'uniqid.require' => '拼图编号为空',
'verify.require' => '拼图位置为空',
'password.require' => '登录密码为空',
]);
if (Account::field($data['type']) !== 'phone') {
$this->error('不支持密码');
}
if (ImageVerify::verify($data['uniqid'], $data['verify'], true) !== 1) {
$this->error('拼图验证失败');
}
$inset = ['phone' => $data['phone'], 'deleted' => 0];
// 通过手机查询所有终端
$account = Account::mk('', $inset);
if ($account->isNull()) $this->error('手机未注册');
if ($account->pwdVerify($data['password'])) {
// 如果当前终端账号不存在则创建
if ($account->getType() !== $data['type']) {
$account = Account::mk($data['type'], $inset);
$account->isNull() && $account->set($inset);
}
$account->isBind() || $account->bind($inset, $inset);
$this->success('登录成功', $account->expire()->get(true));
} else {
$this->error('密码错误');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 通过短信找回密码
* @return void
*/
public function forget()
{
try {
$data = $this->_vali([
'type.require' => '接口类型为空',
'phone.mobile' => '登录手机错误',
'phone.require' => '登录手机为空',
'verify.require' => '短信验证为空',
'passwd.require' => '密码不能为空',
]);
if (Message::checkVerifyCode($data['verify'], $data['phone'], Message::tForget)) {
Message::clearVerifyCode($data['phone'], Message::tForget);
$inset = ['phone' => $data['phone'], 'deleted' => 0];
$account = Account::mk($data['type'], $inset);
if ($account->isNull()) $this->error('账号不存在');
$account->pwdModify($data['passwd']);
$this->success('重置成功', $account->expire()->get(true));
} else {
$this->error('验证码错误');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 用户注册绑定
* @return void
*/
public function register()
{
try {
$data = $this->_vali([
'type.require' => '接口类型为空',
'phone.mobile' => '登录手机错误',
'phone.require' => '登录手机为空',
'verify.require' => '短信验证为空',
'passwd.require' => '密码不能为空',
'fphone.default' => ''
]);
if (Message::checkVerifyCode($data['verify'], $data['phone'], Message::tRegister)) {
Message::clearVerifyCode($data['phone'], Message::tRegister);
$account = Account::mk($data['type']);
$account->set($inset = ['phone' => $data['phone'], 'deleted' => 0]);
$account->isBind() || $account->bind($inset, $inset);
$account->pwdModify($data['passwd']);
// 触发注册事件
$this->app->event->trigger('PluginAccountRegister', $account);
$this->success('注册成功', $account->get(true));
} else {
$this->error('短信验证失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 发送短信验证码
* @return void
*/
public function send()
{
$data = $this->_vali([
'type.default' => 'login',
'phone.mobile' => '手机号错误',
'phone.require' => '手机号为空',
'uniqid.require' => '拼图编号为空',
'verify.require' => '拼图位置为空',
]);
// 发送手机短信验证码
if (ImageVerify::verify($data['uniqid'], $data['verify'], true) === 1) {
if (isset(Message::$scenes[$type = strtoupper($data['type'])])) {
[$state, $info, $result] = Message::sendVerifyCode($data['phone'], 120, $type);
$state ? $this->success($info, $result) : $this->error($info);
} else {
$this->error('无效通道');
}
} else {
$this->error('验证码错误');
}
}
/**
* 生成拼图验证码
* @return void
*/
public function image()
{
$images = [
syspath('public/static/theme/img/login/bg1.jpg'),
syspath('public/static/theme/img/login/bg2.jpg'),
];
$image = ImageVerify::render($images[array_rand($images)]);
$this->success('生成拼图成功', [
'bgimg' => $image['bgimg'],
'water' => $image['water'],
'uniqid' => $image['code'],
]);
}
/**
* 实时验证结果
* @return void
*/
public function verify()
{
$data = $this->_vali([
'uniqid.require' => '拼图验证为空',
'verify.require' => '拼图数值为空'
]);
// state: [ -1:需要刷新, 0:验证失败, 1:验证成功 ]
$state = ImageVerify::verify($data['uniqid'], $data['verify']);
$this->success('验证结果', ['state' => $state]);
}
}

View File

@ -0,0 +1,119 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller\api;
use app\wechat\service\WechatService;
use plugin\account\service\Account;
use think\admin\Controller;
use think\Response;
/**
* 微信服务号入口
* @class Wechat
* @package plugin\account\controller\api
* @example 域名请修改为自己的地址,放到网页代码合适位置
*
* <meta name="referrer" content="always">
* <script referrerpolicy="unsafe-url" src="https://your.domain.com/plugin-account/api.wechat/oauth?mode=1"></script>
*
* 授权模式支持两种模块,参数 mode=0 时为静默授权mode=1 时为完整授权
* 注意:回跳地址默认从 Header 中的 http_referer 获取,也可以传 source 参数
*/
class Wechat extends Controller
{
/**
* 通道认证类型
* @var string
*/
private const type = Account::WECHAT;
/**
* 接口原地址
* @var string
*/
private $source;
/**
* 微信调度器
* @var WechatService
*/
private $wechat;
/**
* 控制器初始化
*/
protected function initialize()
{
if (Account::field(static::type)) {
$this->wechat = WechatService::instance();
$this->source = input('source') ?: $this->request->server('http_referer', $this->request->url(true));
} else {
$this->error('接口未开通');
}
}
/**
* 生成微信网页签名
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
* @throws \think\admin\Exception
*/
public function jssdk()
{
$this->success('获取网页签名!', $this->wechat->getWebJssdkSign($this->source));
}
/**
* 微信网页授权脚本
* @return \think\Response
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
* @throws \think\admin\Exception
* @remark 基于 sessionStorage 标识的登录机制
*/
public function oauth(): Response
{
$script = [];
$result = $this->wechat->getWebOauthInfo($this->source, intval(input('mode', 0)), false);
if (empty($result['openid'])) {
$script[] = 'alert("WeChat Oauth failed.")';
} else {
$fansinfo = $result['fansinfo'] ?? [];
if (empty($fansinfo['is_snapshotuser'])) {
// 筛选保存数据
$data = ['appid' => WechatService::getAppid(), 'openid' => $result['openid'], 'extra' => $fansinfo];
if (isset($fansinfo['unionid'])) $data['unionid'] = $fansinfo['unionid'];
if (isset($fansinfo['nickname'])) $data['nickname'] = $fansinfo['nickname'];
if (isset($fansinfo['headimgurl'])) $data['headimg'] = $fansinfo['headimgurl'];
$result['userinfo'] = Account::mk(static::type)->set($data, true);
// 返回数据给前端
$script[] = "window.WeChatOpenid='{$result['openid']}'";
$script[] = 'window.WeChatFansInfo=' . json_encode($result['fansinfo'], 64 | 128 | 256);
$script[] = 'window.WeChatUserInfo=' . json_encode($result['userinfo'], 64 | 128 | 256);
$script[] = "sessionStorage.setItem('wechat.token','{$result['userinfo']['token']}')";
} else {
$script[] = 'alert("不支持虚拟用户登录!\n请 10 秒后刷新页面选择授权!")';
$script[] = 'location.reload()';
}
}
$script[] = '';
return Response::create(join(";\n", $script))->contentType('application/javascript');
}
}

View File

@ -0,0 +1,245 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller\api;
use app\wechat\service\WechatService;
use plugin\account\service\Account;
use think\admin\Controller;
use think\exception\HttpResponseException;
use think\Response;
use WeMini\Crypt;
use WeMini\Live;
use WeMini\Qrcode;
/**
* 微信小程序入口
* @class Wxapp
* @package plugin\account\controller\api
*/
class Wxapp extends Controller
{
/**
* 接口通道类型
* @var string
*/
private $type = Account::WXAPP;
/**
* 小程序配置参数
* @var array
*/
private $params;
/**
* 接口初始化
* @throws \think\admin\Exception
*/
protected function initialize()
{
if (Account::field($this->type)) {
$this->params = WechatService::getWxconf();
} else {
$this->error('接口未开通');
}
}
/**
* 换取会话
*/
public function session()
{
try {
$input = $this->_vali(['code.require' => '凭证编码为空']);
[$openid, $unionid, $sesskey] = $this->applySesskey($input['code']);
$data = [
'appid' => $this->params['appid'],
'openid' => $openid,
'unionid' => $unionid,
'session_key' => $sesskey,
];
$this->success('授权换取成功', Account::mk($this->type)->set($data, true));
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error("处理失败,{$exception->getMessage()}");
}
}
/**
* 数据解密
*/
public function decode()
{
try {
$input = $this->_vali([
'iv.require' => '解密向量为空',
'code.require' => '授权编码为空',
'encrypted.require' => '密文内容为空',
]);
[$openid, $unionid, $input['session_key']] = $this->applySesskey($input['code']);
$result = Crypt::instance($this->params)->decode($input['iv'], $input['session_key'], $input['encrypted']);
if (is_array($result) && isset($result['avatarUrl']) && isset($result['nickName'])) {
$data = [
'extra' => $result,
'appid' => $this->params['appid'],
'openid' => $openid,
'unionid' => $unionid,
'headimg' => $result['avatarUrl'],
'nickname' => $result['nickName'],
];
if ($data['nickname'] === '微信用户') unset($data['headimg'], $data['nickname']);
$this->success('解密成功', Account::mk($this->type)->set($data, true));
} elseif (is_array($result)) {
if (!empty($result['phoneNumber'])) {
$data = ['appid' => $this->params['appid'], 'openid' => $openid, 'unionid' => $unionid];
($account = Account::mk($this->type))->set($data);
$account->bind(['phone' => $result['phoneNumber']], $data);
$this->success('绑定成功', $account->get(true));
} else {
$this->success('解密成功', $result);
}
} else {
$this->error('解析失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error("处理失败,{$exception->getMessage()}");
}
}
/**
* 快速获取手机号
* @return void
*/
public function phone()
{
try {
$input = $this->_vali([
'code.require' => '授权编码为空',
'openid.require' => '用户编号为空'
]);
$result = Crypt::instance($this->params)->getPhoneNumber($input['code']);
if (is_array($result)) {
$this->success('解密成功', $result);
} else {
$this->error('解析失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error("处理失败,{$exception->getMessage()}");
}
}
/**
* 换取会话授权
* @param string $code 授权编号
* @return void|array [openid, unionid, sessionkey]
*/
private function applySesskey(string $code): array
{
try {
$cache = $this->app->cache->get($code, []);
if (isset($cache['openid']) && isset($cache['session_key'])) {
return [$cache['openid'], $cache['unionid'] ?? '', $cache['session_key']];
}
$result = Crypt::instance($this->params)->session($code);
if (isset($result['openid']) && isset($result['session_key'])) {
$this->app->cache->set($code, $result, 7200);
return [$result['openid'], $result['unionid'] ?? '', $result['session_key']];
} else {
$this->error($result['errmsg'] ?? '换取失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error("授权失败,{$exception->getMessage()}");
}
}
/**
* 获取小程序码
* @return void|\think\Response
*/
public function qrcode(): Response
{
try {
$data = $this->_vali([
'size.default' => 430,
'type.default' => 'base64',
'path.require' => '跳转链接为空',
]);
$result = Qrcode::instance($this->params)->createMiniPath($data['path'], $data['size']);
if ($data['type'] === 'base64') {
$this->success('生成小程序码', ['base64' => 'data:image/png;base64,' . base64_encode($result)]);
} else {
return response($result)->contentType('image/png');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 获取直播列表
*/
public function getLiveList()
{
try {
$data = $this->_vali(['start.default' => 0, 'limit.default' => 10]);
$list = Live::instance($this->params)->getLiveList($data['start'], $data['limit']);
$this->success('直播列表', $list);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 获取回放源视频
*/
public function getLiveInfo()
{
try {
$data = $this->_vali([
'start.default' => 0,
'limit.default' => 10,
'action.default' => 'get_replay',
'room_id.require' => '直播间号为空',
]);
$result = Live::instance($this->params)->getLiveInfo($data);
$this->success('回放列表', $result);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
}

View File

@ -0,0 +1,148 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\controller\api\auth;
use plugin\account\controller\api\Auth;
use plugin\account\model\PluginAccountAuth;
use plugin\account\model\PluginAccountBind;
use plugin\account\service\Message;
use think\admin\service\RuntimeService;
use think\admin\Storage;
use think\exception\HttpResponseException;
/**
* 用户账号管理
* @class Center
* @package plugin\account\controller\api\auth
*/
class Center extends Auth
{
/**
* 获取账号信息
* @return void
*/
public function get()
{
$this->success('获取资料', $this->account->get());
}
/**
* 修改帐号信息
* @return void
*/
public function set()
{
try {
$data = $this->checkUserStatus()->_vali([
'headimg.default' => '',
'nickname.default' => '',
'password.default' => '',
'region_prov.default' => '',
'region_city.default' => '',
'region_area.default' => '',
]);
// 保存用户头像
if (!empty($data['headimg'])) {
$data['headimg'] = Storage::saveImage($data['headimg'], 'headimg')['url'] ?? '';
}
// 修改登录密码
if (!empty($data['password']) && strlen($data['password']) > 4) {
$this->account->pwdModify($data['password']);
unset($data['password']);
}
foreach ($data as $k => $v) if ($v === '') unset($data[$k]);
if (empty($data)) $this->success('无需修改', $this->account->get());
$this->success('修改成功', $this->account->bind(['id' => $this->unid], $data));
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 注销当前账号
* @return void
*/
public function forbid()
{
if (($user = $this->account->user())->isExists()) try {
$this->app->db->transaction(function () use ($user) {
$user->save(['deleted' => 1, 'remark' => '用户主动申请注销账号!']);
PluginAccountAuth::mk()->where(['usid' => $this->usid])->delete();
PluginAccountBind::mk()->where(['unid' => $this->unid])->delete();
});
$this->success('账号注销成功!');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
} else {
$this->error('未完成注册!');
}
}
/**
* 绑定主账号
* @return void
*/
public function bind()
{
try {
$data = $this->_vali([
'phone.mobile' => '手机号错误',
'phone.require' => '手机号为空',
'verify.require' => '验证码为空',
'passwd.default' => ''
]);
if (Message::checkVerifyCode($data['verify'], $data['phone'])) {
Message::clearVerifyCode($data['phone']);
$map = $bind = ['phone' => $data['phone']];
if (!$this->account->isBind()) {
$user = $this->account->get();
$bind['headimg'] = $user['headimg'];
$bind['unionid'] = $user['unionid'];
$bind['nickname'] = $user['nickname'];
}
$this->account->set($map);
$this->account->bind($map, $bind);
if (!empty($data['passwd'])) {
$this->account->pwdModify($data['passwd']);
}
$this->success('关联成功!', $this->account->get(true));
} else {
$this->error('验证失败');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 解除账号关联
* @return void
*/
public function unbind()
{
$this->account->unBind();
$this->success('关联成功', $this->account->get());
}
}

View File

@ -0,0 +1,90 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\model;
use think\admin\Model;
/**
* 模型抽象类
* @class Abs
* @package plugin\account\model
*/
abstract class Abs extends Model
{
/**
* 格式化输出时间
* @param mixed $value
* @return string
*/
public function getCreateTimeAttr($value): string
{
return format_datetime($value);
}
/**
* 格式化输出时间
* @param mixed $value
* @return string
*/
public function getUpdateTimeAttr($value): string
{
return format_datetime($value);
}
/**
* 时间写入格式化
* @param mixed $value
* @return string
*/
public function setCreateTimeAttr($value): string
{
return is_string($value) ? str_replace(['年', '月', '日'], ['-', '-', ''], $value) : $value;
}
/**
* 时间写入格式化
* @param mixed $value
* @return string
*/
public function setUpdateTimeAttr($value): string
{
return $this->setCreateTimeAttr($value);
}
/**
* 字段属性处理
* @param mixed $value
* @return string
*/
public function setExtraAttr($value): string
{
return is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE);
}
/**
* 字段属性处理
* @param mixed $value
* @return array
*/
public function getExtraAttr($value): array
{
return empty($value) ? [] : (is_string($value) ? json_decode($value, true) : $value);
}
}

View File

@ -0,0 +1,38 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\model;
use think\model\relation\HasOne;
/**
* 子账号授权模型
* @class PluginAccountAuth
* @package plugin\account\model
*/
class PluginAccountAuth extends Abs
{
/**
* 关联子账号
* @return \think\model\relation\HasOne
*/
public function client(): HasOne
{
return $this->hasOne(PluginAccountBind::class, 'id', 'usid')->with(['user']);
}
}

View File

@ -0,0 +1,62 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\model;
use plugin\account\service\Account;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
/**
* 用户子账号模型
* @class PluginAccountBind
* @package plugin\account\model
*/
class PluginAccountBind extends Abs
{
/**
* 关联主账号
* @return \think\model\relation\HasOne
*/
public function user(): HasOne
{
return $this->hasOne(PluginAccountUser::class, 'id', 'unid');
}
/**
* 关联授权数据
* @return \think\model\relation\HasMany
*/
public function auths(): HasMany
{
return $this->hasMany(PluginAccountAuth::class, 'usid', 'id');
}
/**
* 增加通道名称显示
* @return array
*/
public function toArray(): array
{
$data = parent::toArray();
if (isset($data['type'])) {
$data['type_name'] = Account::get($data['type'])['name'] ?? $data['type'];
}
return $data;
}
}

View File

@ -0,0 +1,42 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\model;
use plugin\account\service\Message;
/**
* 账号短信验证模型
* @class PluginAccountMsms
* @package plugin\account\model
*/
class PluginAccountMsms extends Abs
{
/**
* 格式化数据
* @return array
*/
public function toArray(): array
{
$data = parent::toArray();
if (isset($data['scene'])) {
$data['scene_name'] = Message::$scenes[$data['scene']] ?? $data['scene'];
}
return $data;
}
}

View File

@ -0,0 +1,38 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\model;
use think\model\relation\HasMany;
/**
* 用户账号模型
* @class PluginAccountUser
* @package plugin\account\model
*/
class PluginAccountUser extends Abs
{
/**
* 关联子账号
* @return \think\model\relation\HasMany
*/
public function clients(): HasMany
{
return $this->hasMany(PluginAccountBind::class, 'unid', 'id');
}
}

View File

@ -0,0 +1,268 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service;
use plugin\account\model\PluginAccountAuth;
use plugin\account\service\contract\AccountAccess;
use plugin\account\service\contract\AccountInterface;
use think\admin\Exception;
use think\admin\extend\JwtExtend;
/**
* 用户账号调度器
* @class Account
* @package plugin\account\service
*/
abstract class Account
{
public const WAP = 'wap';
public const WEB = 'web';
public const WXAPP = 'wxapp';
public const WECHAT = 'wechat';
public const IOSAPP = 'iosapp';
public const ANDROID = 'android';
// 已禁用的账号通道
private static $denys = null;
private static $cacheKey = 'plugin.account.denys';
private static $types = [
self::WAP => ['name' => '手机浏览器', 'field' => 'phone', 'status' => 1],
self::WEB => ['name' => '电脑浏览器', 'field' => 'phone', 'status' => 1],
self::WXAPP => ['name' => '微信小程序', 'field' => 'openid', 'status' => 1],
self::WECHAT => ['name' => '微信服务号', 'field' => 'openid', 'status' => 1],
self::IOSAPP => ['name' => '苹果APP应用', 'field' => 'phone', 'status' => 1],
self::ANDROID => ['name' => '安卓APP应用', 'field' => 'phone', 'status' => 1],
];
/**
* 创建账号实例
* @param string $type 通道编号
* @param string|array $token 令牌或条件
* @param boolean $isjwt 是否JWT模式
* @return AccountInterface
* @throws \think\admin\Exception
*/
public static function mk(string $type, $token = '', bool $isjwt = true): AccountInterface
{
if ($token === AccountAccess::tester) {
if (empty($type)) {
$type = PluginAccountAuth::mk()->where(['token' => $token])->value('type');
if (empty($type)) throw new Exception('账号不存在!');
}
} elseif ($isjwt && is_string($token) && strlen($token) > 32) {
$data = JwtExtend::verify($token);
[$type, $token] = [$type ?: ($data['type'] ?? ''), $data['token'] ?? $token];
if (($data['type'] ?? '') !== $type) throw new Exception('授权不匹配!');
}
if (($field = self::field($type)) || is_array($token)) {
$vars = ['type' => $type, 'field' => $field];
return app(AccountAccess::class, $vars, true)->init($token, $isjwt);
} else {
throw new Exception('登录已超时!', 401);
}
}
/**
* 初始化数据状态
* @return array[]
*/
private static function init(): array
{
if (is_null(self::$denys)) try {
self::$denys = sysdata(self::$cacheKey);
foreach (self::$types as $type => &$item) {
$item['status'] = intval(!in_array($type, self::$denys));
}
} catch (\Exception $exception) {
}
return self::$types;
}
/**
* 动态增加通道
* @param string $type
* @param string $name
* @param string $field
* @return array[]
*/
public static function add(string $type, string $name, string $field = 'phone'): array
{
self::$types[$type] = ['name' => $name, 'field' => $field, 'status' => 1];
return self::types();
}
/**
* 设置通道状态
* @param string $type 通道编号
* @param integer $status 通道状态
* @return boolean
*/
public static function set(string $type, int $status): bool
{
if (isset(self::$types[$type])) {
self::$types[$type]['status'] = $status;
return true;
} else {
return false;
}
}
/**
* 获取通道参数
* @param string $type
* @return array
*/
public static function get(string $type): array
{
return self::$types[$type] ?? [];
}
/**
* 获取全部通道
* @param ?integer $status 指定状态
* @return array
*/
public static function types(?int $status = null): array
{
try {
$all = [];
foreach (self::init() as $type => $item) {
$item['code'] = $type;
if (is_null($status) || $item['status'] === $status) $all[$type] = $item;
}
return $all;
} catch (\Exception $exception) {
return [];
}
}
/**
* 保存用户通道状态
* @return mixed
* @throws \think\admin\Exception
*/
public static function save()
{
self::$denys = [];
foreach (self::types() as $k => $v) {
if (empty($v['status'])) self::$denys[] = $k;
}
return sysdata(self::$cacheKey, self::$denys);
}
/**
* 获取认证字段
* @param string $type 通道编码
* @return string
*/
public static function field(string $type): string
{
$types = self::init();
if (!empty($types[$type]['status'])) {
return $types[$type]['field'] ?? '';
} else {
return '';
}
}
/**
* 接口授权有效时间及默认头像
* @param string|integer|null $expire 有效时间
* @param string|null $headimg 默认头像
* @return integer
* @throws \think\admin\Exception
*/
public static function expire($expire = null, string $headimg = null): int
{
$data = sysdata('plugin.account.access');
if (!is_null($expire) || !is_null($headimg)) {
if (!is_null($expire)) $data['expire'] = $expire;
if (!is_null($headimg)) $data['headimg'] = $headimg;
$data = sysdata('plugin.account.access', $data);
}
return intval($data['expire'] ?? 0);
}
/**
* 解析请求令牌
* @param string $token
* @param ?string $type
* @return AccountInterface
* @throws \think\admin\Exception
*/
public static function token(string $token = '', ?string &$type = null): AccountInterface
{
if ($token === AccountAccess::tester) {
$map = ['token' => $token];
empty($type) || ($map['type'] = $type);
$auth = PluginAccountAuth::mk()->where($map)->findOrEmpty();
if ($auth->isEmpty()) throw new Exception('账号不存在!');
return static::mk($type = $auth->getAttr('type'), $auth->getAttr('token'));
} else {
$data = JwtExtend::verify($token);
return static::mk($type = $data['type'] ?? '-', $data['token'] ?? '-');
}
}
/**
* 账号配置参数设置与读取
* @param null|array|string $data
* @return mixed|void|null
* @throws \think\admin\Exception
*/
public static function config($data = null)
{
if (is_null($data)) {
return sysdata('plugin.account.access');
} elseif (is_array($data)) {
return sysdata('plugin.account.access', $data);
} elseif (is_string($data)) {
return sysdata('plugin.account.access')[$data] ?? null;
} else {
return null;
}
}
/**
* 是否自动注册
* @return boolean
* @throws \think\admin\Exception
*/
public static function enableAutoReigster(): bool
{
return empty(self::config('disRegister'));
}
/**
* 获取默认头像
* @param string|null $headimg
* @return string
* @throws \think\admin\Exception
*/
public static function headimg(string $headimg = null): string
{
$data = sysdata('plugin.account.access');
if (!is_null($headimg)) {
$data['headimg'] = $headimg;
sysdata('plugin.account.access', $data);
}
return $data['headimg'] ?? 'https://thinkadmin.top/static/img/logo.png';
}
}

View File

@ -0,0 +1,153 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service;
use plugin\account\service\contract\MessageInterface;
use plugin\account\service\message\Alisms;
use think\admin\Exception;
use think\admin\Library;
/**
* 短信服务调度器
* @class Message
* @mixin MessageInterface
* @package plugin\account\service
*/
abstract class Message
{
public const tLogin = 'LOGIN';
public const tForget = 'FORGET';
public const tRegister = 'REGISTER';
/**
* 业务场景定义
* @var string[]
*/
public static $scenes = [
self::tLogin => '用户登录验证',
self::tForget => '找回用户密码',
self::tRegister => '用户注册绑定',
];
/**
* 创建短信通道
* @param array $config
* @param ?string $driver
* @return MessageInterface
* @throws \think\admin\Exception
*/
public static function mk(array $config = [], ?string $driver = null): MessageInterface
{
if (!is_null($driver) && !isset(class_implements($driver)[MessageInterface::class])) {
throw new Exception("Sms driver [$driver] Not implements MessageInterface.");
} else {
return app($driver ?: Alisms::class)->init($config);
}
}
/**
* 发送短信验证码
* @param string $phone 手机号码
* @param integer $wait 等待时间
* @param string $scene 业务场景
* @return array [state, message, [timeout]]
*/
public static function sendVerifyCode(string $phone, int $wait = 120, string $scene = self::tLogin): array
{
try {
$ckey = self::genCacheKey($phone, $scene);
$cache = Library::$sapp->cache->get($ckey, []);
// 检查是否已经发送
if (is_array($cache) && isset($cache['time']) && $cache['time'] > time() - $wait) {
$dtime = ($cache['time'] + $wait < time()) ? 0 : ($wait - time() + $cache['time']);
return [1, '验证码已发送', ['time' => $dtime]];
}
// 生成新的验证码
[$code, $time] = [rand(100000, 999999), time()];
Library::$sapp->cache->set($ckey, ['code' => $code, 'time' => $time], 600);
// 尝试发送短信内容
self::mk()->verify($scene, $phone, ['code' => $code]);
return [1, '验证码发送成功', ['time' => ($time + $wait < time()) ? 0 : ($wait - time() + $time)]];
} catch (\Exception $ex) {
trace_file($ex);
isset($ckey) && Library::$sapp->cache->delete($ckey);
return [0, $ex->getMessage(), []];
}
}
/**
* 检查短信验证码
* @param string $vcode 验证码
* @param string $phone 手机号码
* @param string $scene 业务场景
* @return boolean
* @throws \think\admin\Exception
*/
public static function checkVerifyCode(string $vcode, string $phone, string $scene = self::tLogin): bool
{
if (stripos(Library::$sapp->request->domain(), '.thinkadmin.top') !== false) {
if ($vcode === '123456') return true;
}
$cache = Library::$sapp->cache->get(static::genCacheKey($phone, $scene), []);
return is_array($cache) && isset($cache['code']) && $cache['code'] == $vcode;
}
/**
* 清理短信验证码
* @param string $phone
* @param string $scene
* @return boolean
*/
public static function clearVerifyCode(string $phone, string $scene = self::tLogin): bool
{
try {
return Library::$sapp->cache->delete(static::genCacheKey($phone, $scene));
} catch (\Exception $exception) {
return false;
}
}
/**
* 生成验证码缓存名
* @param string $phone 手机号码
* @param string $scene 业务场景
* @return string
* @throws \think\admin\Exception
*/
private static function genCacheKey(string $phone, string $scene = self::tLogin): string
{
if (isset(array_change_key_case(static::$scenes)[strtolower($scene)])) {
return md5(strtolower("sms-{$scene}-{$phone}"));
} else {
throw new Exception("未定义的业务");
}
}
/**
* 静态方法调用
* @param string $name
* @param array $arguments
* @return mixed
* @throws \think\admin\Exception
*/
public static function __callStatic(string $name, array $arguments)
{
return static::mk()->$name(...$arguments);
}
}

View File

@ -0,0 +1,492 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service\contract;
use plugin\account\model\PluginAccountAuth;
use plugin\account\model\PluginAccountBind;
use plugin\account\model\PluginAccountUser;
use plugin\account\service\Account;
use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\admin\extend\JwtExtend;
use think\App;
/**
* 用户账号通用类
* @class AccountAccess
* @package plugin\account\service\contract
*/
class AccountAccess implements AccountInterface
{
/**
* 当前应用实例
* @var \think\App
*/
protected $app;
/**
* 当前用户对象
* @var PluginAccountUser
*/
protected $user;
/**
* 当前认证对象
* @var PluginAccountAuth
*/
protected $auth;
/**
* 当前终端对象
* @var PluginAccountBind
*/
protected $bind;
/**
* 当前通道类型
* @var string
*/
protected $type;
/**
* 授权检查字段
* @var string
*/
protected $field;
/**
* 是否JWT模式
* @var boolean
*/
protected $isjwt;
/**
* 令牌有效时间
* @var integer
*/
protected $expire = 3600;
/**
* 测试专用 TOKEN
* 主要用于接口文档演示
* @var string
*/
public const tester = 'tester';
/**
* 通道构造方法
* @param \think\App $app
* @param string $type 通道类型
* @param string $field 授权字段
* @throws \think\admin\Exception
*/
public function __construct(App $app, string $type, string $field)
{
$this->app = $app;
$this->type = $type;
$this->field = $field;
$this->expire = Account::expire();
}
/**
* 初始化通道
* @param string|array $token 令牌或条件
* @param boolean $isjwt 是否返回令牌
* @return AccountInterface
* @throws \think\admin\Exception
*/
public function init($token = '', bool $isjwt = true): AccountInterface
{
$this->isjwt = $isjwt;
$this->auth = PluginAccountAuth::mk();
$this->bind = PluginAccountBind::mk();
$this->user = PluginAccountUser::mk();
if (is_string($token)) {
$map = ['type' => $this->type, 'token' => $token];
$this->auth = PluginAccountAuth::mk()->where($map)->findOrEmpty();
$this->bind = $this->auth->client()->findOrEmpty();
$this->user = $this->bind->user()->findOrEmpty();
} elseif (is_array($token)) {
// 返向查询终端账号
$map = ['deleted' => 0];
if ($this->type) $map['type'] = $this->type;
$this->bind = PluginAccountBind::mk()->where($map)->where($token)->findOrEmpty();
$this->user = $this->bind->user()->findOrEmpty();
if ($this->bind->isExists()) {
if (empty($this->type)) $this->type = $this->bind->getAttr('type');
if ($this->auth->isEmpty()) $this->token(false);
}
}
return $this;
}
/**
* 设置子账号资料
* @param array $data 用户资料
* @param boolean $rejwt 返回令牌
* @return array
* @throws \think\admin\Exception
*/
public function set(array $data = [], bool $rejwt = false): array
{
// 如果传入授权验证字段
if (isset($data[$this->field])) {
if ($this->bind->isExists()) {
if ($data[$this->field] !== $this->bind->getAttr($this->field)) {
throw new Exception('禁止强行关联!');
}
} else {
$map = [$this->field => $data[$this->field]];
if ($this->type) $map['type'] = $this->type;
$this->bind = PluginAccountBind::mk()->where($map)->findOrEmpty();
}
} elseif ($this->bind->isEmpty()) {
throw new Exception("字段 {$this->field} 为空!");
}
$this->bind = $this->save(array_merge($data, ['type' => $this->type]));
if ($this->bind->isEmpty()) throw new Exception('更新资料失败!');
return $this->token()->get($rejwt);
}
/**
* 获取用户数据
* @param boolean $rejwt 返回令牌
* @param boolean $refresh 刷新数据
* @return array
*/
public function get(bool $rejwt = false, bool $refresh = false): array
{
if ($refresh) {
$this->bind->isExists() && $this->bind->refresh();
$this->user->isExists() && $this->user->refresh();
}
$data = $this->bind->hidden(['sort', 'password'], true)->toArray();
if ($this->bind->isExists()) {
$data['user'] = $this->user->hidden(['sort', 'password'], true)->toArray();
if ($rejwt) $data['token'] = $this->isjwt ? JwtExtend::token([
'type' => $this->auth->getAttr('type'), 'token' => $this->auth->getAttr('token')
]) : $this->auth->getAttr('token');
}
return $data;
}
/**
* 验证终端密码
* @param string $pass 待验证密码
* @return boolean
* @throws \think\admin\Exception
*/
public function pwdVerify(string $pass): bool
{
$pass = md5($pass);
if ($this->user->getAttr('password') === $pass) return !!$this->expire();
return $this->bind->getAttr('password') === $pass && $this->expire();
}
/**
* 修改终端密码
* @param string $pass 待修改密码
* @param boolean $event 触发事件
* @return boolean
*/
public function pwdModify(string $pass, bool $event = true): bool
{
if ($this->bind->isEmpty()) return false;
$data = ['password' => md5($pass)];
$this->user->isExists() && $this->user->save($data);
if (!$this->bind->save($data)) return false;
if ($event) $this->app->event->trigger('PluginAccountChangePassword', [
'unid' => $this->getUnid(), 'pass' => $pass
]);
return true;
}
/**
* 绑定主账号
* @param array $map 主账号条件
* @param array $data 主账号资料
* @return array
* @throws \think\admin\Exception
*/
public function bind(array $map, array $data = []): array
{
if ($this->bind->isEmpty()) throw new Exception('终端账号异常!');
$this->user = PluginAccountUser::mk()->where(['deleted' => 0])->where($map)->findOrEmpty();
if (!empty($data['extra'])) $this->user->setAttr('extra', array_merge($this->user->getAttr('extra'), $data['extra']));
unset($data['id'], $data['code'], $data['extra']);
// 生成新的用户编号
if ($this->user->isEmpty()) do $check = ['code' => $data['code'] = $this->userCode()];
while (PluginAccountUser::mk()->master()->where($check)->findOrEmpty()->isExists());
// 自动绑定默认头像
if (empty($data['headimg']) && $this->user->isEmpty() || empty($this->user->getAttr('headimg'))) {
if (empty($data['headimg'] = $this->bind->getAttr('headimg'))) $data['headimg'] = Account::headimg();
}
// 自动生成用户昵称
if (empty($data['nickname']) && empty($this->user->getAttr('nickname'))) {
if (empty($data['nickname'] = $this->bind->getAttr('nickname'))) {
$prefix = Account::config('userPrefix') ?: (Account::get($this->type)['name'] ?? $this->type);
if ($phone = $data['phone'] ?? $this->user->getAttr('phone')) {
$data['nickname'] = $prefix . substr($phone, -4);
} else {
$data['nickname'] = "{$prefix}{$this->bind->getAttr('id')}";
}
}
}
// 同步用户登录密码
if (!empty($this->bind->getAttr('password'))) {
$data['password'] = $this->bind->getAttr('password');
}
// 保存更新用户数据
if ($this->user->save($data + $map)) {
$this->bind->save(['unid' => $this->user['id']]);
$this->app->event->trigger('PluginAccountBind', [
'type' => $this->type,
'unid' => intval($this->user->getAttr('id')),
'usid' => intval($this->bind->getAttr('id')),
]);
return $this->get();
} else {
throw new Exception('绑定用户失败!');
}
}
/**
* 解绑主账号
* @return array
* @throws \think\admin\Exception
*/
public function unBind(): array
{
if ($this->bind->isEmpty()) {
throw new Exception('终端账号异常!');
}
if (($unid = $this->bind->getAttr('unid')) > 0) {
$this->bind->save(['unid' => 0]);
$this->app->event->trigger('PluginAccountUnbind', [
'type' => $this->type,
'unid' => intval($unid),
'usid' => intval($this->bind->getAttr('id')),
]);
}
return $this->get();
}
/**
* 判断绑定主账号
* @return boolean
*/
public function isBind(): bool
{
return $this->user->isExists();
}
/**
* 判断是否空账号
* @return boolean
*/
public function isNull(): bool
{
return $this->bind->isEmpty();
}
/**
* 获取关联终端
* @return array
*/
public function allBind(): array
{
try {
if ($this->isNull()) return [];
if ($this->isBind() && ($unid = $this->bind->getAttr('unid'))) {
$map = ['unid' => $unid, 'deleted' => 0];
return PluginAccountBind::mk()->where($map)->select()->toArray();
} else {
return [$this->bind->refresh()->toArray()];
}
} catch (\Exception $exception) {
return [];
}
}
/**
* 解除终端关联
* @param integer $usid 终端编号
* @return array
*/
public function delBind(int $usid): array
{
if ($this->isBind() && ($unid = $this->bind->getAttr('unid'))) {
$map = ['id' => $usid, 'unid' => $unid];
PluginAccountBind::mk()->where($map)->update(['unid' => 0]);
}
return $this->allBind();
}
/**
* 刷新账号序号
* @return array
*/
public function recode(): array
{
if ($this->bind->isEmpty()) return $this->get();
if ($this->user->isExists()) {
do $check = ['code' => $this->userCode()];
while (PluginAccountUser::mk()->master()->where($check)->findOrEmpty()->isExists());
$this->user->save($check);
}
return $this->get();
}
/**
* 检查是否有效
* @return array
* @throws \think\admin\Exception
*/
public function check(): array
{
if ($this->bind->isEmpty()) {
throw new Exception('请重新登录!', 401);
}
if ($this->expire > 0 && $this->auth->getAttr('time') < time()) {
throw new Exception('登录已超时!', 403);
}
return static::expire()->get();
}
/**
* 获取用户模型
* @return PluginAccountUser
*/
public function user(): PluginAccountUser
{
return $this->user->hidden(['sort', 'password'], true);
}
/**
* 获取用户编号
* @return string
*/
public function getCode(): string
{
return $this->user->getAttr('code') ?: '';
}
/**
* 获取终端类型
* @return string
*/
public function getType(): string
{
return $this->bind->getAttr('type') ?: '';
}
/**
* 获取用户编号
* @return integer
*/
public function getUnid(): int
{
return intval($this->bind->getAttr('unid'));
}
/**
* 获取终端编号
* @return integer
*/
public function getUsid(): int
{
return intval($this->bind->getAttr('id'));
}
/**
* 生成授权令牌
* @param boolean $expire
* @return AccountInterface
* @throws \think\admin\Exception
*/
public function token(bool $expire = true): AccountInterface
{
// 百分之一概率清理令牌
if (mt_rand(1, 1000) < 10) {
PluginAccountAuth::mk()->whereBetween('time', [1, time()])->delete();
}
$usid = $this->bind->getAttr('id');
// 查询该通道历史授权记录
if ($this->auth->isEmpty()) {
$where = ['usid' => $usid, 'type' => $this->type];
$this->auth = PluginAccountAuth::mk()->where($where)->findOrEmpty();
}
// 生成新令牌数据
if ($this->auth->isEmpty()) {
do $check = ['type' => $this->type, 'token' => md5(uniqid(strval(rand(0, 999))))];
while (PluginAccountAuth::mk()->master()->where($check)->findOrEmpty()->isExists());
$time = $this->expire > 0 ? $this->expire + time() : 0;
$this->auth->save($check + ['usid' => $usid, 'time' => $time]);
}
return $expire ? $this->expire() : $this;
}
/**
* 延期令牌时间
* @return AccountInterface
* @throws \think\admin\Exception
*/
public function expire(): AccountInterface
{
if ($this->auth->isEmpty()) throw new Exception('无授权记录!');
$this->auth->save(['time' => $this->expire > 0 ? $this->expire + time() : 0]);
return $this;
}
/**
* 更新用户资料
* @param array $data
* @return PluginAccountBind
* @throws \think\admin\Exception
*/
private function save(array $data): PluginAccountBind
{
if (empty($data)) throw new Exception('资料不能为空!');
$data['extra'] = array_merge($this->bind->getAttr('extra'), $data['extra'] ?? []);
// 写入默认头像内容
if (empty($data['headimg']) && empty($this->bind->getAttr('headimg'))) {
$data['headimg'] = Account::headimg();
}
// 自动生成账号昵称
if (empty($data['nickname']) && $this->bind->getAttr('nickname')) {
$name = Account::get($this->type)['name'] ?? $this->type;
$data['nickname'] = "{$name}{$this->bind->getAttr('id')}";
}
// 更新写入终端账号
if ($this->bind->save($data) && $this->bind->isExists()) {
return $this->bind->refresh();
} else {
throw new Exception('资料保存失败!');
}
}
/**
* 生成用户编号
* @return string
*/
private function userCode(): string
{
return CodeExtend::uniqidNumber(16, 'U');
}
}

View File

@ -0,0 +1,163 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service\contract;
use plugin\account\model\PluginAccountUser;
/**
* 用户账号接口类
* @class AccountInterface
* @package plugin\account\service\contract
*/
interface AccountInterface
{
/**
* 读取子账号资料
* @param boolean $rejwt
* @param boolean $refresh
* @return array
*/
public function get(bool $rejwt = false, bool $refresh = false): array;
/**
* 设置子账号资料
* @param array $data 用户资料
* @param boolean $rejwt 返回令牌
* @return array
*/
public function set(array $data = [], bool $rejwt = false): array;
/**
* 初始化通道
* @param string|array $token
* @param boolean $isjwt
* @return AccountInterface
*/
public function init($token = '', bool $isjwt = true): AccountInterface;
/**
* 获取用户模型
* @return PluginAccountUser
*/
public function user(): PluginAccountUser;
/**
* 获取用户编号
* @return string
*/
public function getCode(): string;
/**
* 获取终端类型
* @return string
*/
public function getType(): string;
/**
* 获取用户编号
* @return integer
*/
public function getUnid(): int;
/**
* 获取终端编号
* @return integer
*/
public function getUsid(): int;
/**
* 绑定主账号
* @param array $map 主账号条件
* @param array $data 主账号资料
* @return array
*/
public function bind(array $map, array $data = []): array;
/**
* 解绑主账号
* @return array
*/
public function unBind(): array;
/**
* 判断绑定主账号
* @return boolean
*/
public function isBind(): bool;
/**
* 判断是否空账号
* @return bool
*/
public function isNull(): bool;
/**
* 获取关联终端
* @return array
*/
public function allBind(): array;
/**
* 解除终端关联
* @param integer $usid 终端编号
* @return array
*/
public function delBind(int $usid): array;
/**
* 验证终端密码
* @param string $pass
* @return boolean
*/
public function pwdVerify(string $pass): bool;
/**
* 修改终端密码
* @param string $pass 待修改密码
* @param boolean $event 触发事件
* @return boolean
*/
public function pwdModify(string $pass, bool $event = true): bool;
/**
* 刷新账号序号
* @return array
*/
public function recode(): array;
/**
* 检查是否有效
* @return array
* @throws \think\admin\Exception
*/
public function check(): array;
/**
* 生成授权令牌
* @return \plugin\account\service\contract\AccountInterface
* @throws \think\db\exception\DbException
*/
public function token(): AccountInterface;
/**
* 延期令牌时间
* @return \plugin\account\service\contract\AccountInterface
*/
public function expire(): AccountInterface;
}

View File

@ -0,0 +1,63 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service;
namespace plugin\account\service\contract;
/**
* 通用短信接口类
* @class MessageInterface
* @package plugin\account\service\contract
*/
interface MessageInterface
{
/**
* 初始化短信通道
* @return static
* @throws \think\admin\Exception
*/
public function init(array $config = []): MessageInterface;
/**
* 发送短信内容
* @param string $code 短信模板CODE
* @param string $phone 接收手机号码
* @param array $params 短信模板变量
* @param array $options 其他配置参数
* @return array
* @throws \think\admin\Exception
*/
public function send(string $code, string $phone, array $params = [], array $options = []): array;
/**
* 发送短信验证码
* @param string $scene 业务场景
* @param string $phone 手机号码
* @param array $params 模板变量
* @param array $options 其他配置
* @return array
*/
public function verify(string $scene, string $phone, array $params = [], array $options = []): array;
/**
* 获取区域配置
* @return array
*/
public static function regions(): array;
}

View File

@ -0,0 +1,75 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service\contract;
use plugin\account\model\PluginAccountMsms;
use think\admin\Exception;
/**
* 短信通用接口
* @class MessageUsageTrait
* @package plugin\account\service\contract
*/
trait MessageUsageTrait
{
/**
* 业务场景
* @var string[]
*/
protected $scenes = [];
/**
* 获取短信区域配置
* @return array[]
*/
public static function regions(): array
{
return static::$regions ?? [];
}
/**
* 根据场景配置发送验证码
* @param string $scene 业务场景
* @param string $phone 手机号码
* @param array $params 模板变量
* @param array $options 其他配置
* @return array
* @throws \think\admin\Exception
*/
public function verify(string $scene, string $phone, array $params = [], array $options = []): array
{
$scenes = array_change_key_case($this->scenes);
if (empty($scenes) || empty($scenes[strtolower($scene)])) {
throw new Exception('业务场景未配置!');
}
$result = $this->send($scenes[strtolower($scene)], $phone, $params, $options);
PluginAccountMsms::mk()->save([
'unid' => intval(sysvar('plugin_account_user_unid')),
'usid' => intval(sysvar('plugin_account_user_usid')),
'type' => class_basename(static::class),
'smsid' => $result['smsid'] ?? '',
'scene' => $scene,
'phone' => $phone,
'status' => 1,
'result' => json_encode($result['result'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
'params' => json_encode($result['params'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
]);
return $result;
}
}

View File

@ -0,0 +1,153 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace plugin\account\service\message;
use plugin\account\service\contract\MessageInterface;
use plugin\account\service\contract\MessageUsageTrait;
use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\admin\extend\HttpExtend;
/**
* 阿里云短信服务
* @class Alisms
* @package plugin\account\service\message
*/
class Alisms implements MessageInterface
{
use MessageUsageTrait;
private $keyid;
private $secret;
private $region;
private $getway;
private $signtx;
protected static $regions = [
'cn-qingdao' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北1青岛'],
'cn-beijing' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北2北京'],
'cn-zhangjiakou' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北3张家口'],
'cn-huhehaote' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北5呼和浩特'],
'cn-wulanchabu' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北6乌兰察布'],
'cn-hangzhou' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华东1杭州'],
'cn-shanghai' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华东2上海'],
'cn-shenzhen' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华南1深圳'],
'cn-chengdu' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '西南1成都'],
'cn-hongkong' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '中国(香港)'],
'ap-northeast-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '日本(东京)'],
'ap-southeast-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '新加坡'],
'ap-southeast-2' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '澳大利亚(悉尼)'],
'ap-southeast-3' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '马来西亚(吉隆坡)'],
'ap-southeast-5' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '印度尼西亚(雅加达)'],
'us-east-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '美国(弗吉尼亚)'],
'us-west-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '美国(硅谷)'],
'eu-west-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '英国(伦敦)'],
'eu-central-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '德国(法兰克福)'],
'ap-south-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '印度(孟买)'],
'me-east-1' => ['host' => 'dysmsapi.ap-southeast-1.aliyuncs.com', 'name' => '阿联酋(迪拜)'],
'cn-hangzhou-finance' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华东1 金融云'],
'cn-shanghai-finance-1' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华东2 金融云'],
'cn-shenzhen-finance-1' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华南1 金融云'],
'cn-beijing-finance-1' => ['host' => 'dysmsapi.aliyuncs.com', 'name' => '华北2 金融云'],
];
/**
* 初始化短信通道
* @return $this
* @throws \think\admin\Exception
*/
public function init(array $config = []): MessageInterface
{
$options = array_merge(sysdata('plugin.account.smscfg'), $config);
$this->keyid = $options['alisms_keyid'] ?? '';
$this->secret = $options['alisms_secret'] ?? '';
$this->signtx = $options['alisms_signtx'] ?? '';
$this->region = $options['alisms_region'] ?? 'cn-shanghai';
$this->scenes = $options['alisms_scenes'] ?? [];
$this->getway = self::$regions[$this->region]['host'] ?? 'dysmsapi.aliyuncs.com';
return $this;
}
/**
* 发送短信内容
* @param string $code 短信模板CODE
* @param string $phone 接收手机号码
* @param array $params 短信模板变量
* @param array $options 其他配置参数
* @return array
* @throws \think\admin\Exception
*/
public function send(string $code, string $phone, array $params = [], array $options = []): array
{
$result = $this->request($params = array_merge([
'SignName' => $this->signtx,
'PhoneNumbers' => $phone,
'TemplateCode' => $code,
'TemplateParam' => json_encode((object)$params)
], $options));
return ['smsid' => $result['BizId'], 'params' => $params, 'result' => $result];
}
/**
* 生成接口请求 TOKEN
* @param array $params
* @param string $action
* @param string $method
* @return array
* @throws \think\admin\Exception
*/
protected function request(array $params = [], string $action = 'SendSms', string $method = 'POST'): array
{
date_default_timezone_set('UTC');
$querys = array_merge([
'AccessKeyId' => $this->keyid,
'Action' => $action,
'Format' => 'JSON',
'RegionId' => $this->region,
'SignatureMethod' => 'HMAC-SHA1',
'SignatureNonce' => CodeExtend::uuid(),
'SignatureVersion' => '1.0',
'Timestamp' => date('Y-m-d\TH:i:s\Z'),
'Version' => '2017-05-25'
], $params);
$result = HttpExtend::request($method, "https://{$this->getway}", [
'data' => $params, 'query' => ['Signature' => $this->buildSign($method, $querys)] + $querys,
]);
if (is_string($result) && is_array($json = json_decode($result, true))) {
if (isset($json['Code']) && $json['Code'] === 'OK') return $json;
throw new Exception($json['Message'] ?? $result, 500, $json);
} else {
throw new Exception('接口调用失败!' . var_export($result, true), 500);
}
}
/**
* 生成数据签名
* @param string $method
* @param array $querys
* @return string
*/
private function buildSign(string $method, array $querys): string
{
[$vars] = [[], ksort($querys)];
foreach ($querys as $k => $v) $vars[] = urlencode($k) . '=' . urlencode($v);
return base64_encode(hash_hmac('sha1', "{$method}&%2F&" . urlencode(join('&', $vars)), "{$this->secret}&", true));
}
}

View File

@ -0,0 +1,67 @@
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body ta-pl-40">
<label class="layui-form-item block relative">
<span class="help-label"><b>认证有效时间</b>Expire Time</span>
<input class="layui-input" name="expire" type="number" vali-name="认证有效时间" data-blur-number="0" min="0" required value="{$data.expire|default='3600'}">
<span class="help-block">设置为 0 表示永不过期,建议设置有效时间达到系统自动回收令牌。</span>
</label>
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>登录自动注册</b>Auto Register</span>
<div class="layui-textarea help-checks">
{empty name='data.disRegister'}{php}$data['disRegister']=0;{/php}{/empty}
{foreach ['启用自动注册','禁止自动注册'] as $k=>$v}
<label class="think-radio">
{if isset($data.disRegister) and $data.disRegister eq $k}
<input type="radio" name="disRegister" value="{$k}" lay-ignore checked> {$v}
{else}
<input type="radio" name="disRegister" value="{$k}" lay-ignore> {$v}
{/if}
</label>
{/foreach}
</div>
<span class="help-block">启用自动登录时,通过验证码登录时账号不存在会自动创建!</span>
</div>
<label class="layui-form-item block relative">
<span class="help-label"><b>默认昵称前缀</b>NickName Prefix</span>
<input class="layui-input" name="userPrefix" vali-name="默认昵称前缀" placeholder="请输入默认昵称前缀" required maxlength="20" value="{$data.userPrefix|default='用户'}">
<span class="help-block">用户绑定账号后会自动使用此前缀与手机号后4位拼接为新默认昵称。</span>
</label>
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>默认用户头像</b>Default Headimg</span>
<div class="layui-input-wrap">
<label class="relative label-required-null">
<input class="layui-input layui-bg-gray" data-tips-hover data-tips-image readonly name="headimg" vali-name="用户默认头像" required value="{$data.headimg|default=''}">
</label>
<i class="input-right-icon pointer layui-icon layui-icon-upload" data-file data-type="gif,jpg,png,jpeg" data-field="headimg"></i>
</div>
<span class="help-block">当用户未设置头像时,自动使用此头像设置的图片链接。</span>
</div>
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>开放接口通道</b>Interface Types</span>
<div class="layui-textarea help-checks">
{foreach $types as $k=>$v}
<label class="think-checkbox" data-width style="width:120px" title="{$v.name|lang}">
{empty name='v.status'}
<input type="checkbox" name="types[]" value='{$k}' lay-ignore> {$v.name|lang}
{else}
<input type="checkbox" name="types[]" value='{$k}' lay-ignore checked> {$v.name|lang}
{/empty}
</label>
{/foreach}
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>

View File

@ -0,0 +1,80 @@
{extend name='table'}
{block name="button"}
<!--{if auth("config")}-->
<button data-modal="{:url('config')}" data-width="800px" data-title="账号接口配置" class='layui-btn layui-btn-sm layui-btn-primary'>账号配置</button>
<!--{/if}-->
{/block}
{block name="content"}
<div class="layui-tab layui-tab-card">
<ul class="layui-tab-title">
{foreach ['index'=>'用户管理','recycle'=>'回 收 站'] as $k=>$v}{if isset($type) and $type eq $k}
<li data-open="{:url('index')}?type={$k}" class="layui-this">{$v}</li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}</li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='device/index_search'}
<table id="UserTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
</div>
<script>
$(function () {
let $table = $('#UserTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'desc'},
cols: [[
{field: 'id', hide: true},
{checkbox: true, fixed: true},
{field: 'sort', title: '排序权重', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
{field: 'headimg', title: '头像', width: 60, align: 'center', templet: '<div>{{-showTableImage(d.headimg,true)}}</div>'},
{field: 'type', title: '终端类型', minWidth: 110, align: 'center', templet: '<div>{{d.type_name||"-"}}</div>'},
{field: 'phone', title: '绑定手机', minWidth: 110, align: 'center', templet: '<div>{{d.phone||"-"}}</div>'},
{field: 'username', title: '用户姓名', minWidth: 100, align: 'center', templet: '<div>{{d.username||"-"}}</div>'},
{field: 'nickname', title: '用户昵称', minWidth: 100, align: 'center', templet: '<div>{{d.nickname||"-"}}</div>'},
{
field: 'id', title: '关联账号', minWidth: 100, align: 'center', templet: function (d) {
return d.user ? d.user.code : '-';
}
},
{field: 'status', title: '账号状态', align: 'center', minWidth: 110, templet: '#StatusSwitchTpl'},
{field: 'create_time', title: '首次登录', align: 'center', minWidth: 170, sort: true},
{toolbar: '#toolbar', field: 'id', title: '操作面板', align: 'center', minWidth: 150, fixed: 'right'}
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitch)', function (obj) {
let data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
$.form.load("{:url('state')}", data, 'post', function (ret) {
let fn = () => $table.trigger('reload');
ret.code > 0 ? fn() : $.msg.error(ret.info, 3, fn);
return false;
}, false);
});
});
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchTpl">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="已激活|已禁用" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已启用</b>' : '<b class="color-red">已禁用</b>'}}
<!--{/if}-->
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputTpl">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</script>
<script type="text/html" id="toolbar">
{if auth("remove")}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要永久删除此账号吗?" data-action="{:url('remove')}" data-value="id#{{d.id}}"> </a>
{/if}
</script>
{/block}

View File

@ -0,0 +1,94 @@
<form action="{:sysuri()}" autocomplete="off" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">终端类型</label>
<div class="layui-input-inline">
<select class="layui-select" name="utype">
<option value="">-- 全部 --</option>
{foreach $types as $k=>$v}
{if isset($get.utype) and $v.field eq $get.utype}
<option selected value="{$k}">{$v.name}</option>
{else}
<option value="{$k}">{$v.name}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">绑定手机</label>
<label class="layui-input-inline">
<input class="layui-input" name="phone" placeholder="请输入绑定手机" value="{$get.phone|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">用户姓名</label>
<label class="layui-input-inline">
<input class="layui-input" name="username" placeholder="请输入用户姓名" value="{$get.username|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">用户昵称</label>
<label class="layui-input-inline">
<input class="layui-input" name="nickname" placeholder="请输入用户昵称" value="{$get.nickname|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用状态</label>
<div class="layui-input-inline">
<select class="layui-select" name="status">
<option value="">-- 全部 --</option>
{foreach ['已冻结的用户', '已激活的用户'] as $k=>$v}
{if $k.'' eq input('status')}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">首次登录</label>
<div class="layui-input-inline">
<input class="layui-input" data-date-range name="create_at" placeholder="请选择绑定时间" value="{$get.create_at|default=''}">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
<button class="layui-btn layui-btn-primary" data-form-export="{:url('index')}?type={$type}" type="button">
<i class="layui-icon layui-icon-export"></i>
</button>
</div>
</form>
<script>
require(['excel'], function (excel) {
excel.bind(function (data) {
// 设置表格内容
data.forEach(function (item, index) {
data[index] = [
{v: item.id, t: 'n'},
item.type_name || item.type || '',
item.username || '-',
item.nickname || '-',
item.phone || '-',
(item.user || {}).code || '-',
item.create_time || '',
];
});
// 设置表头内容
data.unshift(['ID', '终端类型', '用户姓名', '用户昵称', '绑定手机', '关联账号', '首次登录']);
// 设置表格样式
return this.withStyle(data, {A: 60, B: 80, C: 80, E: 80});
}, '用户账号数据' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>

View File

@ -0,0 +1,22 @@
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body ta-pl-40">
{foreach $types as $k=>$v}
<label class="think-checkbox" data-width style="width:120px" title="{$v.name|lang}">
{empty name='v.status'}
<input type="checkbox" name="types[]" value='{$k}' lay-ignore> {$v.name|lang}
{else}
<input type="checkbox" name="types[]" value='{$k}' lay-ignore checked> {$v.name|lang}
{/empty}
</label>
{/foreach}
</div>
<div class="hr-line-dashed"></div>
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>

View File

@ -0,0 +1,70 @@
{extend name='table'}
{block name="content"}
<div class="layui-tab layui-tab-card">
<ul class="layui-tab-title">
{foreach ['index'=>'用户管理','recycle'=>'回 收 站'] as $k=>$v}{if isset($type) and $type eq $k}
<li data-open="{:url('index')}?type={$k}" class="layui-this">{$v}</li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}</li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='master/index_search'}
<table id="UserTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
</div>
<script>
$(function () {
let $table = $('#UserTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'desc'},
cols: [[
{field: 'id', hide: true},
{checkbox: true, fixed: true},
{field: 'sort', title: '排序权重', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
{field: 'headimg', title: '头像', width: 60, align: 'center', templet: '<div>{{-showTableImage(d.headimg,true)}}</div>'},
{field: 'code', title: '用户编号', minWidth: 100, align: 'center', templet: '<div>{{d.code||"-"}}</div>'},
{field: 'phone', title: '绑定手机', minWidth: 110, align: 'center', templet: '<div>{{d.phone||"-"}}</div>'},
{field: 'email', title: '绑定邮箱', minWidth: 110, align: 'center', templet: '<div>{{d.email||"-"}}</div>'},
{field: 'username', title: '用户姓名', minWidth: 100, align: 'center', templet: '<div>{{d.username||"-"}}</div>'},
{field: 'nickname', title: '用户昵称', minWidth: 100, align: 'center', templet: '<div>{{d.nickname||"-"}}</div>'},
{field: 'status', title: '账号状态', align: 'center', minWidth: 110, templet: '#StatusSwitchTpl'},
{field: 'create_time', title: '绑定时间', align: 'center', minWidth: 170, sort: true},
{toolbar: '#toolbar', title: '操作面板', align: 'center', minWidth: 100, fixed: 'right'}
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitch)', function (obj) {
let data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
$.form.load("{:url('state')}", data, 'post', function (ret) {
let fn = () => $table.trigger('reload');
ret.code > 0 ? fn() : $.msg.error(ret.info, 3, fn)
return false;
}, false);
});
});
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchTpl">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="已激活|已禁用" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已启用</b>' : '<b class="color-red">已禁用</b>'}}
<!--{/if}-->
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputTpl">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</script>
<script type="text/html" id="toolbar">
{if auth("remove")}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要永久删除此账号吗?" data-action="{:url('remove')}" data-value="id#{{d.id}}"> </a>
{/if}
</script>
{/block}

View File

@ -0,0 +1,71 @@
<form action="{:sysuri()}" autocomplete="off" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">用户编号</label>
<label class="layui-input-inline">
<input class="layui-input" name="code" placeholder="请输入用户编号" value="{$get.code|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">绑定手机</label>
<label class="layui-input-inline">
<input class="layui-input" name="phone" placeholder="请输入绑定手机" value="{$get.phone|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">绑定邮箱</label>
<label class="layui-input-inline">
<input class="layui-input" name="email" placeholder="请输入绑定邮箱" value="{$get.email|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">用户姓名</label>
<label class="layui-input-inline">
<input class="layui-input" name="username" placeholder="请输入用户姓名" value="{$get.username|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">绑定时间</label>
<div class="layui-input-inline">
<input class="layui-input" data-date-range name="create_at" placeholder="请选择绑定时间" value="{$get.create_at|default=''}">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
<button class="layui-btn layui-btn-primary" data-form-export="{:url('index')}?type={$type|default='index'}" type="button">
<i class="layui-icon layui-icon-export"></i>
</button>
</div>
</form>
<script>
require(['excel'], function (excel) {
excel.bind(function (data) {
// 设置表格内容
data.forEach(function (item, index) {
data[index] = [
{v: item.id, t: 'n'},
item.code || '-',
item.username || '-',
item.nickname || '-',
item.phone || '-',
item.email || '-',
item.create_time || '',
];
});
// 设置表头内容
data.unshift(['ID', '用户编号', '用户姓名', '用户昵称', '绑定手机', '绑定邮箱', '绑定时间']);
// 设置表格样式
return this.withStyle(data, {A: 60, B: 110, C: 80, E: 80});
}, '用户账号数据' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>

View File

@ -0,0 +1,47 @@
<form action="{:sysuri()}" class='layui-form layui-card' data-auto="true" method="post">
<div class="layui-card-body ta-pl-40">
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>服务区域</b>Region</span>
<select name="alisms_region" class="layui-select">
{foreach $regions as $k => $region}
{if isset($vo.alisms_region) and $k eq $vo.alisms_region}
<option selected value="{$k}">[ {$k} ] {$region.name}</option>
{else}
<option value="{$k}">[ {$k} ] {$region.name}</option>
{/if}{/foreach}
</select>
</div>
<label class="layui-form-item block relative">
<span class="help-label"><b>阿里云账号</b>AccessKeyId</span>
<input class="layui-input" name="alisms_keyid" vali-name="阿里云账号" placeholder="请输入阿里云账号" required value="{$vo.alisms_keyid|default=''}">
</label>
<label class="layui-form-item block relative">
<span class="help-label"><b>阿里云密钥</b>AccessKeySecret</span>
<input class="layui-input" name="alisms_secret" vali-name="阿里云密钥" placeholder="请输入阿里云密钥" required value="{$vo.alisms_secret|default=''}">
</label>
<label class="layui-form-item block relative">
<span class="help-label"><b>短信签名</b>SignName</span>
<input class="layui-input" name="alisms_signtx" vali-name="短信签名" placeholder="请输入短信签名" required value="{$vo.alisms_signtx|default=''}">
</label>
{foreach $scenes as $k=>$s}
<label class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>{$s}</b>{:ucfirst(strtolower($k))} Code</span>
<input class="layui-input" required name="alisms_scenes[{$k}]" value="{$vo.alisms_scenes[$k]|default=''}" placeholder="请输入短信模板编号">
</label>
{/foreach}
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center ta-mt-20">
<button class="layui-btn" type="submit">保存配置</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
</div>
</form>

View File

@ -0,0 +1,45 @@
{extend name='table'}
{block name="button"}
<!--{if auth('config')}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-modal="{:url('config')}">短信配置</a>
<!--{/if}-->
{/block}
{block name="content"}
<div class="think-box-shadow">
{include file='message/index_search'}
<label class="layui-hide"><textarea id="SecensData">{$secens|default=''|json_encode}</textarea></label>
<table id="MessageData" data-url="{:sysuri()}"></table>
<script>
let secens = JSON.parse(document.getElementById('SecensData').value || '{}');
function showScene(scene) {
return secens[scene] || scene;
}
$(function () {
$('#MessageData').layTable({
even: true, height: 'full', loading: true,
sort: {field: 'id', type: 'desc'},
cols: [[
{field: 'id', hide: true},
{field: 'smsid', title: '消息编号', sort: true, minWidth: 100, width: '12%', align: 'center'},
{field: 'type', title: '短信类型', sort: true, minWidth: 90, width: '8%', align: 'center'},
{field: 'phone', title: '发送手机', sort: true, minWidth: 100, width: '10%', align: 'center'},
{field: 'scene', title: '业务场景', align: 'center', minWidth: 100, width: '8%', templet: '<div>{{-showScene(d.scene_name)}}</div>'},
{field: 'params', title: '短信内容', align: 'center'},
{field: 'result', title: '返回结果', align: 'center'},
{
field: 'status', title: '执行结果', minWidth: 80, width: '8%', sort: true, align: 'center', templet: function (d) {
return ['<b class="color-red">失败</b>', '<b class="color-green">成功</b>'][d.status];
}
},
{field: 'create_time', title: '发送时间', width: 170, align: 'center', sort: true},
]]
});
});
</script>
</div>
{/block}

View File

@ -0,0 +1,68 @@
<fieldset>
<legend>{:lang('条件搜索')}</legend>
<form action="{$request->url()}" data-table-id="MessageData" autocomplete="off" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">消息编号</label>
<label class="layui-input-inline">
<input class="layui-input" name="smsid" placeholder="请输入消息编号" value="{$get.smsid|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">发送手机</label>
<label class="layui-input-inline">
<input class="layui-input" name="phone" placeholder="请输入发送手机" value="{$get.phone|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">业务场景</label>
<div class="layui-input-inline">
<select class="layui-select" name="scene">
<option value="">-- 全部 --</option>
{foreach $scenes as $k => $v}
{if isset($get.scene) and $get.scene eq $k}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline layui-hide">
<label class="layui-form-label">短信内容</label>
<label class="layui-input-inline">
<input class="layui-input" name="content" placeholder="请输入短信内容" value="{$get.content|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">执行结果</label>
<div class="layui-input-inline">
<select class="layui-select" name="status">
<option value="">-- 全部 --</option>
{foreach ['发送失败','发送成功'] as $k => $v}
{if $k.'' eq input('status')}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">发送时间</label>
<div class="layui-input-inline">
<input class="layui-input" data-date-range name="create_time" placeholder="请选择发送时间" value="{$get.create_time|default=''}">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>

View File

@ -0,0 +1,23 @@
<div class="layui-card">
{block name='style'}{/block}
{block name='header'}
{notempty name='title'}
<div class="layui-card-header">
<span class="layui-icon font-s10 color-desc ta-mr-5">&#xe65b;</span>{$title|lang}
<div class="pull-right">{block name='button'}{/block}</div>
</div>
{/notempty}
{/block}
<div class="layui-card-line"></div>
<div class="layui-card-body">
<div class="layui-card-table">
{notempty name='showErrorMessage'}
<div class="think-box-notify" type="error">
<b>系统提示:</b><span>{$showErrorMessage|raw}</span>
</div>
{/notempty}
{block name='content'}{/block}
</div>
</div>
{block name='script'}{/block}
</div>

View File

@ -0,0 +1,222 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
class InstallAccount extends Migrator
{
/**
* 创建数据库
*/
public function change()
{
$this->_create_plugin_account_auth();
$this->_create_plugin_account_bind();
$this->_create_plugin_account_msms();
$this->_create_plugin_account_user();
}
/**
* 创建数据对象
* @class PluginAccountAuth
* @table plugin_account_auth
* @return void
*/
private function _create_plugin_account_auth()
{
// 当前数据表
$table = 'plugin_account_auth';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-授权',
])
->addColumn('usid', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '终端账号'])
->addColumn('time', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '有效时间'])
->addColumn('type', 'string', ['limit' => 20, 'default' => '', 'null' => true, 'comment' => '授权类型'])
->addColumn('token', 'string', ['limit' => 32, 'default' => '', 'null' => true, 'comment' => '授权令牌'])
->addColumn('tokenv', 'string', ['limit' => 32, 'default' => '', 'null' => true, 'comment' => '授权验证'])
->addColumn('create_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('usid', ['name' => 'i8a91c286f_usid'])
->addIndex('type', ['name' => 'i8a91c286f_type'])
->addIndex('time', ['name' => 'i8a91c286f_time'])
->addIndex('token', ['name' => 'i8a91c286f_token'])
->addIndex('create_time', ['name' => 'i8a91c286f_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountBind
* @table plugin_account_bind
* @return void
*/
private function _create_plugin_account_bind()
{
// 当前数据表
$table = 'plugin_account_bind';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-终端',
])
->addColumn('unid', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '会员编号'])
->addColumn('type', 'string', ['limit' => 20, 'default' => '', 'null' => true, 'comment' => '终端类型'])
->addColumn('phone', 'string', ['limit' => 30, 'default' => '', 'null' => true, 'comment' => '绑定手机'])
->addColumn('appid', 'string', ['limit' => 30, 'default' => '', 'null' => true, 'comment' => 'APPID'])
->addColumn('openid', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'OPENID'])
->addColumn('unionid', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'UnionID'])
->addColumn('headimg', 'string', ['limit' => 500, 'default' => '', 'null' => true, 'comment' => '用户头像'])
->addColumn('nickname', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('password', 'string', ['limit' => 32, 'default' => '', 'null' => true, 'comment' => '登录密码'])
->addColumn('extra', 'text', ['default' => NULL, 'null' => true, 'comment' => '扩展数据'])
->addColumn('sort', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status', 'integer', ['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '账号状态'])
->addColumn('deleted', 'integer', ['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删,1已删)'])
->addColumn('create_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '注册时间'])
->addColumn('update_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i4ec9ee5c7_type'])
->addIndex('unid', ['name' => 'i4ec9ee5c7_unid'])
->addIndex('sort', ['name' => 'i4ec9ee5c7_sort'])
->addIndex('phone', ['name' => 'i4ec9ee5c7_phone'])
->addIndex('appid', ['name' => 'i4ec9ee5c7_appid'])
->addIndex('status', ['name' => 'i4ec9ee5c7_status'])
->addIndex('openid', ['name' => 'i4ec9ee5c7_openid'])
->addIndex('unionid', ['name' => 'i4ec9ee5c7_unionid'])
->addIndex('deleted', ['name' => 'i4ec9ee5c7_deleted'])
->addIndex('create_time', ['name' => 'i4ec9ee5c7_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountMsms
* @table plugin_account_msms
* @return void
*/
private function _create_plugin_account_msms()
{
// 当前数据表
$table = 'plugin_account_msms';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-短信',
])
->addColumn('unid', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => false, 'comment' => '账号编号'])
->addColumn('usid', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => false, 'comment' => '终端编号'])
->addColumn('type', 'string', ['limit' => 100, 'default' => '', 'null' => true, 'comment' => '短信类型'])
->addColumn('scene', 'string', ['limit' => 100, 'default' => '', 'null' => true, 'comment' => '业务场景'])
->addColumn('smsid', 'string', ['limit' => 100, 'default' => '', 'null' => true, 'comment' => '消息编号'])
->addColumn('phone', 'string', ['limit' => 100, 'default' => '', 'null' => true, 'comment' => '目标手机'])
->addColumn('result', 'string', ['limit' => 512, 'default' => '', 'null' => true, 'comment' => '返回结果'])
->addColumn('params', 'string', ['limit' => 512, 'default' => '', 'null' => true, 'comment' => '短信内容'])
->addColumn('status', 'integer', ['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '短信状态(0失败,1成功)'])
->addColumn('create_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '创建时间'])
->addColumn('update_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('type', ['name' => 'i66baec398_type'])
->addIndex('usid', ['name' => 'i66baec398_usid'])
->addIndex('unid', ['name' => 'i66baec398_unid'])
->addIndex('phone', ['name' => 'i66baec398_phone'])
->addIndex('smsid', ['name' => 'i66baec398_smsid'])
->addIndex('scene', ['name' => 'i66baec398_scene'])
->addIndex('status', ['name' => 'i66baec398_status'])
->addIndex('create_time', ['name' => 'i66baec398_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
/**
* 创建数据对象
* @class PluginAccountUser
* @table plugin_account_user
* @return void
*/
private function _create_plugin_account_user()
{
// 当前数据表
$table = 'plugin_account_user';
// 存在则跳过
if ($this->hasTable($table)) return;
// 创建数据表
$this->table($table, [
'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '插件-账号-资料',
])
->addColumn('code', 'string', ['limit' => 16, 'default' => '', 'null' => true, 'comment' => '用户编号'])
->addColumn('phone', 'string', ['limit' => 20, 'default' => '', 'null' => true, 'comment' => '用户手机'])
->addColumn('email', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户邮箱'])
->addColumn('unionid', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => 'UnionID'])
->addColumn('username', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户姓名'])
->addColumn('nickname', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '用户昵称'])
->addColumn('password', 'string', ['limit' => 32, 'default' => '', 'null' => true, 'comment' => '认证密码'])
->addColumn('headimg', 'string', ['limit' => 500, 'default' => '', 'null' => true, 'comment' => '用户头像'])
->addColumn('region_prov', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在省份'])
->addColumn('region_city', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在城市'])
->addColumn('region_area', 'string', ['limit' => 99, 'default' => '', 'null' => true, 'comment' => '所在区域'])
->addColumn('remark', 'string', ['limit' => 500, 'default' => '', 'null' => true, 'comment' => '备注(内部使用)'])
->addColumn('extra', 'text', ['default' => NULL, 'null' => true, 'comment' => '扩展数据'])
->addColumn('sort', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重'])
->addColumn('status', 'integer', ['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '用户状态(0拉黑,1正常)'])
->addColumn('deleted', 'integer', ['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删,1已删)'])
->addColumn('create_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '注册时间'])
->addColumn('update_time', 'datetime', ['default' => NULL, 'null' => true, 'comment' => '更新时间'])
->addIndex('code', ['name' => 'iddb76b051_code'])
->addIndex('sort', ['name' => 'iddb76b051_sort'])
->addIndex('phone', ['name' => 'iddb76b051_phone'])
->addIndex('email', ['name' => 'iddb76b051_email'])
->addIndex('status', ['name' => 'iddb76b051_status'])
->addIndex('unionid', ['name' => 'iddb76b051_unionid'])
->addIndex('deleted', ['name' => 'iddb76b051_deleted'])
->addIndex('username', ['name' => 'iddb76b051_username'])
->addIndex('nickname', ['name' => 'iddb76b051_nickname'])
->addIndex('region_prov', ['name' => 'iddb76b051_region_prov'])
->addIndex('region_city', ['name' => 'iddb76b051_region_city'])
->addIndex('region_area', ['name' => 'iddb76b051_region_area'])
->addIndex('create_time', ['name' => 'iddb76b051_create_time'])
->create();
// 修改主键长度
$this->table($table)->changeColumn('id', 'integer', ['limit' => 11, 'identity' => true]);
}
}

View File

@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | Account Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-account
// | github 代码仓库https://github.com/zoujingli/think-plugs-account
// +----------------------------------------------------------------------
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', -1);
/**
* 数据库更新补丁
* @class InstallAccount20240308
* @package think\migration\Migrator
*/
class InstallAccount20240308 extends Migrator
{
/**
* 更新数据库
*/
public function change()
{
// 短信记录表修正字段
$table = $this->table('plugin_account_msms');
$table->hasColumn('unid') || $table->renameColumn('uuid', 'unid')->update();
// 用户表增加密码字段
$table = $this->table('plugin_account_user');
$table->hasColumn('password') || $table->addColumn('password', 'string', [
'limit' => 32, 'default' => '', 'null' => true, 'after' => 'nickname', 'comment' => '登录密码'
])->update();
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace think\admin\tests;
use PHPUnit\Framework\TestCase;
use plugin\account\service\Account;
class AccountTest extends TestCase
{
public function testAddType()
{
Account::add('test', '测试接口');
$this->assertIsString(Account::field('test'));
}
public function testGetTypes()
{
$info = Account::types();
$this->assertIsArray($info);
}
public function testChangeType()
{
$field = Account::field('web');
$this->assertNotEmpty($field);
Account::set('web', 0);
$field = Account::field('web');
$this->assertEmpty($field);
try {
Account::mk('web');
} catch (\think\admin\Exception $exception) {
$this->assertStringContainsString('未定义', $exception->getMessage());
}
}
public function testAddAccount()
{
$account = Account::mk(Account::WAP);
$info = $account->set(['phone' => '138888888888', 'nickname' => '账号创建测试'], true);
$this->assertEquals($info['id'], $account->get()['id'], '创建用户测试成功!');
}
public function testBindAccount()
{
$username = 'UserName' . uniqid();
$account = Account::mk(Account::WAP);
$phone = '13888888' . mt_rand(1000, 9999);
$account->set(['phone' => $phone]);
// 关联绑定主账号
$info = $account->bind(['phone' => $phone], ['username' => $username]);
$this->assertEquals($info['user']['username'], $username, '账号绑定关联成功!');
// 刷新主账号序号
$news = $account->recode();
$this->assertNotEquals($info['user']['code'], $news['user']['code'], '刷新用户序号成功');
}
public function testUnbindAccount()
{
$account = Account::mk(Account::WAP);
$account->set(['phone' => '138888888888']);
// 关联绑定主账号
$info = $account->bind(['phone' => '138888888888'], ['username' => 'UserName' . uniqid()]);
$this->assertNotEmpty($info['user'], '账号绑定成功!');
$info = $account->unBind();
$this->assertEmpty($info['user'], '账号解绑成功!');
}
}

View File

@ -0,0 +1,22 @@
<?php
use think\facade\Db;
include_once dirname(__DIR__) . '/vendor/autoload.php';
include_once dirname(__DIR__) . '/vendor/topthink/framework/src/helper.php';
Db::setConfig([
'default' => 'mysql',
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'admin_v6',
'username' => 'admin_v6',
'password' => 'FbYBHcWKr2',
'hostport' => '3306',
'charset' => 'utf8mb4',
'debug' => true,
],
],
]);

View File

@ -0,0 +1,3 @@
*.js linguist-language=php
*.css linguist-language=php
*.html linguist-language=php

View File

@ -0,0 +1,26 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
permissions: write-all
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

11
plugin/think-plugs-admin/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
.env
.git
.svn
.idea
.fleet
.vscode
.DS_Store
*.log
*.zip
/vendor
/composer.lock

View File

@ -0,0 +1,54 @@
{
"type": "think-admin-plugin",
"name": "zoujingli/think-plugs-admin",
"license": "MIT",
"homepage": "https://thinkadmin.top",
"description": "Admin Plugin for ThinkAdmin",
"authors": [
{
"name": "Anyon",
"email": "zoujingli@qq.com"
}
],
"require": {
"php": ">7.1",
"ext-json": "*",
"topthink/framework": "^6.0|^8.0",
"topthink/think-view": "^1.0|^2.0",
"zoujingli/ip2region": "^1.0|^2.0|@dev",
"zoujingli/think-install": "^1.0|@dev",
"zoujingli/think-library": "^6.1|@dev",
"zoujingli/think-plugs-static": "^1.0|@dev"
},
"autoload": {
"psr-4": {
"app\\admin\\": "src"
}
},
"extra": {
"think": {
"services": [
"app\\admin\\Service"
]
},
"plugin": {
"copy": {
"stc/database": "database/migrations"
}
},
"config": {
"type": "module",
"name": "系统后台管理",
"document": "https://thinkadmin.top/plugin/think-plugs-admin.html",
"description": "后台基础管理模块,系统账号及安全配置管理。"
}
},
"prefer-stable": true,
"minimum-stability": "dev",
"config": {
"sort-packages": true,
"allow-plugins": {
"zoujingli/think-install": true
}
}
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2014-2024 Anyon <zoujingli@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,77 @@
# ThinkPlugsAdmin for ThinkAdmin
[![Latest Stable Version](https://poser.pugx.org/zoujingli/think-plugs-admin/v/stable)](https://packagist.org/packages/zoujingli/think-plugs-admin)
[![Latest Unstable Version](https://poser.pugx.org/zoujingli/think-plugs-admin/v/unstable)](https://packagist.org/packages/zoujingli/think-plugs-admin)
[![Total Downloads](https://poser.pugx.org/zoujingli/think-plugs-admin/downloads)](https://packagist.org/packages/zoujingli/think-plugs-admin)
[![Monthly Downloads](https://poser.pugx.org/zoujingli/think-plugs-admin/d/monthly)](https://packagist.org/packages/zoujingli/think-plugs-admin)
[![Daily Downloads](https://poser.pugx.org/zoujingli/think-plugs-admin/d/daily)](https://packagist.org/packages/zoujingli/think-plugs-admin)
[![PHP Version](https://thinkadmin.top/static/icon/php-7.1.svg)](https://thinkadmin.top)
[![License](https://thinkadmin.top/static/icon/license-mit.svg)](https://mit-license.org)
**ThinkPlugsAdmin** 是 **ThinkAdmin** 的核心插件,提供后台基础管理模块功能,基于 MIT 协议开源,免费可商用!
我们的主代码仓库位于 Gitee而 Github 则作为镜像仓库,主要用于发布 Composer 包,方便广大开发者获取和使用。
请注意,安装此插件将占用并替换 `app/admin` 目录(采用先删除再写入的方式)。若您曾对 `app/admin` 进行了自定义修改,我们不建议您安装此插件,以避免修改内容丢失。
当您使用 `Composer` 卸载此插件时,请留意它并不会自动删除 `app/admin` 目录及对应的数据表,这些操作需要您手动完成。
如果您不希望 `app/admin` 目录被插件更新替换,有一个简单的方法可以避免这一情况:在 `app/admin` 目录下创建一个名为 `ignore` 的文件(例如 `app/admin/ignore`,请确保文件名没有后缀)。这样,即使执行了插件的安装或更新操作,该目录也将被忽略,不会被替换更新。
### 插件文档
https://thinkadmin.top/plugin/think-plugs-admin.html
### 安装插件
```shell
### 安装前建议尝试更新所有组件
composer update --optimize-autoloader
### 注意,插件仅支持在 ThinkAdmin v6.1 中使用
composer require zoujingli/think-plugs-admin --optimize-autoloader
```
### 卸载插件
```shell
### 插件卸载不会删除数据表和 app/admin 的代码
### 卸载后通过 composer update 时不会再更新,其他依赖除外
composer remove zoujingli/think-plugs-admin
```
### 功能节点
可根据下面的功能节点配置菜单和访问权限,按钮操作级别的节点未展示!
* 系统参数配置:`admin/config/index`
* 系统任务管理:`admin/queue/index`
* 系统日志管理:`admin/oplog/index`
* 数据字典管理:`admin/base/index`
* 系统文件管理:`admin/file/index`
* 系统菜单管理:`admin/menu/index`
* 系统权限管理:`admin/auth/index`
* 系统用户管理:`admin/user/index`
### 插件数据库
本插件涉及数据表有:
* 系统-权限:`system_auth`
* 系统-授权:`system_auth_node`
* 系统-字典:`system_base`
* 系统-配置:`system_config`
* 系统-数据:`system_data`
* 系统-文件:`system_file`
* 系统-菜单:`system_menu`
* 系统-日志:`system_oplog`
* 系统-任务:`system_queue`
* 系统-用户:`system_user`
### 版权说明
**ThinkPlugsAdmin** 遵循 **MIT** 开源协议发布,并免费提供使用。
本项目包含的第三方源码和二进制文件的版权信息将另行标注,请在对应文件查看。
版权所有 Copyright © 2014-2024 by ThinkAdmin (https://thinkadmin.top) All rights reserved。

View File

@ -0,0 +1,69 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin;
use think\admin\Plugin;
/**
* 插件服务注册
* @class Service
* @package app\admin
*/
class Service extends Plugin
{
/**
* 定义插件名称
* @var string
*/
protected $appName = '系统管理';
/**
* 定义安装包名
* @var string
*/
protected $package = 'zoujingli/think-plugs-admin';
/**
* 定义插件中心菜单
* @return array
*/
public static function menu(): array
{
return [
[
'name' => '系统配置',
'subs' => [
['name' => '系统参数配置', 'icon' => 'layui-icon layui-icon-set', 'node' => 'admin/config/index'],
['name' => '系统任务管理', 'icon' => 'layui-icon layui-icon-log', 'node' => 'admin/queue/index'],
['name' => '系统日志管理', 'icon' => 'layui-icon layui-icon-form', 'node' => 'admin/oplog/index'],
['name' => '数据字典管理', 'icon' => 'layui-icon layui-icon-code-circle', 'node' => 'admin/base/index'],
['name' => '系统文件管理', 'icon' => 'layui-icon layui-icon-carousel', 'node' => 'admin/file/index'],
['name' => '系统菜单管理', 'icon' => 'layui-icon layui-icon-layouts', 'node' => 'admin/menu/index'],
],
],
[
'name' => '权限管理',
'subs' => [
['name' => '系统权限管理', 'icon' => 'layui-icon layui-icon-vercode', 'node' => 'admin/auth/index'],
['name' => '系统用户管理', 'icon' => 'layui-icon layui-icon-username', 'node' => 'admin/user/index'],
],
],
];
}
}

View File

@ -0,0 +1,134 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemAuth;
use think\admin\model\SystemNode;
use think\admin\Plugin;
use think\admin\service\AdminService;
/**
* 系统权限管理
* @class Auth
* @package app\admin\controller
*/
class Auth extends Controller
{
/**
* 系统权限管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemAuth::mQuery()->layTable(function () {
$this->title = '系统权限管理';
}, static function (QueryHelper $query) {
$query->like('title,desc')->equal('status,utype')->dateBetween('create_at');
});
}
/**
* 修改权限状态
* @auth true
*/
public function state()
{
SystemAuth::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除系统权限
* @auth true
*/
public function remove()
{
SystemAuth::mDelete();
}
/**
* 添加系统权限
* @auth true
*/
public function add()
{
SystemAuth::mForm('form');
}
/**
* 编辑系统权限
* @auth true
*/
public function edit()
{
SystemAuth::mForm('form');
}
/**
* 表单后置数据处理
* @param array $data
*/
protected function _form_filter(array $data)
{
if ($this->request->isGet()) {
$this->title = empty($data['title']) ? "添加访问授权" : "编辑【{$data['title']}】授权";
} elseif ($this->request->post('action') === 'json') {
if ($this->app->isDebug()) AdminService::clear();
$ztree = AdminService::getTree(empty($data['id']) ? [] : SystemNode::mk()->where(['auth' => $data['id']])->column('node'));
usort($ztree, static function ($a, $b) {
if (explode('-', $a['node'])[0] !== explode('-', $b['node'])[0]) {
if (stripos($a['node'], 'plugin-') === 0) return 1;
}
return $a['node'] === $b['node'] ? 0 : ($a['node'] > $b['node'] ? 1 : -1);
});
[$ps, $cs] = [Plugin::get(), (array)$this->app->config->get('app.app_names', [])];
foreach ($ztree as &$n) $n['title'] = lang($cs[$n['node']] ?? (($ps[$n['node']] ?? [])['name'] ?? $n['title']));
$this->success('获取权限节点成功!', $ztree);
} elseif (empty($data['nodes'])) {
$this->error('未配置功能节点!');
}
}
/**
* 节点更新处理
* @param boolean $state
* @param array $post
* @return void
*/
protected function _form_result(bool $state, array $post)
{
if ($state && $this->request->post('action') === 'save') {
[$map, $data] = [['auth' => $post['id']], []];
foreach ($post['nodes'] ?? [] as $node) $data[] = $map + ['node' => $node];
SystemNode::mk()->where($map)->delete();
count($data) > 0 && SystemNode::mk()->insertAll($data);
sysoplog('系统权限管理', "配置系统权限[{$map['auth']}]授权成功");
$this->success('权限修改成功!', 'javascript:history.back()');
}
}
}

View File

@ -0,0 +1,113 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemBase;
/**
* 数据字典管理
* @class Base
* @package app\admin\controller
*/
class Base extends Controller
{
/**
* 数据字典管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemBase::mQuery()->layTable(function () {
$this->title = '数据字典管理';
$this->types = SystemBase::types();
$this->type = $this->get['type'] ?? ($this->types[0] ?? '-');
}, static function (QueryHelper $query) {
$query->where(['deleted' => 0])->equal('type');
$query->like('code,name,status')->dateBetween('create_at');
});
}
/**
* 添加数据字典
* @auth true
*/
public function add()
{
SystemBase::mForm('form');
}
/**
* 编辑数据字典
* @auth true
*/
public function edit()
{
SystemBase::mForm('form');
}
/**
* 表单数据处理
* @param array $data
* @throws \think\db\exception\DbException
*/
protected function _form_filter(array &$data)
{
if ($this->request->isGet()) {
$this->types = SystemBase::types();
$this->types[] = '--- ' . lang('新增类型') . ' ---';
$this->type = $this->get['type'] ?? ($this->types[0] ?? '-');
} else {
$map = [];
$map[] = ['deleted', '=', 0];
$map[] = ['code', '=', $data['code']];
$map[] = ['type', '=', $data['type']];
$map[] = ['id', '<>', $data['id'] ?? 0];
if (SystemBase::mk()->where($map)->count() > 0) {
$this->error("数据编码已经存在!");
}
}
}
/**
* 修改数据状态
* @auth true
*/
public function state()
{
SystemBase::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除数据记录
* @auth true
*/
public function remove()
{
SystemBase::mDelete();
}
}

View File

@ -0,0 +1,146 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\Plugin;
use think\admin\service\AdminService;
use think\admin\service\ModuleService;
use think\admin\service\RuntimeService;
use think\admin\service\SystemService;
use think\admin\Storage;
use think\admin\storage\AliossStorage;
use think\admin\storage\QiniuStorage;
use think\admin\storage\TxcosStorage;
/**
* 系统参数配置
* @class Config
* @package app\admin\controller
*/
class Config extends Controller
{
const themes = [
'default' => '默认色0',
'white' => '简约白0',
'red-1' => '玫瑰红1',
'blue-1' => '深空蓝1',
'green-1' => '小草绿1',
'black-1' => '经典黑1',
'red-2' => '玫瑰红2',
'blue-2' => '深空蓝2',
'green-2' => '小草绿2',
'black-2' => '经典黑2',
];
/**
* 系统参数配置
* @auth true
* @menu true
*/
public function index()
{
$this->title = '系统参数配置';
$this->files = Storage::types();
$this->plugins = Plugin::get(null, true);
$this->issuper = AdminService::isSuper();
$this->systemid = ModuleService::getRunVar('uni');
$this->framework = ModuleService::getLibrarys('topthink/framework');
$this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library');
if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) {
$url = url('admin/index/pass', ['id' => AdminService::getUserId()]);
$this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>", [$url]);
}
uasort($this->plugins, static function ($a, $b) {
if ($a['space'] === $b['space']) return 0;
return $a['space'] > $b['space'] ? 1 : -1;
});
$this->fetch();
}
/**
* 修改系统参数
* @auth true
* @throws \think\admin\Exception
*/
public function system()
{
if ($this->request->isGet()) {
$this->title = '修改系统参数';
$this->themes = static::themes;
$this->fetch();
} else {
$post = $this->request->post();
// 修改网站后台入口路径
if (!empty($post['xpath'])) {
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $post['xpath'])) {
$this->error('后台入口格式错误!');
}
if ($post['xpath'] !== 'admin') {
if (is_dir(syspath("app/{$post['xpath']}")) || !empty(Plugin::get($post['xpath']))) {
$this->error(lang('已存在 %s 应用!', [$post['xpath']]));
}
}
RuntimeService::set(null, [$post['xpath'] => 'admin']);
}
// 修改网站 ICON 图标,替换 public/favicon.ico
if (preg_match('#^https?://#', $post['site_icon'] ?? '')) try {
SystemService::setFavicon($post['site_icon'] ?? '');
} catch (\Exception $exception) {
trace_file($exception);
}
// 数据数据到系统配置表
foreach ($post as $k => $v) sysconf($k, $v);
sysoplog('系统配置管理', "修改系统参数成功");
$this->success('数据保存成功!', admuri('admin/config/index'));
}
}
/**
* 修改文件存储
* @auth true
* @throws \think\admin\Exception
*/
public function storage()
{
$this->_applyFormToken();
if ($this->request->isGet()) {
$this->type = input('type', 'local');
if ($this->type === 'alioss') {
$this->points = AliossStorage::region();
} elseif ($this->type === 'qiniu') {
$this->points = QiniuStorage::region();
} elseif ($this->type === 'txcos') {
$this->points = TxcosStorage::region();
}
$this->fetch("storage-{$this->type}");
} else {
$post = $this->request->post();
if (!empty($post['storage']['allow_exts'])) {
$deny = ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'];
$exts = array_unique(str2arr(strtolower($post['storage']['allow_exts'])));
if (count(array_intersect($deny, $exts)) > 0) $this->error('禁止上传可执行的文件!');
$post['storage']['allow_exts'] = join(',', $exts);
}
foreach ($post as $name => $value) sysconf($name, $value);
sysoplog('系统配置管理', "修改系统存储参数");
$this->success('修改文件存储成功!');
}
}
}

View File

@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemFile;
use think\admin\service\AdminService;
use think\admin\Storage;
/**
* 系统文件管理
* @class File
* @package app\admin\controller
*/
class File extends Controller
{
/**
* 存储类型
* @var array
*/
protected $types;
/**
* 控制器初始化
* @return void
*/
protected function initialize()
{
$this->types = Storage::types();
}
/**
* 系统文件管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemFile::mQuery()->layTable(function () {
$this->title = '系统文件管理';
$this->xexts = SystemFile::mk()->distinct()->column('xext');
}, static function (QueryHelper $query) {
$query->like('name,hash,xext')->equal('type')->dateBetween('create_at');
$query->where(['issafe' => 0, 'status' => 2, 'uuid' => AdminService::getUserId()]);
});
}
/**
* 数据列表处理
* @param array $data
* @return void
*/
protected function _page_filter(array &$data)
{
foreach ($data as &$vo) {
$vo['ctype'] = $this->types[$vo['type']] ?? $vo['type'];
}
}
/**
* 编辑系统文件
* @auth true
* @return void
*/
public function edit()
{
SystemFile::mForm('form');
}
/**
* 删除系统文件
* @auth true
* @return void
*/
public function remove()
{
if (!AdminService::isSuper()) {
$where = ['uuid' => AdminService::getUserId()];
}
SystemFile::mDelete('', $where ?? []);
}
/**
* 清理重复文件
* @auth true
* @return void
* @throws \think\db\exception\DbException
*/
public function distinct()
{
$map = ['uuid' => AdminService::getUserId()];
$db1 = SystemFile::mk()->fieldRaw('max(id) id')->where($map)->group('type,xkey');
$db2 = $this->app->db->table($db1->buildSql())->alias('dt')->field('id');
SystemFile::mk()->whereRaw("id not in {$db2->buildSql()}")->delete();
SystemFile::mk()->where($map)->where(['status' => 1])->delete();
$this->success('清理重复文件成功!');
}
}

View File

@ -0,0 +1,157 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\model\SystemUser;
use think\admin\service\AdminService;
use think\admin\service\MenuService;
/**
* 后台界面入口
* @class Index
* @package app\admin\controller
*/
class Index extends Controller
{
/**
* 显示后台首页
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
/*! 根据运行模式刷新权限 */
AdminService::apply($this->app->isDebug());
/*! 读取当前用户权限菜单树 */
$this->menus = MenuService::getTree();
/*! 判断当前用户的登录状态 */
$this->login = AdminService::isLogin();
/*! 菜单为空且未登录跳转到登录页 */
if (empty($this->menus) && empty($this->login)) {
$this->redirect(sysuri('admin/login/index'));
} else {
$this->title = '系统管理后台';
$this->super = AdminService::isSuper();
$this->theme = AdminService::getUserTheme();
$this->fetch();
}
}
/**
* 后台主题切换
* @login true
* @return void
* @throws \think\admin\Exception
*/
public function theme()
{
if ($this->request->isGet()) {
$this->theme = AdminService::getUserTheme();
$this->themes = Config::themes;
$this->fetch();
} else {
$data = $this->_vali(['site_theme.require' => '主题名称不能为空!']);
if (AdminService::setUserTheme($data['site_theme'])) {
$this->success('主题配置保存成功!');
} else {
$this->error('主题配置保存失败!');
}
}
}
/**
* 修改用户资料
* @login true
* @param mixed $id 用户ID
*/
public function info($id = 0)
{
$this->_applyFormToken();
if (AdminService::getUserId() === intval($id)) {
SystemUser::mForm('user/form', 'id', [], ['id' => $id]);
} else {
$this->error('只能修改自己的资料!');
}
}
/**
* 资料修改表单处理
* @param array $data
*/
protected function _info_form_filter(array &$data)
{
if ($this->request->isPost()) {
unset($data['username'], $data['authorize']);
}
}
/**
* 资料修改结果处理
* @param bool $status
*/
protected function _info_form_result(bool $status)
{
if ($status) {
$this->success('用户资料修改成功!', 'javascript:location.reload()');
}
}
/**
* 修改当前用户密码
* @login true
* @param mixed $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function pass($id = 0)
{
$this->_applyFormToken();
if (AdminService::getUserId() !== intval($id)) {
$this->error('禁止修改他人密码!');
}
if ($this->app->request->isGet()) {
$this->verify = true;
SystemUser::mForm('user/pass', 'id', [], ['id' => $id]);
} else {
$data = $this->_vali([
'password.require' => '登录密码不能为空!',
'repassword.require' => '重复密码不能为空!',
'oldpassword.require' => '旧的密码不能为空!',
'password.confirm:repassword' => '两次输入的密码不一致!',
]);
$user = SystemUser::mk()->find($id);
if (empty($user)) $this->error('用户不存在!');
if (md5($data['oldpassword']) !== $user['password']) {
$this->error('旧密码验证失败,请重新输入!');
}
if ($user->save(['password' => md5($data['password'])])) {
sysoplog('系统用户管理', "修改用户[{$user['id']}]密码成功");
// 修改密码同步事件处理
$this->app->event->trigger('PluginAdminChangePassword', [
'uuid' => intval($user['id']), 'pass' => $data['password']
]);
$this->success('密码修改成功,下次请使用新密码登录!', '');
} else {
$this->error('密码修改失败,请稍候再试!');
}
}
}
}

View File

@ -0,0 +1,137 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\extend\CodeExtend;
use think\admin\model\SystemUser;
use think\admin\service\AdminService;
use think\admin\service\CaptchaService;
use think\admin\service\RuntimeService;
use think\admin\service\SystemService;
/**
* 用户登录管理
* @class Login
* @package app\admin\controller
*/
class Login extends Controller
{
/**
* 后台登录入口
* @return void
* @throws \think\admin\Exception
*/
public function index()
{
if ($this->app->request->isGet()) {
if (AdminService::isLogin()) {
$this->redirect(sysuri('admin/index/index'));
} else {
// 加载登录模板
$this->title = '系统登录';
// 登录验证令牌
$this->captchaType = 'LoginCaptcha';
$this->captchaToken = CodeExtend::uuid();
// 当前运行模式
$this->runtimeMode = RuntimeService::check();
// 后台背景处理
$images = str2arr(sysconf('login_image|raw') ?: '', '|');
if (empty($images)) $images = [
SystemService::uri('/static/theme/img/login/bg1.jpg'),
SystemService::uri('/static/theme/img/login/bg2.jpg'),
];
$this->loginStyle = sprintf('style="background-image:url(%s)" data-bg-transition="%s"', $images[0], join(',', $images));
// 更新后台主域名,用于部分无法获取域名的场景调用
if ($this->request->domain() !== sysconf('base.site_host|raw')) {
sysconf('base.site_host', $this->request->domain());
}
$this->fetch();
}
} else {
$data = $this->_vali([
'username.require' => '登录账号不能为空!',
'username.min:4' => '账号不能少于4位字符!',
'password.require' => '登录密码不能为空!',
'password.min:4' => '密码不能少于4位字符!',
'verify.require' => '图形验证码不能为空!',
'uniqid.require' => '图形验证标识不能为空!',
]);
if (!CaptchaService::instance()->check($data['verify'], $data['uniqid'])) {
$this->error('图形验证码验证失败,请重新输入!');
}
/*! 用户信息验证 */
$map = ['username' => $data['username'], 'is_deleted' => 0];
$user = SystemUser::mk()->where($map)->findOrEmpty();
if ($user->isEmpty()) {
$this->app->session->set('LoginInputSessionError', true);
$this->error('登录账号或密码错误,请重新输入!');
}
if (empty($user['status'])) {
$this->app->session->set('LoginInputSessionError', true);
$this->error('账号已经被禁用,请联系管理员!');
}
if (md5("{$user['password']}{$data['uniqid']}") !== $data['password']) {
$this->app->session->set('LoginInputSessionError', true);
$this->error('登录账号或密码错误,请重新输入!');
}
$user->hidden(['sort', 'status', 'password', 'is_deleted']);
$this->app->session->set('user', $user->toArray());
$this->app->session->delete('LoginInputSessionError');
// 更新登录次数
$user->where(['id' => $user->getAttr('id')])->inc('login_num')->update([
'login_at' => date('Y-m-d H:i:s'), 'login_ip' => $this->app->request->ip(),
]);
// 刷新用户权限
AdminService::apply(true);
sysoplog('系统用户登录', '登录系统后台成功');
$this->success('登录成功', sysuri('admin/index/index'));
}
}
/**
* 生成验证码
* @return void
*/
public function captcha()
{
$input = $this->_vali([
'type.require' => '类型不能为空!',
'token.require' => '标识不能为空!',
]);
$image = CaptchaService::instance()->initialize();
$captcha = ['image' => $image->getData(), 'uniqid' => $image->getUniqid()];
// 未发生异常时,直接返回验证码内容
if (!$this->app->session->get('LoginInputSessionError')) {
$captcha['code'] = $image->getCode();
}
$this->success('生成验证码成功', $captcha);
}
/**
* 退出登录
* @return void
*/
public function out()
{
$this->app->session->destroy();
$this->success('退出登录成功!', sysuri('admin/login/index'));
}
}

View File

@ -0,0 +1,147 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\extend\DataExtend;
use think\admin\model\SystemMenu;
use think\admin\service\AdminService;
use think\admin\service\MenuService;
use think\admin\service\NodeService;
/**
* 系统菜单管理
* @class Menu
* @package app\admin\controller
*/
class Menu extends Controller
{
/**
* 系统菜单管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->title = '系统菜单管理';
$this->type = $this->get['type'] ?? 'index';
SystemMenu::mQuery()->layTable();
}
/**
* 列表数据处理
* @param array $data
*/
protected function _index_page_filter(array &$data)
{
$data = DataExtend::arr2tree($data);
// 回收站过滤有效菜单
if ($this->type === 'recycle') foreach ($data as $k1 => &$p1) {
if (!empty($p1['sub'])) foreach ($p1['sub'] as $k2 => &$p2) {
if (!empty($p2['sub'])) foreach ($p2['sub'] as $k3 => $p3) {
if ($p3['status'] > 0) unset($p2['sub'][$k3]);
}
if (empty($p2['sub']) && ($p2['url'] === '#' or $p2['status'] > 0)) unset($p1['sub'][$k2]);
}
if (empty($p1['sub']) && ($p1['url'] === '#' or $p1['status'] > 0)) unset($data[$k1]);
}
// 菜单数据树数据变平化
$data = DataExtend::arr2table($data);
foreach ($data as &$vo) {
if ($vo['url'] !== '#' && !preg_match('/^(https?:)?(\/\/|\\\\)/i', $vo['url'])) {
$vo['url'] = trim(url($vo['url']) . ($vo['params'] ? "?{$vo['params']}" : ''), '\\/');
}
}
}
/**
* 添加系统菜单
* @auth true
*/
public function add()
{
$this->_applyFormToken();
SystemMenu::mForm('form');
}
/**
* 编辑系统菜单
* @auth true
*/
public function edit()
{
$this->_applyFormToken();
SystemMenu::mForm('form');
}
/**
* 表单数据处理
* @param array $vo
*/
protected function _form_filter(array &$vo)
{
if ($this->request->isGet()) {
$debug = $this->app->isDebug();
/* 清理权限节点 */
$debug && AdminService::clear();
/* 读取系统功能节点 */
$this->auths = [];
$this->nodes = MenuService::getList($debug);
foreach (NodeService::getMethods($debug) as $node => $item) {
if ($item['isauth'] && substr_count($node, '/') >= 2) {
$this->auths[] = ['node' => $node, 'title' => $item['title']];
}
}
/* 选择自己上级菜单 */
$vo['pid'] = $vo['pid'] ?? input('pid', '0');
/* 列出可选上级菜单 */
$menus = SystemMenu::mk()->order('sort desc,id asc')->column('id,pid,icon,url,node,title,params', 'id');
$this->menus = DataExtend::arr2table(array_merge($menus, [['id' => '0', 'pid' => '-1', 'url' => '#', 'title' => '顶部菜单']]));
if (isset($vo['id'])) foreach ($this->menus as $menu) if ($menu['id'] === $vo['id']) $vo = $menu;
foreach ($this->menus as $key => $menu) if ($menu['spt'] >= 3 || $menu['url'] !== '#') unset($this->menus[$key]);
if (isset($vo['spt']) && isset($vo['spc']) && in_array($vo['spt'], [1, 2]) && $vo['spc'] > 0) {
foreach ($this->menus as $key => $menu) if ($vo['spt'] <= $menu['spt']) unset($this->menus[$key]);
}
}
}
/**
* 修改菜单状态
* @auth true
*/
public function state()
{
SystemMenu::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除系统菜单
* @auth true
*/
public function remove()
{
SystemMenu::mDelete();
}
}

View File

@ -0,0 +1,95 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use Ip2Region;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemOplog;
use think\exception\HttpResponseException;
/**
* 系统日志管理
* @class Oplog
* @package app\admin\controller
*/
class Oplog extends Controller
{
/**
* 系统日志管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemOplog::mQuery()->layTable(function () {
$this->title = '系统日志管理';
$columns = SystemOplog::mk()->column('action,username', 'id');
$this->users = array_unique(array_column($columns, 'username'));
$this->actions = array_unique(array_column($columns, 'action'));
}, static function (QueryHelper $query) {
$query->dateBetween('create_at')->equal('username,action')->like('content,geoip,node');
});
}
/**
* 列表数据处理
* @param array $data
* @throws \Exception
*/
protected function _index_page_filter(array &$data)
{
$region = new Ip2Region();
foreach ($data as &$vo) try {
$vo['geoisp'] = $region->simple($vo['geoip']);
} catch (\Exception $exception) {
$vo['geoip'] = $exception->getMessage();
}
}
/**
* 清理系统日志
* @auth true
*/
public function clear()
{
try {
SystemOplog::mQuery()->empty();
sysoplog('系统运维管理', '成功清理所有日志');
$this->success('日志清理成功!');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error(lang("日志清理失败,%s", [$exception->getMessage()]));
}
}
/**
* 删除系统日志
* @auth true
*/
public function remove()
{
SystemOplog::mDelete();
}
}

View File

@ -0,0 +1,117 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemQueue;
use think\admin\service\AdminService;
use think\admin\service\ProcessService;
use think\admin\service\QueueService;
use think\exception\HttpResponseException;
/**
* 系统任务管理
* @class Queue
* @package app\admin\controller
*/
class Queue extends Controller
{
/**
* 系统任务管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemQueue::mQuery()->layTable(function () {
$this->title = '系统任务管理';
$this->iswin = ProcessService::iswin();
if ($this->super = AdminService::isSuper()) {
$this->command = ProcessService::think('xadmin:queue start');
if (!$this->iswin && !empty($_SERVER['USER'])) {
$this->command = "sudo -u {$_SERVER['USER']} {$this->command}";
}
}
}, static function (QueryHelper $query) {
$query->equal('status')->like('code|title#title,command');
$query->timeBetween('enter_time,exec_time')->dateBetween('create_at');
});
}
/**
* 分页数据回调处理
* @param array $data
* @param array $result
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected function _index_page_filter(array $data, array &$result)
{
$result['extra'] = ['dos' => 0, 'pre' => 0, 'oks' => 0, 'ers' => 0];
SystemQueue::mk()->field('status,count(1) count')->group('status')->select()->map(static function ($item) use (&$result) {
if (intval($item['status']) === 1) $result['extra']['pre'] = $item['count'];
if (intval($item['status']) === 2) $result['extra']['dos'] = $item['count'];
if (intval($item['status']) === 3) $result['extra']['oks'] = $item['count'];
if (intval($item['status']) === 4) $result['extra']['ers'] = $item['count'];
});
}
/**
* 重启系统任务
* @auth true
*/
public function redo()
{
try {
$data = $this->_vali(['code.require' => '任务编号不能为空!']);
$queue = QueueService::instance()->initialize($data['code'])->reset();
$queue->progress(1, '>>> 任务重置成功 <<<', '0.00');
$this->success('任务重置成功!', $queue->code);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 清理运行数据
* @auth true
*/
public function clean()
{
$this->_queue('定时清理系统运行数据', "xadmin:queue clean", 0, [], 0, 3600);
}
/**
* 删除系统任务
* @auth true
*/
public function remove()
{
SystemQueue::mDelete();
}
}

View File

@ -0,0 +1,182 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemAuth;
use think\admin\model\SystemBase;
use think\admin\model\SystemUser;
use think\admin\service\AdminService;
/**
* 系统用户管理
* @class User
* @package app\admin\controller
*/
class User extends Controller
{
/**
* 系统用户管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->type = $this->get['type'] ?? 'index';
SystemUser::mQuery()->layTable(function () {
$this->title = '系统用户管理';
$this->bases = SystemBase::items('身份权限');
}, function (QueryHelper $query) {
// 加载对应数据列表
$query->where(['is_deleted' => 0, 'status' => intval($this->type === 'index')]);
// 关联用户身份资料
/** @var \think\model\Relation|\think\db\Query $query */
$query->with(['userinfo' => static function ($query) {
$query->field('code,name,content');
}]);
// 数据列表搜索过滤
$query->equal('status,usertype')->dateBetween('login_at,create_at');
$query->like('username|nickname#username,contact_phone#phone,contact_mail#mail');
});
}
/**
* 添加系统用户
* @auth true
*/
public function add()
{
SystemUser::mForm('form');
}
/**
* 编辑系统用户
* @auth true
*/
public function edit()
{
SystemUser::mForm('form');
}
/**
* 修改用户密码
* @auth true
*/
public function pass()
{
$this->_applyFormToken();
if ($this->request->isGet()) {
$this->verify = false;
SystemUser::mForm('pass');
} else {
$data = $this->_vali([
'id.require' => '用户ID不能为空',
'password.require' => '登录密码不能为空!',
'repassword.require' => '重复密码不能为空!',
'repassword.confirm:password' => '两次输入的密码不一致!',
]);
$user = SystemUser::mk()->findOrEmpty($data['id']);
if ($user->isExists() && $user->save(['password' => md5($data['password'])])) {
// 修改密码同步事件处理
$this->app->event->trigger('PluginAdminChangePassword', [
'uuid' => $data['id'], 'pass' => $data['password']
]);
sysoplog('系统用户管理', "修改用户[{$data['id']}]密码成功");
$this->success('密码修改成功,请使用新密码登录!', '');
} else {
$this->error('密码修改失败,请稍候再试!');
}
}
}
/**
* 表单数据处理
* @param array $data
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected function _form_filter(array &$data)
{
if ($this->request->isPost()) {
// 检查资料是否完整
empty($data['username']) && $this->error('登录账号不能为空!');
if ($data['username'] !== AdminService::getSuperName()) {
empty($data['authorize']) && $this->error('未配置权限!');
}
// 处理上传的权限格式
$data['authorize'] = arr2str($data['authorize'] ?? []);
if (empty($data['id'])) {
// 检查账号是否重复
$map = ['username' => $data['username'], 'is_deleted' => 0];
if (SystemUser::mk()->where($map)->count() > 0) {
$this->error("账号已经存在,请使用其它账号!");
}
// 新添加的用户密码与账号相同
$data['password'] = md5($data['username']);
} else {
unset($data['username']);
}
} else {
// 权限绑定处理
$data['authorize'] = str2arr($data['authorize'] ?? '');
$this->auths = SystemAuth::items();
$this->bases = SystemBase::items('身份权限');
$this->super = AdminService::getSuperName();
}
}
/**
* 修改用户状态
* @auth true
*/
public function state()
{
$this->_checkInput();
SystemUser::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除系统用户
* @auth true
*/
public function remove()
{
$this->_checkInput();
SystemUser::mDelete();
}
/**
* 检查输入变量
*/
private function _checkInput()
{
if (in_array('10000', str2arr(input('id', '')))) {
$this->error('系统超级账号禁止删除!');
}
}
}

View File

@ -0,0 +1,91 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use think\admin\Controller;
use think\admin\service\AdminService;
use think\Response;
/**
* 扩展插件管理
* @class Plugs
* @package app\admin\controller\api
*/
class Plugs extends Controller
{
/**
* 图标选择器
* @login true
*/
public function icon()
{
$this->title = '图标选择器';
// 读取 layui 字体图标
if (empty($this->layuiIcons = $this->app->cache->get('LayuiIcons', []))) {
$style = file_get_contents(syspath('public/static/plugs/layui/css/layui.css'));
if (preg_match_all('#\.(layui-icon-[\w-]+):#', $style, $matches)) {
if (count($this->layuiIcons = $matches[1]) > 0) {
$this->app->cache->set('LayuiIcons', $this->layuiIcons, 60);
}
}
}
// 读取自定义字体图标
if (empty($this->thinkIcons = $this->app->cache->get('ThinkAdminSelfIcons', []))) {
$style = file_get_contents(syspath('public/static/theme/css/iconfont.css'));
if (preg_match_all('#\.(iconfont-[\w-]+):#', $style, $matches)) {
if (count($this->thinkIcons = $matches[1]) > 0) {
$this->app->cache->set('ThinkAdminSelfIcons', $this->thinkIcons, 60);
}
}
}
$this->field = $this->app->request->get('field', 'icon');
$this->fetch(realpath(__DIR__ . '/../../view/api/icon.html'));
}
/**
* 前端脚本变量
* @return \think\Response
* @throws \think\admin\Exception
*/
public function script(): Response
{
$token = $this->request->get('uptoken', '');
$domain = boolval(AdminService::withUploadUnid($token));
return response(join("\r\n", [
sprintf("window.taDebug = %s;", $this->app->isDebug() ? 'true' : 'false'),
sprintf("window.taAdmin = '%s';", sysuri('admin/index/index', [], false, $domain)),
sprintf("window.taEditor = '%s';", sysconf('base.editor|raw') ?: 'ckeditor4'),
]))->contentType('application/javascript');
}
/**
* 优化数据库
* @login true
*/
public function optimize()
{
if (AdminService::isSuper()) {
sysoplog('系统运维管理', '创建数据库优化任务');
$this->_queue('优化数据库所有数据表', 'xadmin:database optimize');
} else {
$this->error('请使用超管账号操作!');
}
}
}

View File

@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use Psr\Log\NullLogger;
use think\admin\Controller;
use think\admin\model\SystemQueue;
use think\admin\service\AdminService;
use think\exception\HttpResponseException;
/**
* 任务监听服务管理
* @class Queue
* @package app\admin\controller\api
*/
class Queue extends Controller
{
/**
* 停止监听服务
* @login true
*/
public function stop()
{
if (AdminService::isSuper()) try {
$message = $this->app->console->call('xadmin:queue', ['stop'])->fetch();
if (stripos($message, 'sent end signal to process')) {
sysoplog('系统运维管理', '尝试停止任务监听服务');
$this->success('停止任务监听服务成功!');
} elseif (stripos($message, 'processes to stop')) {
$this->success('没有找到需要停止的服务!');
} else {
$this->error(nl2br($message));
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 启动监听服务
* @login true
*/
public function start()
{
if (AdminService::isSuper()) try {
$message = $this->app->console->call('xadmin:queue', ['start'])->fetch();
if (stripos($message, 'daemons started successfully for pid')) {
sysoplog('系统运维管理', '尝试启动任务监听服务');
$this->success('任务监听服务启动成功!');
} elseif (stripos($message, 'daemons already exist for pid')) {
$this->success('任务监听服务已经启动!');
} else {
$this->error(nl2br($message));
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 检查监听服务
* @login true
*/
public function status()
{
if (AdminService::isSuper()) try {
$message = $this->app->console->call('xadmin:queue', ['status'])->fetch();
if (preg_match('/process.*?\d+.*?running/', $message)) {
echo "<span class='color-green pointer' data-tips-text='{$message}'>{$this->app->lang->get('已启动')}</span>";
} else {
echo "<span class='color-red pointer' data-tips-text='{$message}'>{$this->app->lang->get('未启动')}</span>";
}
} catch (\Error|\Exception $exception) {
echo "<span class='color-red pointer' data-tips-text='{$exception->getMessage()}'>{$this->app->lang->get('异 常')}</span>";
} else {
$message = lang('只有超级管理员才能操作!');
echo "<span class='color-red pointer' data-tips-text='{$message}'>{$this->app->lang->get('无权限')}</span>";
}
}
/**
* 查询任务进度
* @login true
*/
public function progress()
{
$input = $this->_vali(['code.require' => '任务编号不能为空!']);
$this->app->db->setLog(new NullLogger()); /* 关闭数据库请求日志 */
$message = SystemQueue::mk()->where($input)->value('message', '');
$this->success('获取任务进度成功d', json_decode($message, true));
}
}

View File

@ -0,0 +1,138 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use think\admin\Controller;
use think\admin\model\SystemConfig;
use think\admin\service\AdminService;
use think\admin\service\RuntimeService;
use think\exception\HttpResponseException;
/**
* 系统运行管理
* @class System
* @package app\admin\controller\api
*/
class System extends Controller
{
/**
* 网站压缩发布
* @login true
*/
public function push()
{
if (AdminService::isSuper()) try {
RuntimeService::push() && sysoplog('系统运维管理', '刷新发布运行缓存');
$this->success('网站缓存加速成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 清理运行缓存
* @login true
*/
public function clear()
{
if (AdminService::isSuper()) try {
RuntimeService::clear() && sysoplog('系统运维管理', '清理网站日志缓存');
$this->success('清空日志缓存成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 当前运行模式
* @login true
*/
public function debug()
{
if (AdminService::isSuper()) if (input('state')) {
RuntimeService::set('product');
sysoplog('系统运维管理', '开发模式切换为生产模式');
$this->success('已切换为生产模式!', 'javascript:location.reload()');
} else {
RuntimeService::set('debug');
sysoplog('系统运维管理', '生产模式切换为开发模式');
$this->success('已切换为开发模式!', 'javascript:location.reload()');
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 修改富文本编辑器
* @return void
* @throws \think\admin\Exception
*/
public function editor()
{
if (AdminService::isSuper()) {
$editor = input('editor', 'auto');
sysconf('base.editor', $editor);
sysoplog('系统运维管理', "切换编辑器为{$editor}");
$this->success('已切换后台编辑器!', 'javascript:location.reload()');
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 清理系统配置
* @login true
*/
public function config()
{
if (AdminService::isSuper()) try {
[$tmpdata, $newdata] = [[], []];
foreach (SystemConfig::mk()->order('type,name asc')->cursor() as $item) {
$tmpdata[$item['type']][$item['name']] = $item['value'];
}
foreach ($tmpdata as $type => $items) foreach ($items as $name => $value) {
$newdata[] = ['type' => $type, 'name' => $name, 'value' => $value];
}
$this->app->db->transaction(static function () use ($newdata) {
SystemConfig::mQuery()->empty()->insertAll($newdata);
});
$this->app->cache->delete('SystemConfig');
sysoplog('系统运维管理', '清理系统配置参数');
$this->success('清理系统配置成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
}

View File

@ -0,0 +1,337 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemFile;
use think\admin\service\AdminService;
use think\admin\Storage;
use think\admin\storage\AliossStorage;
use think\admin\storage\AlistStorage;
use think\admin\storage\LocalStorage;
use think\admin\storage\QiniuStorage;
use think\admin\storage\TxcosStorage;
use think\admin\storage\UpyunStorage;
use think\exception\HttpResponseException;
use think\file\UploadedFile;
use think\Response;
/**
* 文件上传接口
* @class Upload
* @package app\admin\controller\api
*/
class Upload extends Controller
{
/**
* 文件上传脚本
* @return Response
* @throws \think\admin\Exception
*/
public function index(): Response
{
$data = ['exts' => []];
[$uuid, $unid, $exts] = $this->initUnid(false);
$allows = str2arr(sysconf('storage.allow_exts|raw'));
if (empty($uuid) && $unid > 0) $allows = array_intersect($exts, $allows);
foreach ($allows as $ext) $data['exts'][$ext] = Storage::mime($ext);
$template = realpath(__DIR__ . '/../../view/api/upload.js');
$data['exts'] = json_encode($data['exts'], JSON_UNESCAPED_UNICODE);
$data['nameType'] = sysconf('storage.name_type|raw') ?: 'xmd5';
return view($template, $data)->contentType('application/x-javascript');
}
/**
* 文件选择器
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function image()
{
[$uuid, $unid] = $this->initUnid();
SystemFile::mQuery()->layTable(function () {
$this->title = '文件选择器';
}, function (QueryHelper $query) use ($unid, $uuid) {
if ($unid && $uuid) $query->where(function ($query) use ($uuid, $unid) {
/** @var \think\db\Query $query */
$query->whereOr([['uuid', '=', $uuid], ['unid', '=', $unid]]);
}); else {
$query->where($unid ? ['unid' => $unid] : ['uuid' => $uuid]);
}
$query->where(['status' => 2, 'issafe' => 0])->in('xext#type');
$query->like('name,hash')->dateBetween('create_at')->order('id desc');
});
}
/**
* 文件上传检查
*/
public function state()
{
try {
[$uuid, $unid] = $this->initUnid();
[$name, $safe] = [input('name'), $this->getSafe()];
$data = ['uptype' => $this->getType(), 'safe' => intval($safe), 'key' => input('key')];
$file = SystemFile::mk()->data($this->_vali([
'xkey.value' => $data['key'],
'type.value' => $this->getType(),
'uuid.value' => $uuid,
'unid.value' => $unid,
'name.require' => '名称不能为空!',
'hash.require' => '哈希不能为空!',
'xext.require' => '后缀不能为空!',
'size.require' => '大小不能为空!',
'mime.default' => '',
'status.value' => 1,
]));
$mime = $file->getAttr('mime');
if (empty($mime)) $file->setAttr('mime', Storage::mime($file->getAttr('xext')));
$info = Storage::instance($data['uptype'])->info($data['key'], $safe, $name);
if (isset($info['url']) && isset($info['key'])) {
$file->save(['xurl' => $info['url'], 'isfast' => 1, 'issafe' => $data['safe']]);
$extr = ['id' => $file->id ?? 0, 'url' => $info['url'], 'key' => $info['key']];
$this->success('文件已经上传', array_merge($data, $extr), 200);
} elseif ('local' === $data['uptype']) {
$local = LocalStorage::instance();
$data['url'] = $local->url($data['key'], $safe, $name);
$data['server'] = $local->upload();
} elseif ('qiniu' === $data['uptype']) {
$qiniu = QiniuStorage::instance();
$data['url'] = $qiniu->url($data['key'], $safe, $name);
$data['token'] = $qiniu->token($data['key'], 3600, $name);
$data['server'] = $qiniu->upload();
} elseif ('alioss' === $data['uptype']) {
$alioss = AliossStorage::instance();
$token = $alioss->token($data['key'], 3600, $name);
$data['url'] = $token['siteurl'];
$data['policy'] = $token['policy'];
$data['signature'] = $token['signature'];
$data['OSSAccessKeyId'] = $token['keyid'];
$data['server'] = $alioss->upload();
} elseif ('txcos' === $data['uptype']) {
$txcos = TxcosStorage::instance();
$token = $txcos->token($data['key'], 3600, $name);
$data['url'] = $token['siteurl'];
$data['q-ak'] = $token['q-ak'];
$data['policy'] = $token['policy'];
$data['q-key-time'] = $token['q-key-time'];
$data['q-signature'] = $token['q-signature'];
$data['q-sign-algorithm'] = $token['q-sign-algorithm'];
$data['server'] = $txcos->upload();
} elseif ('upyun' === $data['uptype']) {
$upyun = UpyunStorage::instance();
$token = $upyun->token($data['key'], 3600, $name, input('hash', ''));
$data['url'] = $token['siteurl'];
$data['policy'] = $token['policy'];
$data['server'] = $upyun->upload();
$data['authorization'] = $token['authorization'];
} elseif ('alist' === $data['uptype']) {
$alist = AlistStorage::instance();
$data['url'] = $alist->url($data['key']);
$data['server'] = $alist->upload();
$data['filepath'] = $alist->real($data['key']);
$data['authorization'] = $alist->token();
} else {
$this->error('未知的存储引擎!');
}
$file->save(['xurl' => $data['url'], 'isfast' => 0, 'issafe' => $data['safe']]);
$this->success('获取上传授权参数', array_merge($data, ['id' => $file->id ?? 0]), 404);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 更新文件状态
* @return void
*/
public function done()
{
[$uuid, $unid] = $this->initUnid();
$data = $this->_vali([
'id.require' => '编号不能为空!',
'hash.require' => '哈希不能为空!',
'uuid.value' => $uuid,
'unid.value' => $unid,
]);
$file = SystemFile::mk()->where($data)->findOrEmpty();
if ($file->isEmpty()) $this->error('文件不存在!');
if ($file->save(['status' => 2])) {
$this->success('更新成功!');
} else {
$this->error('更新失败!');
}
}
/**
* 文件上传入口
* @throws \think\admin\Exception
*/
public function file()
{
[$uuid, $unid, $unexts] = $this->initUnid();
// 开始处理文件上传
$file = $this->getFile();
$extension = strtolower($file->getOriginalExtension());
$saveFileName = input('key') ?: Storage::name($file->getPathname(), $extension, '', 'md5_file');
// 检查文件名称是否合法
if (strpos($saveFileName, '..') !== false) {
$this->error('文件路径不能出现跳级操作!');
}
// 检查文件后缀是否被恶意修改
if (strtolower(pathinfo(parse_url($saveFileName, PHP_URL_PATH), PATHINFO_EXTENSION)) !== $extension) {
$this->error('文件后缀异常,请重新上传文件!');
}
// 屏蔽禁止上传指定后缀的文件
if (!in_array($extension, str2arr(sysconf('storage.allow_exts|raw')))) {
$this->error('文件类型受限,请在后台配置规则!');
}
// 前端用户上传后缀检查处理
if (empty($uuid) && $unid > 0 && !in_array($extension, $unexts)) {
$this->error('文件类型受限,请上传允许的文件类型!');
}
if (in_array($extension, ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'])) {
$this->error('文件安全保护,禁止上传可执行文件!');
}
try {
$safeMode = $this->getSafe();
if (($type = $this->getType()) === 'local') {
$local = LocalStorage::instance();
$distName = $local->path($saveFileName, $safeMode);
if (PHP_SAPI === 'cli') {
is_dir(dirname($distName)) || mkdir(dirname($distName), 0777, true);
rename($file->getPathname(), $distName);
} else {
$file->move(dirname($distName), basename($distName));
}
$info = $local->info($saveFileName, $safeMode, $file->getOriginalName());
if (in_array($extension, ['jpg', 'gif', 'png', 'bmp', 'jpeg', 'wbmp'])) {
if ($this->imgNotSafe($distName) && $local->del($saveFileName)) {
$this->error('图片未通过安全检查!');
}
[$width, $height] = getimagesize($distName);
if (($width < 1 || $height < 1) && $local->del($saveFileName)) {
$this->error('读取图片的尺寸失败!');
}
}
} else {
$bina = file_get_contents($file->getPathname());
$info = Storage::instance($type)->set($saveFileName, $bina, $safeMode, $file->getOriginalName());
}
if (isset($info['url'])) {
$this->success('文件上传成功!', ['url' => $safeMode ? $saveFileName : $info['url']]);
} else {
$this->error('文件处理失败,请稍候再试!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 获取上传类型
* @return boolean
*/
private function getSafe(): bool
{
return boolval(input('safe', '0'));
}
/**
* 获取上传方式
* @return string
* @throws \think\admin\Exception
*/
private function getType(): string
{
$type = strtolower(input('uptype', ''));
if (in_array($type, array_keys(Storage::types()))) {
return $type;
} else {
return strtolower(sysconf('storage.type|raw'));
}
}
/**
* 获取文件对象
* @return UploadedFile|void
*/
private function getFile(): UploadedFile
{
try {
$file = $this->request->file('file');
if ($file instanceof UploadedFile) {
return $file;
} else {
$this->error('读取临时文件失败!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error(lang($exception->getMessage()));
}
}
/**
* 初始化用户状态
* @param boolean $check
* @return array
*/
private function initUnid(bool $check = true): array
{
$uuid = AdminService::getUserId();
[$unid, $exts] = AdminService::withUploadUnid();
if ($check && empty($uuid) && empty($unid)) {
$this->error('未登录,禁止使用文件上传!');
} else {
return [$uuid, $unid, $exts];
}
}
/**
* 检查图片是否安全
* @param string $filename
* @return boolean
*/
private function imgNotSafe(string $filename): bool
{
$source = fopen($filename, 'rb');
if (($size = filesize($filename)) > 512) {
$hexs = bin2hex(fread($source, 512));
fseek($source, $size - 512);
$hexs .= bin2hex(fread($source, 512));
} else {
$hexs = bin2hex(fread($source, $size));
}
if (is_resource($source)) fclose($source);
$bins = hex2bin($hexs);
/* 匹配十六进制中的 <% ( ) %> 或 <? ( ) ?> 或 <script | /script> */
foreach (['<?php ', '<% ', '<script '] as $key) if (stripos($bins, $key) !== false) return true;
$result = preg_match("/(3c25.*?28.*?29.*?253e)|(3c3f.*?28.*?29.*?3f3e)|(3C534352495054)|(2F5343524950543E)|(3C736372697074)|(2F7363726970743E)/is", $hexs);
return $result === false || $result > 0;
}
}

View File

@ -0,0 +1,200 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
$extra = [];
$extra['开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。'] = 'Developers may use it during functional debugging. When there are system exceptions, detailed error messages will be displayed, and operation logs and database SQL statement information will also be recorded.';
$extra['项目正式部署上线后使用,系统异常时统一显示 “%s”只记录重要的异常日志信息强烈推荐上线后使用此模式。'] = 'After the project is officially deployed and launched, it will be used. When there are system exceptions, " %s " will be displayed uniformly, and only important exception log information will be recorded. It is strongly recommended to use this mode after launch.';
$extra['旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。'] = 'The old version of the editor is compatible with browsers, but the content editing experience is slightly insufficient.';
$extra['新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。'] = 'The new version of the editor only supports the new feature browser and has a good experience in content editing. It is recommended to use it.';
$extra['优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。'] = 'Priority should be given to using the new version of the editor. If the browser does not support the new version, it will automatically be downgraded to the old version of the editor.';
$extra['文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。'] = 'Uploading files to the `static/upload` directory of the local server does not support uploading large files, occupying server disk space, and consuming server bandwidth traffic during access.';
$extra['文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。'] = 'Files can be uploaded to the Alist storage server or Cloud storage space. According to the service configuration, large file upload can be supported without occupying the server space and server bandwidth traffic.';
$extra['文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = 'Files can be uploaded to Qiniu Cloud storage space. It supports large file upload, does not occupy server space and server bandwidth traffic, and supports CDN accelerated access. It is recommended when there is a large amount of access.';
$extra['文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Upyun Cloud's USS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
$extra['文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Aliyun Cloud's OSS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
$extra['文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Tencent Cloud's COS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
$extra['网站名称及网站图标,将显示在浏览器的标签上。'] = "The website name and icon will be displayed on the browser's label.";
$extra['管理程序名称,将显示在后台左上角标题。'] = 'The name of the management program will be displayed in the header in the upper left corner of the background.';
$extra['管理程序版本,将显示在后台左上角标题。'] = 'The management program version will be displayed in the top left corner of the background with a title.';
$extra['网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。'] = 'Website copyright information is displayed on the backend login page and linked to the information filing management system.';
$extra['网站备案号,可以在 %s 查询获取,将显示在登录页面下面。'] = 'The website registration number can be found at %s and will be displayed below the login page.';
$extra['公安备案号,可以在 %s 查询获取,将在登录页面下面显示。'] = 'The public security registration number can be obtained by searching at %s and will be displayed below the login page.';
$extra['点击可复制【服务启动指令】'] = "Click to copy the 'Service Start Command'";
$extra['待处理 %s 个任务,处理中 %s 个任务,已完成 %s 个任务,已失败 %s 个任务。'] = 'There are %s tasks to be processed, %s tasks in progress, %s tasks completed, and %s tasks failed.';
$extra['确定要切换到生产模式运行吗?'] = 'Are you sure you want to switch to Production mode?';
$extra['确定要切换到开发模式运行吗?'] = 'Are you sure you want to switch to Development mode?';
$extra["超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>"] = "The super administrator password has not been changed. Suggest <a data-modal='%s'>changing password</a>.";
$extra['等待处理'] = 'Pending';
$extra['正在处理'] = 'Processing';
$extra['处理完成'] = 'Completed';
$extra['处理失败'] = 'Failed';
$extra['条件搜索'] = 'Search';
$extra['批量删除'] = 'Batch Delete';
$extra['上传进度 %s'] = 'Upload progress %s';
$extra['文件上传出错!'] = 'File upload error.';
$extra['文件上传失败!'] = 'File upload failed.';
$extra['大小超出限制!'] = 'Size exceeds limit.';
$extra['文件秒传成功!'] = 'Successfully transmitted the file in seconds.';
$extra['上传接口异常!'] = 'Abnormal upload interface.';
$extra['文件上传成功!'] = 'File uploaded successfully.';
$extra['图片压缩失败!'] = 'Image compression failed.';
$extra['无效的文件上传对象!'] = 'Invalid file upload object.';
return array_merge($extra, [
// 系统操作
'基本资料' => 'Basic information',
'安全设置' => 'Security setting',
'缓存加速' => 'Cache acceleration',
'清理缓存' => 'Clean cache',
'配色方案' => 'Color scheme',
'立即登录' => 'Login',
'退出登录' => 'Logout',
'系统提示:' => 'System Notify: ',
'清空日志缓存成功!' => 'Successfully cleared the log cache.',
'获取任务进度成功!' => 'Successfully obtained task progress.',
'网站缓存加速成功!' => 'Website cache acceleration successful.',
'请使用超管账号操作!' => 'Please use a super managed account to operate.',
'停止任务监听服务成功!' => 'Successfully stopped task listening service.',
'任务监听服务启动成功!' => 'Task monitoring service started successfully.',
'任务监听服务已经启动!' => 'The task monitoring service has started.',
'没有找到需要停止的服务!' => 'No services found that need to be stopped.',
'已切换后台编辑器!' => 'Switched to background editor.',
// 其他搜索器提示
'请选择登录时间' => 'Please select the Login time',
'请选择创建时间' => 'Please select the creation time',
'请输入账号或名称' => 'Please enter an account or name',
'请输入权限名称' => 'Please enter the permission name',
'请输入数据编码' => 'Please enter the data code',
'请输入数据名称' => 'Please enter the data name',
'请输入文件名称' => 'Please enter the file name',
'请输入文件哈希' => 'Please enter the file hash',
'请输入操作节点' => 'Please enter the operate node',
'请输入操作内容' => 'Please enter the operate content',
'请输入访问地址' => 'Please enter the access Geoip',
// 系统配置
'运行模式' => 'Running Mode',
'生产模式' => 'Production mode',
'开发模式' => 'Development mode',
'以开发模式运行' => 'Running in Development mode',
'以生产模式运行' => 'Running in Production mode',
'清理无效配置' => 'Clean up Invalid Configurations',
'修改系统参数' => 'Modify System Parameters',
'清理系统配置成功!' => 'Successfully cleaned.',
'自适应模式' => 'Adaptive Mode',
'富编辑器' => 'RichText Editor',
'存储引擎' => 'Storage Engine',
'系统参数' => 'System Parameter',
'网站名称' => 'Site Name',
'管理程序名称' => 'Program Name',
'管理程序版本' => 'Program Version',
'公安备案号' => 'Public security registration number',
'网站备案号' => 'Website registration number',
'网站版权信息' => 'Website copyright information',
'系统信息' => 'System Information',
'应用插件' => 'Plugin Information',
'核心框架' => 'Core Framework',
'平台框架' => 'Platform Framework',
'操作系统' => 'Operating System',
'运行环境' => 'Runtime Environment',
'仅开发模式可见' => 'Visible only in Development mode',
'仅生产模式可见' => 'Visible only in Production mode',
'插件名称' => 'Plugin Name',
'应用名称' => 'App Name',
'插件包名' => 'Package Name',
'插件版本' => 'Plugin Version',
'授权协议' => 'License',
'文件默认存储方式' => 'Default storage method for file upload',
'当前系统配置参数' => 'Current system configuration parameters',
'仅超级管理员可配置' => 'Only super administrators can configure',
// 系统任务管理
'优化数据库' => 'Optimize Database',
'开启服务' => 'Start Service',
'关闭服务' => 'Shutdown Service',
'定时清理' => 'Regular cleaning',
'服务状态' => 'Service',
'任务统计' => 'Total',
'编号名称' => 'Name',
'任务指令' => 'Command',
'任务状态' => 'Status',
'计划时间' => 'scheduled time',
'任务名称' => 'Name',
'检查中' => 'Checking',
'任务计划' => 'Scheduled',
'重 置' => 'Reset',
'日 志' => 'Logs',
'异 常' => 'Abnormal',
'无权限' => 'Denied',
'已启动' => 'Started',
'未启动' => 'Stopped',
// 数据字典管理
'数据编码' => 'Code',
'数据名称' => 'Name',
'操作账号' => 'User',
'操作节点' => "Node",
'操作行为' => 'Action',
'操作内容' => "Content",
'访问地址' => 'Geo IP',
'网络服务商' => 'ISP.',
'日志清理成功!' => 'Logger Clear Complate.',
'成功清理所有日志' => 'Successfully cleared all logs.',
// 系统文件管理
'文件名称' => 'Name',
'文件哈希' => "HASH",
'文件大小' => "Size",
'文件后缀' => 'Exts',
'存储方式' => 'Storage Type',
'清理重复' => 'Clear Replace',
'上传方式' => 'Upload Type',
'查看文件' => 'View',
'文件链接' => 'Link',
'秒传' => 'Speedy',
'普通' => 'Normal',
// 系统菜单管理
'图 标' => "Icon",
'添加菜单' => 'Add',
'禁用菜单' => 'Forbid',
'激活菜单' => "Resume",
'系统菜单' => 'Menus',
'菜单名称' => 'Name',
'跳转链接' => 'Link',
'上级菜单' => 'Parent',
'菜单链接' => 'Link',
'链接参数' => 'Params',
'权限节点' => "Node",
'菜单图标' => 'Icon',
'选择图标' => 'Select Icon',
// 系统权限管理
"授 权" => 'Auth',
'添加权限' => 'Add',
'权限名称' => "Name",
'权限描述' => 'Description',
'请输入权限描述' => 'Please enter a permission description',
// 系统用户管理
'账号名称' => 'Username',
'添加用户' => 'Add User',
'最后登录' => "Last Login Time",
'头像' => "Head",
'登录账号' => 'Username',
'用户名称' => 'Nickname',
'登录次数' => 'Login Times',
'系统用户' => 'System User',
'密 码' => 'Password',
'系统用户管理' => 'Users',
]);

View File

@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
use think\admin\Library;
use think\admin\service\RuntimeService;
/*! 演示环境禁止操作路由绑定 */
if (RuntimeService::check('demo')) {
Library::$sapp->route->post('index/pass', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止修改用户密码!')]);
});
Library::$sapp->route->post('config/system', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止修改系统配置!')]);
});
Library::$sapp->route->post('config/storage', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止修改系统配置!')]);
});
Library::$sapp->route->post('menu', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止给菜单排序!')]);
});
Library::$sapp->route->post('menu/index', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止给菜单排序!')]);
});
Library::$sapp->route->post('menu/add', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止添加菜单!')]);
});
Library::$sapp->route->post('menu/edit', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止编辑菜单!')]);
});
Library::$sapp->route->post('menu/state', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止禁用菜单!')]);
});
Library::$sapp->route->post('menu/remove', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止删除菜单!')]);
});
Library::$sapp->route->post('user/pass', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止修改密码!')]);
});
}

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
<meta name="renderer" content="webkit">
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<link rel="stylesheet" href="__ROOT__/static/theme/css/iconfont.css?at={:date('md')}">
<link rel="stylesheet" href="__ROOT__/static/plugs/layui/css/layui.css?v={:date('ymd')}">
<style>
::-webkit-input-placeholder {
color: #aaa
}
::-webkit-scrollbar {
width: 3px;
height: 3px
}
::-webkit-scrollbar-track {
background: #ccc
}
::-webkit-scrollbar-thumb {
background-color: #666
}
::selection {
background-color: #ec494e;
color: #fff
}
::-moz-selection {
background-color: #ec494e;
color: #fff
}
:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset;
-webkit-text-fill-color: #333
}
ul li {
width: 20%;
height: 65px;
display: block;
float: left;
margin-right: -1px;
margin-left: -2px;
margin-bottom: -2px;
cursor: pointer;
font-size: 14px;
overflow: hidden;
text-align: center;
padding: 15px 0 10px 0;
border: 1px solid #e2e2e2;
background-color: #efefef;
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
transition: all .2s linear;
-o-transition: all .2s linear;
-moz-transition: all .2s linear;
-webkit-transition: all .2s linear
}
ul li:hover {
color: #fff;
background-color: #563d7c
}
ul li:hover i, ul li:hover div {
color: #fff;
}
ul li i {
color: #333;
display: inline-block;
font-size: 30px !important
}
ul li div {
color: #333;
height: 35px;
font-size: 13px;
line-height: 35px;
white-space: nowrap
}
</style>
</head>
<body>
<ul>
{foreach $layuiIcons??[] as $icon}
<li>
<i class="layui-icon {$icon}"></i>
<div class="icon-title">{$icon}</div>
</li>
{/foreach}
{foreach $thinkIcons??[] as $icon}
<li>
<i class="iconfont {$icon}"></i>
<div class="icon-title">{$icon}</div>
</li>
{/foreach}
</ul>
<script src="__ROOT__/static/plugs/jquery/jquery.min.js" type="text/javascript"></script>
<script>
$(function () {
$('li').on('click', function (className) {
if ((className = $(this).find('i').get(0).className)) {
top.$('[name="{$field}"]').val(className).trigger('change');
top.layer.close(top.layer.getFrameIndex(window.name));
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,327 @@
define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
allowMime = JSON.parse('{$exts|raw}');
function UploadAdapter(elem, done) {
return new (function (elem, done) {
let that = this;
/*! 初始化变量 */
this.option = {elem: $(elem), exts: [], mimes: []};
this.option.size = this.option.elem.data('size') || 0;
this.option.safe = this.option.elem.data('safe') ? 1 : 0;
this.option.hide = this.option.elem.data('hload') ? 1 : 0;
this.option.mult = this.option.elem.data('multiple') > 0;
this.option.path = (this.option.elem.data('path') || '').replace(/\W/g, '');
this.option.type = this.option.safe ? 'local' : this.option.elem.attr('data-uptype') || '';
this.option.quality = parseFloat(this.option.elem.data('quality') || '1.0');
this.option.maxWidth = parseInt(this.option.elem.data('max-width') || '0');
this.option.maxHeight = parseInt(this.option.elem.data('max-height') || '0');
this.option.cutWidth = parseInt(this.option.elem.data('cut-width') || '0');
this.option.cutHeight = parseInt(this.option.elem.data('cut-height') || '0');
/*! 查找表单元素, 如果没有找到将不会自动写值 */
if (this.option.elem.data('input')) {
this.option.input = $(this.option.elem.data('input'))
} else if (this.option.elem.data('field')) {
this.option.input = $('input[name="' + this.option.elem.data('field') + '"]:not([type=file])');
this.option.elem.data('input', this.option.input.size() > 0 ? this.option.input.get(0) : null);
}
/*! 文件选择筛选,使用 MIME 规则过滤文件列表 */
$((this.option.elem.data('type') || '').split(',')).map(function (i, e) {
if (allowMime[e]) that.option.exts.push(e), that.option.mimes.push(allowMime[e]);
});
/*! 初始化上传组件 */
this.adapter = new Adapter(this.option, layui.upload.render({
url: '{:url("admin/api.upload/file",[],false,true)}', auto: false, elem: elem, accept: 'file', multiple: this.option.mult, exts: this.option.exts.join('|'), acceptMime: this.option.mimes.join(','), choose: function (obj) {
obj.items = [], obj.files = obj.pushFile();
layui.each(obj.files, function (idx, file) {
obj.items.push(file);
file.path = that.option.path;
file.quality = that.option.quality;
file.maxWidth = that.option.maxWidth;
file.maxHeight = that.option.maxHeight;
file.cutWidth = that.option.cutWidth;
file.cutHeight = that.option.cutHeight;
});
that.adapter.event('upload.choose', obj.items);
that.adapter.upload(obj.items, done);
layui.each(obj.files, function (idx) {
delete obj.files[idx];
});
}
}));
})(elem, done)
}
// 创建对象
UploadAdapter.adapter = window.AdminUploadAdapter = Adapter;
// 上传文件
function Adapter(option, uploader) {
this.uploader = uploader, this.config = function (option) {
return (this.option = Object.assign({}, this.option || {}, option || {})), this;
}, this.init = function (option) {
this.uploader && this.uploader.config.elem.next().val('');
this.files = {}, this.loader = 0, this.count = {total: 0, error: 0, success: 0};
return this.config(option).config({safe: this.option.safe || 0, type: this.option.type || ''});
}, this.init(option);
}
// 文件推送
Adapter.prototype.upload = function (files, done) {
let that = this.init();
layui.each(files, function (index, file) {
that.count.total++, file.index = index, that.files[index] = file;
if (!that.option.hide && !file.notify) {
file.notify = new NotifyExtend(file);
}
if (that.option.size && file.size > that.option.size) {
that.event('upload.error', {file: file}, file, '{:lang("大小超出限制!")}');
}
});
layui.each(files, function (index, file) {
// 禁传异常状态文件
if (typeof file.xstate === 'number' && file.xstate === -1) return;
// 图片限宽限高压缩
let isGif = /^image\/gif/.test(file.type);
if (!isGif && /^image\//.test(file.type) && (file.maxWidth + file.maxHeight + file.cutWidth + file.cutHeight > 0 || file.quality !== 1)) {
require(['compressor'], function (Compressor) {
let options = {quality: file.quality, resize: 'cover'};
if (file.cutWidth) options.width = file.cutWidth;
if (file.cutHeight) options.height = file.cutHeight;
if (file.maxWidth) options.maxWidth = file.maxWidth;
if (file.maxHeight) options.maxHeight = file.maxHeight;
new Compressor(file, Object.assign(options, {
success(blob) {
blob.index = file.index, blob.notify = file.notify, blob.path = file.path, files[index] = blob;
that.hash(files[index]).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
}, error: function () {
that.event('upload.error', {file: file}, file, '{:lang("图片压缩失败!")}');
}
}));
});
} else {
that.hash(file).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
}
});
};
// 文件上传
Adapter.prototype.request = function (file, done) {
let that = this, data = {key: file.xkey, safe: that.option.safe, uptype: that.option.type};
data.size = file.size, data.name = file.name, data.hash = file.xmd5, data.mime = file.type, data.xext = file.xext;
jQuery.ajax("{:url('admin/api.upload/state',[],false,true)}", {
data: data, method: 'post', success: function (ret) {
file.id = ret.data.id || 0, file.xurl = ret.data.url;
file.xsafe = ret.data.safe, file.xpath = ret.data.key, file.xtype = ret.data.uptype;
if (parseInt(ret.code) === 404) {
let uploader = {};
uploader.uptype = ret.data.uptype;
uploader.url = ret.data.server;
uploader.head = {};
uploader.form = new FormData();
uploader.form.append('key', ret.data.key);
uploader.form.append('safe', ret.data.safe);
uploader.form.append('uptype', ret.data.uptype);
if (ret.data.uptype === 'qiniu') {
uploader.form.append('token', ret.data.token);
} else if (ret.data.uptype === 'alist') {
uploader.type = 'put';
uploader.head['file-path'] = ret.data['filepath'];
uploader.head['authorization'] = ret.data['authorization'];
uploader.form = new FormData();
} else if (ret.data.uptype === 'alioss') {
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('signature', ret.data['signature']);
uploader.form.append('OSSAccessKeyId', ret.data['OSSAccessKeyId']);
uploader.form.append('success_action_status', '200');
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
} else if (ret.data.uptype === 'txcos') {
uploader.form.append('q-ak', ret.data['q-ak']);
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('q-key-time', ret.data['q-key-time']);
uploader.form.append('q-signature', ret.data['q-signature']);
uploader.form.append('q-sign-algorithm', ret.data['q-sign-algorithm']);
uploader.form.append('success_action_status', '200');
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
} else if (ret.data.uptype === 'upyun') {
uploader.form.delete('key');
uploader.form.delete('safe');
uploader.form.delete('uptype');
uploader.form.append('save-key', ret.data['key']);
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('authorization', ret.data['authorization']);
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
}
uploader.form.append('file', file, file.name), jQuery.ajax({
xhrFields: {withCredentials: ret.data.uptype === 'local'}, headers: uploader.head, url: uploader.url, data: uploader.form, type: uploader.type || 'post', xhr: function (xhr) {
xhr = new XMLHttpRequest();
return xhr.upload.addEventListener('progress', function (event) {
file.xtotal = event.total, file.xloaded = event.loaded || 0;
that.progress((file.xloaded / file.xtotal * 100).toFixed(2), file)
}), xhr;
}, contentType: false, error: function () {
that.event('upload.error', {file: file}, file, '{:lang("上传接口异常!")}');
}, processData: false, success: function (ret) {
// 兼容数据格式
if (typeof ret === 'string' && ret.length > 0) try {
ret = JSON.parse(ret) || ret;
} catch (e) {
console.log(e)
}
if (typeof ret !== 'object') {
ret = {code: 1, url: file.xurl, info: '{:lang("文件上传成功!")}'};
}
/*! 检查单个文件上传返回的结果 */
if (typeof ret === 'object' && ret.code < 1) {
that.event('upload.error', {file: file}, file, ret.info || '{:lang("文件上传失败!")}');
} else if (uploader.uptype === 'alist' && parseInt(ret.code) !== 200) {
that.event('upload.error', {file: file}, file, ret.message || '{:lang("文件上传失败!")}');
} else {
that.done(ret, file.index, file, done, '{:lang("文件上传成功!")}');
}
}
});
} else if (parseInt(ret.code) === 200) {
(file.xurl = ret.data.url), that.progress('100.00', file);
that.done({code: 1, url: file.xurl, info: file.xstats, data: {code: 200, url: file.xurl}}, file.index, file, done, '{:lang("文件秒传成功!")}');
} else {
that.event('upload.error', {file: file}, file, ret.info || ret.error.message || '{:lang("文件上传出错!")}');
}
}
});
};
// 上传进度
Adapter.prototype.progress = function (number, file) {
this.event('upload.progress', {number: number, file: file});
if (file.notify) file.notify.setProgress(number);
};
// 上传结果
Adapter.prototype.done = function (ret, idx, file, done, message) {
/*! 检查单个文件上传返回的结果 */
if (ret.code < 1) return $.msg.tips(ret.info || '{:lang("文件上传失败!")}');
if (typeof file.xurl !== 'string') return $.msg.tips('{:lang("无效的文件上传对象!")}');
jQuery.post("{:url('admin/api.upload/done',[],false,true)}", {id: file.id, hash: file.xmd5});
/*! 单个文件上传成功结果处理 */
if (typeof done === 'function') {
done.call(this.option.elem, file.xurl, this.files['id']);
} else if (this.option.mult < 1 && this.option.elem.data('input')) {
$(this.option.elem.data('input')).val(file.xurl).trigger('change', file);
}
// 文件上传成功事件
this.event('push', file.xurl).event('upload.done', {file: file, data: ret}, file, message);
/*! 所有文件上传完成后结果处理 */
if (this.count.success + this.count.error >= this.count.total) {
this.option.hide || $.msg.close(this.loader);
if (this.option.mult > 0 && this.option.elem.data('input')) {
let urls = this.option.elem.data('input').value || [];
if (typeof urls === 'string') urls = urls.split('|');
for (let i in this.files) urls.push(this.files[i].xurl);
$(this.option.elem.data('input')).val(urls.join('|')).trigger('change', this.files);
}
this.event('upload.complete', {file: this.files}, file).init().uploader && this.uploader.reload();
}
};
/*! 触发事件过程 */
Adapter.prototype.event = function (name, data, file, message) {
if (name === 'upload.error') {
this.count.error++, file.xstate = -1, file.xstats = message;
if (file.notify) file.notify.setError(message || file.xstats || '');
} else if (name === 'upload.done') {
this.count.success++, file.xstate = 1, file.xstats = message;
if (file.notify) file.notify.setSuccess(message || file.xstats || '')
}
if (this.option.elem) {
this.option.elem.triggerHandler(name, data);
if (this.option.input) this.option.input.triggerHandler(name, data);
}
return this;
};
/**
* 计算文件 HASH
* @param {File} file 文件对象
* @return {Promise}
*/
Adapter.prototype.hash = function (file) {
let defer = jQuery.Deferred();
file.xext = file.name.indexOf('.') > -1 ? file.name.split('.').pop() : 'tmp';
/*! 兼容不能计算文件 HASH 的情况 */
let IsDate = '{$nameType|default=""}'.indexOf('date') > -1;
if (!window.FileReader || IsDate) return jQuery.when((function (xmd5, chars) {
while (xmd5.length < 32) xmd5 += chars.charAt(Math.floor(Math.random() * chars.length));
return SetFileXdata(file, xmd5, 6), defer.promise();
})(layui.util.toDateString(Date.now(), 'yyyyMMddHHmmss-'), '0123456789'));
/*! 读取文件并计算 HASH 值 */
return new LoadNextChunk(file).ReadAsChunk();
function SetFileXdata(file, xmd5, slice) {
file.xmd5 = xmd5, file.xstate = 0, file.xstats = '';
file.xkey = file.xmd5.substring(0, slice || 2) + '/' + file.xmd5.substring(slice || 2) + '.' + file.xext;
if (file.path) file.xkey = file.path + '/' + file.xkey;
return defer.resolve(file, file.xmd5, file.xkey), file;
}
function LoadNextChunk(file) {
let that = this, reader = new FileReader(), spark = new SparkMD5.ArrayBuffer();
let slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
this.chunkIdx = 0, this.chunkSize = 2097152, this.chunkTotal = Math.ceil(file.size / this.chunkSize);
reader.onload = function (event) {
spark.append(event.target.result);
++that.chunkIdx < that.chunkTotal ? that.ReadAsChunk() : SetFileXdata(file, spark.end());
}, reader.onerror = function () {
defer.reject();
}, this.ReadAsChunk = function () {
this.start = that.chunkIdx * that.chunkSize;
this.loaded = this.start + that.chunkSize >= file.size ? file.size : this.start + that.chunkSize;
reader.readAsArrayBuffer(slice.call(file, this.start, this.loaded));
defer.notify(file, (this.loaded / file.size * 100).toFixed(2));
return defer.promise();
};
}
};
return UploadAdapter;
/**
* 上传状态提示扩展插件
* @param {File} file 文件对象
* @constructor
*/
function NotifyExtend(file) {
let that = this, message = "{:lang('上传进度 %s', ['<span data-upload-progress>0%</span>'])}";
this.notify = Notify.notify({width: 260, title: file.name, showProgress: true, description: message, type: 'default', position: 'top-right', closeTimeout: 0});
this.$elem = $(this.notify.notification.nodes);
this.$elem.find('.growl-notification__progress').addClass('is-visible');
this.$elem.find('.growl-notification__progress-bar').addClass('transition');
this.setProgress = function (number) {
this.$elem.find('[data-upload-progress]').html(number + '%');
this.$elem.find('.growl-notification__progress-bar').css({width: number + '%'});
return this;
}, this.setError = function (message) {
this.$elem.find('.growl-notification__desc').html(message || '{:lang("文件上传失败!")}');
this.$elem.removeClass('growl-notification--default').addClass('growl-notification--error')
return this.close();
}, this.setSuccess = function (message) {
this.setProgress('100.00');
this.$elem.find('.growl-notification__desc').html(message || '{:lang("文件上传成功!")}');
this.$elem.removeClass('growl-notification--default').addClass('growl-notification--success');
return this.close();
}, this.close = function (timeout) {
return setTimeout(function () {
that.notify.close();
}, timeout || 2000), this;
};
}
});

View File

@ -0,0 +1,161 @@
<div class="image-dialog" id="ImageDialog">
<div class="image-dialog-head">
<label class="pull-left flex">
<input class="layui-input margin-right-5" v-model="keys" style="height:30px;line-height:30px" placeholder="{:lang('请输入搜索关键词')}">
<a class="layui-btn layui-btn-sm layui-btn-normal" @click="search">{:lang('搜 索')}</a>
</label>
<div class="pull-right">
<a class="layui-btn layui-btn-sm layui-btn-normal" @click="uploadImage">{:lang('上传图片')}</a>
</div>
</div>
<div class="image-dialog-body">
<div class="image-dialog-item" v-for="x in list" @click="setItem(x)" style="display:none" v-show="show" :class="{'image-dialog-checked':x.checked}">
<div class="uploadimage" :style="x.style"></div>
<p class="image-dialog-item-name layui-elip" v-text="x.name"></p>
<div class="image-dialog-item-tool">
<span class="image-dialog-item-type">{{x.xext.toUpperCase()}}</span>
<span class="image-dialog-item-size">{{formatSize(x.size)}}</span>
{if auth('admin/file/remove')}
<span class="layui-icon layui-icon-close image-dialog-item-close" @click.stop="remove(x)"></span>
{/if}
</div>
</div>
</div>
<div class="image-dialog-foot">
<div id="ImageDialogPage" class="image-dialog-page"></div>
<div id="ImageDialogButton layui-hide" class="image-dialog-button layui-btn layui-btn-normal" v-if="data.length>0" @click="confirm">
{php} $tag = '{{data.length}}'; {/php}
{:lang('已选 %s 张,确认', [$tag])}
</div>
</div>
</div>
<script>
require(['vue'], function (vue) {
var app = new vue({
el: '#ImageDialog',
data: {
didx: 0,
page: 1, limit: 15, show: false, mult: false,
keys: '', list: [], data: [], idxs: {}, urls: [],
},
created: function () {
this.didx = $.msg.mdx.pop();
this.$btn = $('#{$get.id|default=""}');
this.$ups = $('#ImageDialogUploadLayout [data-file]');
this.mult = "{$get.file|default='image'}" === 'images';
this.loadPage(), setTimeout(function () {
$('#ImageDialogButton').removeClass('layui-hide');
}, 1000);
},
methods: {
// 搜索刷新数据
search: function () {
this.page = 1;
this.loadPage();
},
// 确认选择数据
confirm: function () {
this.urls = [];
this.data.forEach(function (file) {
app.setValue(file.xurl);
});
this.setInput();
},
// 删除指定的图片
remove: function (x) {
$.msg.confirm('确认要移除这张图片吗?', function () {
$.form.load('{:url("admin/file/remove")}', {id: x.id}, 'POST', function (ret) {
ret.code > 0 ? app.loadPage() : $.msg.error(ret.info);
return app.$forceUpdate(), false;
})
})
},
// 格式文件大小
formatSize: function (size) {
return $.formatFileSize(size);
},
// 设置单项数据
setItem: function (item) {
if (!this.mult) {
this.setValue(item.xurl).setInput();
} else if ((item.checked = !this.idxs[item.id])) {
(this.idxs[item.id] = item) && this.data.push(item);
} else {
delete this.idxs[item.id];
this.data.forEach(function (temp, idx) {
temp.id === item.id && app.data.splice(idx, 1);
});
}
},
// 更新列表数据
setList: function (items, count) {
this.list = items;
this.list.forEach(function (item) {
item.checked = !!app.idxs[item.id]
item.style = 'background-image:url(' + item.xurl + ')';
});
this.addPage(count);
},
// 设置选择数据
setValue: function (xurl) {
$.msg.close(this.didx);
this.urls.push(xurl) && this.$btn.triggerHandler('push', xurl);
return this;
},
// 设置输入表单
setInput: function () {
if (this.$btn.data('input')) {
$(this.$btn.data('input')).val(this.urls.join('|')).trigger('change');
}
},
// 创建分页工具条
addPage: function (count) {
this.show = true;
layui.laypage.render({
curr: this.page, count: count, limit: app.limit,
layout: ['count', 'prev', 'page', 'next', 'refresh'],
elem: 'ImageDialogPage', jump: function (obj, first) {
if (!first) app.loadPage(app.page = obj.curr);
},
});
},
// 加载页面数据
loadPage: function () {
this.params = {page: this.page, limit: this.limit, output: 'layui.table', name: this.keys || ''};
this.params.type = '{$get.type|default="gif,png,jpg,jpeg"}';
$.form.load('{:url("image",[],false,true)}', this.params, 'get', function (ret) {
return app.setList(ret.data, ret.count), false;
});
},
// 上传图片文件
uploadImage: function () {
this.urls = [];
this.$ups.off('push').on('push', function (e, xurl) {
app.setValue(xurl);
}).off('upload.complete').on('upload.complete', function () {
app.setInput();
}).click();
},
}
});
});
</script>
<label class="layui-hide" id="ImageDialogUploadLayout">
<!-- 图片上传组件 开始 -->
{if isset($get.file) && $get.file eq 'image'}
<button data-file="one" data-type="{$get.type|default='gif,png,jpg,jpeg'}"
data-path="{$get.path|default=''}" data-size="{$get.size|default=0}"
data-cut-width="{$get.cutWidth|default=0}" data-cut-height="{$get.cutHeight|default=0}"
data-max-width="{$get.maxWidth|default=0}" data-max-height="{$get.maxHeight|default=0}"
></button>
{else}
<button data-file="mul" data-type="{$get.type|default='gif,png,jpg,jpeg'}"
data-path="{$get.path|default=''}" data-size="{$get.size|default=0}"
data-cut-width="{$get.cutWidth|default=0}" data-cut-height="{$get.cutHeight|default=0}"
data-max-width="{$get.maxWidth|default=0}" data-max-height="{$get.maxHeight|default=0}"
></button>
{/if}
<!-- 图片上传组件 结束 -->
</label>

View File

@ -0,0 +1,143 @@
{extend name='main'}
{block name="button"}
<button data-target-submit class='layui-btn layui-btn-sm'>{:lang('保存数据')}</button>
<button data-target-backup class="layui-btn layui-btn-sm layui-btn-danger">{:lang('取消编辑')}</button>
{/block}
{block name="content"}
<div class="think-box-shadow">
<form method="post" id="RoleForm" class="layui-form layui-card">
<div class="layui-card-body">
<label class="layui-form-item relative block">
<span class="help-label"><b>{:lang('权限名称')}</b>Auth Name</span>
<input maxlength="100" class="layui-input" name="title" value='{$vo.title|default=""}' required vali-name="{:lang('权限名称')}" placeholder="{:lang('请输入权限名称')}">
<span class="help-block">{:lang('访问权限名称需要保持不重复,在给用户授权时需要根据名称选择!')}</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>{:lang('权限描述')}</b>Auth Remark</span>
<textarea placeholder="{:lang('请输入权限描述')}" maxlength="200" class="layui-textarea" name="desc">{$vo.desc|default=""}</textarea>
</label>
<div class="layui-form-item">
<span class="help-label label-required-prev"><b>{:lang('功能节点')}</b>Auth Nodes</span>
<ul id="zTree" class="ztree notselect"></ul>
</div>
<div class="hr-line-dashed"></div>
{notempty name='vo.id'}<input name="id" value="{$vo.id}" type="hidden"/>{/notempty}
<div class="layui-form-item text-center">
<button data-target-submit class="layui-btn">{:lang('保存数据')}</button>
<button data-target-backup class="layui-btn layui-btn-danger" type="button">{:lang('取消编辑')}</button>
</div>
</div>
</form>
</div>
{/block}
{block name="script"}
<script>
require(['jquery.ztree'], function () {
new function () {
let that = this;
this.data = {}, this.ztree = null, this.setting = {
view: {showLine: false, showIcon: false, dblClickExpand: false},
check: {enable: true, nocheck: false, chkboxType: {"Y": "ps", "N": "ps"}}, callback: {
beforeClick: function (id, node) {
node.children.length < 1 ? that.ztree.checkNode(node, !node.checked, true, true) : that.ztree.expandNode(node);
return false;
}
}
};
this.renderChildren = function (list, level) {
let childrens = [];
for (let i in list) childrens.push({
open: true, node: list[i].node, name: list[i].title || list[i].node,
checked: list[i].checked || false, children: this.renderChildren(list[i]._sub_, level + 1)
});
return childrens;
};
this.syncData = function () {
$.form.load('{:sysuri()}', {id: '{$vo.id|default=0}', action: 'json'}, 'post', function (ret) {
return (that.data = that.renderChildren(ret.data, 1)), that.showTree(), false;
});
};
this.showTree = function () {
this.ztree = $.fn.zTree.init($("#zTree"), this.setting, this.data);
while (true) {
let nodes = this.ztree.getNodesByFilter(function (node) {
return (!node.node && node.children.length < 1);
});
if (nodes.length < 1) break;
for (let i in nodes) this.ztree.removeNode(nodes[i]);
}
};
// 刷新数据
this.syncData();
// 监听表单提交
$('#RoleForm').vali(function (form) {
let data = that.ztree.getCheckedNodes(true);
Object.assign(form, {nodes: [], action: 'save'})
for (let i in data) if (data[i].node) form.nodes.push(data[i].node);
$.form.load('{:sysuri()}', form, 'post');
});
};
});
</script>
{/block}
{block name="style"}
<style>
ul.ztree li {
line-height: 24px;
white-space: normal !important;
}
ul.ztree li span.button.switch {
margin-right: 5px;
}
ul.ztree ul ul li {
display: inline-block;
white-space: normal;
}
ul.ztree > li {
border: 1px solid rgba(0, 0, 0, 0.10);
padding: 15px;
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
margin-bottom: 10px;
}
ul.ztree > li > ul {
padding: 10px;
margin-top: 10px;
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
ul.ztree > li > ul > li {
padding: 5px 0;
}
ul.ztree > li > a > span {
font-size: 15px;
font-weight: 700;
}
ul.ztree .level2 .button.level2 {
width: 0;
}
ul.ztree li span.button.noline_open {
background-position-y: -73px;
}
ul.ztree li span.button.noline_close {
background-position-y: -73px;
}
ul.ztree .level1 > .node_name {
font-weight: bold;
}
</style>
{/block}

View File

@ -0,0 +1,76 @@
{extend name='table'}
{block name="button"}
<!--{if auth("add")}-->
<button data-open='{:url("add")}' data-table-id="RoleTable" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('添加权限')}</button>
<!--{/if}-->
<!--{if auth("remove")}-->
<button data-action='{:url("remove")}' data-rule="id#{id}" data-table-id="RoleTable" data-confirm="{:lang('确定要批量删除权限吗?')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</button>
<!--{/if}-->
{/block}
{block name="content"}
<div class="think-box-shadow">
{include file='auth/index_search'}
<table id="RoleTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
{/block}
{block name='script'}
<script>
$(function () {
// 初始化表格组件
$('#RoleTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'desc'},
cols: [[
{checkbox: true, fixed: true},
{field: 'sort', title: '{:lang("排序权重")}', align: 'center', width: 100, sort: true, templet: '#SortInputRoleTableTpl'},
{field: 'title', title: '{:lang("权限名称")}', align: 'center', minWidth: 140},
{field: 'desc', title: '{:lang("权限描述")}', align: 'center', minWidth: 110, templet: '<div>{{d.desc||"-"}}</div>'},
{field: 'status', title: '{:lang("使用状态")}', align: 'center', minWidth: 110, templet: '#StatusSwitchRoleTableTpl'},
{field: 'create_at', title: '{:lang("创建时间")}', align: 'center', minWidth: 170, sort: true},
{toolbar: '#ToolbarRoleTableTpl', title: '{:lang("操作面板")}', align: 'center', minWidth: 210, fixed: 'right'},
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitchRoleTable)', function (obj) {
let data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
$.form.load("{:url('state')}", data, 'post', function (ret) {
if (ret.code < 1) $.msg.error(ret.info, 3, function () {
$('#RoleTable').trigger('reload');
});
return false;
}, false);
});
});
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputRoleTableTpl">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:request()->url()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchRoleTableTpl">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="{:lang('已激活')}|{:lang('已禁用')}" lay-filter="StatusSwitchRoleTable" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">{:lang("已启用")}</b>' : '<b class="color-red">{:lang("已禁用")}</b>'}}
<!--{/if}-->
</script>
<!-- 数据操作工具条模板 -->
<script type="text/html" id="ToolbarRoleTableTpl">
<!--{if auth('edit')}-->
<a class="layui-btn layui-btn-primary layui-btn-sm" data-open='{:url("edit")}?id={{d.id}}'>{:lang("编 辑")}</a>
<!--{/if}-->
<!--{if auth("remove")}-->
<a class="layui-btn layui-btn-danger layui-btn-sm" data-action="{:url('remove')}" data-value="id#{{d.id}}" data-confirm="{:lang('确定要删除权限吗?')}">{:lang("删 除")}</a>
<!--{/if}-->
</script>
{/block}

View File

@ -0,0 +1,45 @@
<fieldset>
<legend>{:lang('条件搜索')}</legend>
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('权限名称')}</label>
<div class="layui-input-inline">
<input name="title" value="{$get.title|default=''}" placeholder="{:lang('请输入权限名称')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('权限描述')}</label>
<div class="layui-input-inline">
<input name="desc" value="{$get.desc|default=''}" placeholder="{:lang('请输入权限描述')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('使用状态')}</label>
<div class="layui-input-inline">
<select class="layui-select" name="status">
<option value=''>-- {:lang('全部')} --</option>
{foreach [lang('已禁用记录'),lang('已激活记录')] as $k=>$v}
{if isset($get.status) and $get.status eq $k.""}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('创建时间')}</label>
<div class="layui-input-inline">
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>
</fieldset>

View File

@ -0,0 +1,68 @@
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="BaseTable">
<div class="layui-card-body padding-left-40">
<div class="layui-form-item label-required-prev">
<div class="help-label"><b>数据类型</b>Data Type</div>
{if isset($vo.type)}
<label><input readonly value="{$vo.type|default=''}" class="layui-input think-bg-gray"></label>
{else}
<select class="layui-select" lay-filter="DataType">
{foreach $types as $type}{if (isset($vo.type) and $type eq $vo.type) or ($type eq input('get.type'))}
<option selected value="{$type}">{$type}</option>
{else}
<option value="{$type}">{$type}</option>
{/if}{/foreach}
</select>
<script>
(function (callable) {
layui.form.on('select(DataType)', callable);
callable({value: "{$vo.type|default=''}" || $('[lay-filter=DataType]').val()});
})(function (data) {
if (data.value === '--- 新增类型 ---') {
$('#DataTypeInput').removeClass('layui-hide').find('input').val('').focus();
} else {
$('#DataTypeInput').addClass('layui-hide').find('input').val(data.value);
}
});
</script>
{/if}
<p class="help-block">请选择数据类型,数据创建后不能再次修改哦 ~</p>
<div id="DataTypeInput" class="layui-hide relative">
<input class="layui-input" maxlength="20" name="type" required vali-name="数据类型" placeholder="请输入数据类型" value="{$vo.type|default=''}">
<p class="help-block">请输入新的数据类型,数据创建后不能再次修改哦 ~</p>
</div>
</div>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据编码</b>Data Code</span>
{if isset($vo.code)}
<input readonly maxlength="100" class="layui-input think-bg-gray" name="code" value='{$vo.code|default=""}' required placeholder="请输入数据编码">
{else}
<input maxlength="100" class="layui-input" name="code" value='{$vo.code|default=""}' required vali-name="数据编码" placeholder="请输入数据编码">
{/if}
<span class="help-block">请输入新的数据编码,数据创建后不能再次修改,同种数据类型的数据编码不能出现重复 ~</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据名称</b>Data Name</span>
<input maxlength="500" class="layui-input" name="name" value='{$vo.name|default=""}' required vali-name="数据名称" placeholder="请输入数据名称">
<span class="help-block">请输入当前数据名称,请尽量保持名称的唯一性,数据名称尽量不要出现重复 ~</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据内容</b>Data Content</span>
<textarea name="content" class="layui-textarea" placeholder="请输入数据内容">{$vo.content|default=''}</textarea>
</label>
</div>
<div class="hr-line-dashed"></div>
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>

View File

@ -0,0 +1,86 @@
{extend name='table'}
{block name="button"}
<!--{if auth("add")}-->
<button data-table-id="BaseTable" data-modal='{:url("add")}?type={$type|default=""}' class='layui-btn layui-btn-sm layui-btn-primary'>添加数据</button>
<!--{/if}-->
<!--{if auth("remove")}-->
<button data-table-id="BaseTable" data-action='{:url("remove")}' data-rule="id#{id}" data-confirm="确定要批量删除数据吗?" class='layui-btn layui-btn-sm layui-btn-primary'>批量删除</button>
<!--{/if}-->
{/block}
{block name="content"}
<div class="layui-tab layui-tab-card">
<ul class="layui-tab-title">
{foreach $types as $t}{if isset($type) and $type eq $t}
<li class="layui-this" data-open="{:sysuri()}?type={$t}">{$t}</li>
{else}
<li data-open="{:sysuri()}?type={$t}">{$t}</li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='base/index_search'}
<table id="BaseTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
</div>
{/block}
{block name='script'}
<script>
$(function () {
// 初始化表格组件
$('#BaseTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'asc'},
where: {type: '{$type|default=""}'},
cols: [[
{checkbox: true, fixed: true},
{field: 'sort', title: '{:lang("排序权重")}', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
// {field: 'type', title: '数据类型', minWidth: 140, align: 'center'},
{field: 'code', title: '数据编码', width: '20%', align: 'left'},
{field: 'name', title: '数据名称', width: '30%', align: 'left'},
{field: 'status', title: '数据状态', minWidth: 110, align: 'center', templet: '#StatusSwitchTpl'},
{field: 'create_at', title: '创建时间', minWidth: 170, align: 'center', sort: true},
{toolbar: '#toolbar', align: 'center', minWidth: 150, title: '数据操作', fixed: 'right'},
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitch)', function (obj) {
var data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
$.form.load("{:url('state')}", data, 'post', function (ret) {
if (ret.code < 1) $.msg.error(ret.info, 3, function () {
$('#BaseTable').trigger('reload');
});
return false;
}, false);
});
});
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputTpl">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchTpl">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="已激活|已禁用" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已启用</b>' : '<b class="color-red">已禁用</b>'}}
<!--{/if}-->
</script>
<!-- 数据操作工具条模板 -->
<script type="text/html" id="toolbar">
<!--{if auth('edit')}-->
<a class="layui-btn layui-btn-primary layui-btn-sm" data-event-dbclick data-title="编辑数据" data-modal='{:url("edit")}?id={{d.id}}'> </a>
<!--{/if}-->
<!--{if auth("remove")}-->
<a class="layui-btn layui-btn-danger layui-btn-sm" data-confirm="确定要删除数据吗?" data-action="{:url('remove')}" data-value="id#{{d.id}}"> </a>
<!--{/if}-->
</script>
{/block}

View File

@ -0,0 +1,42 @@
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('数据编码')}</label>
<div class="layui-input-inline">
<input name="code" value="{$get.code|default=''}" placeholder="{:lang('请输入数据编码')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('数据名称')}</label>
<div class="layui-input-inline">
<input name="name" value="{$get.name|default=''}" placeholder="{:lang('请输入数据名称')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('使用状态')}</label>
<div class="layui-input-inline">
<select class="layui-select" name="status">
<option value=''>-- {:lang('全部')} --</option>
{foreach [lang('已禁用记录'),lang('已激活记录')] as $k=>$v}
{if isset($get.status) and $get.status eq $k.""}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">{:lang('创建时间')}</label>
<div class="layui-input-inline">
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>

View File

@ -0,0 +1,234 @@
{extend name="main"}
{block name="button"}
<!--{if isset($super) and $super}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-load="{:url('admin/api.system/config')}">{:lang('清理无效配置')}</a>
<!--{/if}-->
<!--{if auth('system')}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-modal="{:url('system')}">{:lang('修改系统参数')}</a>
<!--{/if}-->
{/block}
{block name="content"}
<!--{notempty name='issuper'}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('运行模式')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
<!--{if $app->isDebug()}-->
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到生产模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=1">{:lang('以生产模式运行')}</a>
<!--{else}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到开发模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=0">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以生产模式运行')}</a>
<!--{/if}-->
</div>
<div class="margin-top-20">
<p><b>{:lang('开发模式')}</b>{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}</p>
<p><b>{:lang('生产模式')}</b>{:lang('项目正式部署上线后使用,系统异常时统一显示 “%s”只记录重要的异常日志信息强烈推荐上线后使用此模式。',[config('app.error_message')])}</p>
</div>
</div>
</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('富编辑器')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<div class="layui-card-body layui-clear">
<div class="layui-btn-group shadow-mini">
{if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if}
{foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k}
{if auth('storage')}<a data-title="配置{$v}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-action="{:url('admin/api.system/editor')}" data-value="editor#{$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width pull-left">
<p><b>CKEditor4</b>{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}</p>
<p><b>CKEditor5</b>{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}</p>
<p><b>wangEditor</b>{:lang('国产优质富文本编辑器对于小程序及App内容支持会更友好推荐使用。')}</p>
<p><b>{:lang('自适应模式')}</b>{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}</p>
</div>
</div>
</div>
<!--{/notempty}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('存储引擎')}</b>( {:lang('文件默认存储方式')} )
</span>
</div>
<!-- 初始化存储配置 -->
{if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if}
{if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
{if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
{if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if}
{if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if}
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
{foreach $files as $k => $v}{if sysconf('storage.type') eq $k}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width">
<p><b>{:lang('本地服务器存储')}</b>{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}</p>
<p><b>{:lang('自建Alist存储')}</b>{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}</p>
<p><b>{:lang('七牛云对象存储')}</b>{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('又拍云USS存储')}</b>{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('阿里云OSS存储')}</b>{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('腾讯云COS存储')}</b>{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
</div>
</div>
</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统参数')}</b>( {:lang('当前系统配置参数')} )
</span>
</div>
<div class="layui-card-body">
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站名称')}</b>Website</div>
<label class="relative block">
<input readonly value="{:sysconf('site_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序名称')}</b>Name</div>
<label class="relative block">
<input readonly value="{:sysconf('app_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序名称,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序版本')}</b>Version</div>
<label class="relative block">
<input readonly value="{:sysconf('app_version')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_version')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序版本,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('公安备案号')}</b>Beian</div>
<label class="relative block">
<input readonly value="{:sysconf('beian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('beian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<p class="help-block">
{:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['<a target="_blank" href="https://www.beian.gov.cn/portal/registerSystemInfo">www.beian.gov.cn</a>'])}
</p>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站备案号')}</b>Miitbeian</div>
<label class="relative block">
<input readonly value="{:sysconf('miitbeian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('miitbeian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">
{:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['<a target="_blank" href="https://beian.miit.gov.cn">beian.miit.gov.cn</a>'])}
</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站版权信息')}</b>Copyright</div>
<label class="relative block">
<input readonly value="{:sysconf('site_copy')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_copy')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}</div>
</div>
</div>
</div>
<!--{if $app->isDebug()}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统信息')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<tbody>
<tr>
<th class="nowrap text-center">{:lang('核心框架')}</th>
<td><a target="_blank" href="https://www.thinkphp.cn">ThinkPHP Version {$framework.version|default='None'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('平台框架')}</th>
<td><a target="_blank" href="https://thinkadmin.top">ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('操作系统')}</th>
<td>{:php_uname()}</td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('运行环境')}</th>
<td>{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}</td>
</tr>
<!-- {notempty name='systemid'} -->
<tr>
<th class="nowrap text-center">{:lang('系统序号')}</th>
<td>{$systemid|default=''}</td>
</tr>
<!-- {/notempty} -->
</tbody>
</table>
</div>
</div>
{notempty name='plugins'}
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('应用插件')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<thead>
<tr>
<th class="nowrap text-center">{:lang('应用名称')}</th>
<th class="nowrap text-center">{:lang('插件名称')}</th>
<th class="nowrap text-left">{:lang('插件包名')}</th>
<th class="nowrap text-center">{:lang('插件版本')}</th>
<th class="nowrap text-center">{:lang('授权协议')}</th>
</tr>
</thead>
<tbody>
{foreach $plugins as $key=>$plugin}
<tr>
<td class="nowrap text-center">{$key}</td>
<td class="nowrap text-center">{$plugin.name|lang}</td>
<td class="nowrap text-left">
{if empty($plugin.install.document)}{$plugin.package}
{else}<a target="_blank" href="{$plugin.install.document}">{$plugin.package}</a>{/if}
</td>
<td class="nowrap text-center">{$plugin.install.version|default='unknow'}</td>
<td class="nowrap text-center">
{if empty($plugin.install.license)} -
{elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###}
{else}{$plugin.install.license|default='-'}{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
{/notempty}
<!--{/if}-->
{/block}

View File

@ -0,0 +1,49 @@
<div class="layui-form-item">
<label class="layui-form-label label-required">
<b class="color-green">命名方式</b><br><span class="nowrap color-desc">NameType</span>
</label>
<div class="layui-input-block">
<div class="layui-input help-checks">
{foreach ['xmd5'=>'文件哈希值 ( 支持秒传 )','date'=>'日期+随机 ( 普通上传 )'] as $k=>$v}
<label class="think-radio notselect">
{if sysconf('storage.name_type') eq $k}
<input checked type="radio" name="storage.name_type" value="{$k}" lay-ignore> {$v}
{else}
<input type="radio" name="storage.name_type" value="{$k}" lay-ignore> {$v}
{/if}
</label>
{/foreach}
</div>
<p class="help-block">类型为“文件哈希”时可以实现文件秒传功能,同一个文件只需上传一次节省存储空间,推荐使用。</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label label-required">
<b class="color-green">链接类型</b><br><span class="nowrap color-desc">LinkType</span>
</label>
<div class="layui-input-block">
<div class="layui-input help-checks">
{foreach ['none'=>'简洁链接','full'=>'完整链接','none+compress'=>'简洁并压缩图片','full+compress'=>'完整并压缩图片'] as $k=>$v}
<label class="think-radio notselect">
{if sysconf('storage.link_type') eq $k}
<input checked type="radio" name="storage.link_type" value="{$k}" lay-ignore> {$v}
{else}
<input type="radio" name="storage.link_type" value="{$k}" lay-ignore> {$v}
{/if}
</label>
{/foreach}
</div>
<p class="help-block">类型为“简洁链接”时链接将只返回 hash 地址,而“完整链接”将携带参数保留文件名,图片压缩功能云平台会单独收费。</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.allow_exts">
<b class="color-green">允许类型</b><br><span class="nowrap color-desc">AllowExts</span>
</label>
<div class="layui-input-block">
<input id="storage.allow_exts" type="text" name="storage.allow_exts" value="{:sysconf('storage.allow_exts')}" required vali-name="文件后缀" placeholder="请输入系统文件上传后缀" class="layui-input">
<p class="help-block">设置系统允许上传的文件后缀多个以英文逗号隔开如png,jpg,rar,doc未包含在设置内的文件后缀将不被允许上传。</p>
</div>
</div>

View File

@ -0,0 +1,98 @@
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body padding-top-20">
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3646641&userCode=ztlqlu4v">阿里云</a> OSS 存储,需要配置 OSS 公开访问及跨域策略</p>
<p>配置跨域访问 CORS 规则,设置:来源 Origin *,允许 Methods POST允许 Headers *</p>
</div>
{include file='config/storage-0'}
<div class="layui-form-item">
<label class="layui-form-label label-required">
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
</label>
<div class="layui-input-block">
{if !sysconf('storage.alioss_http_protocol')}{php}sysconf('storage.alioss_http_protocol','http');{/php}{/if}
<div class="layui-input help-checks">
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
<label class="think-radio">
{if sysconf('storage.alioss_http_protocol') eq $protocol}
<input checked type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$remark}
{else}
<input type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$remark}
{/if}
</label>
{/foreach}
</div>
<p class="help-block">阿里云OSS存储访问协议其中 HTTPS 需要配置证书才能使用AUTO 为相对协议)</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">
<b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
</label>
<div class="layui-input-block">
<select class="layui-select" name="storage.alioss_point" lay-search>
{foreach $points as $point => $title}
{if sysconf('storage.alioss_point') eq $point}
<option selected value="{$point}">{$title} {$point} </option>
{else}
<option value="{$point}">{$title} {$point} </option>
{/if}{/foreach}
</select>
<p class="help-block">阿里云OSS存储空间所在区域需要严格对应储存所在区域才能上传文件</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alioss_bucket">
<b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
</label>
<div class="layui-input-block">
<input id="storage.alioss_bucket" type="text" name="storage.alioss_bucket" value="{:sysconf('storage.alioss_bucket')}" required vali-name="空间名称" placeholder="请输入阿里云OSS存储 Bucket (空间名称)" class="layui-input">
<p class="help-block">填写阿里云OSS存储空间名称think-admin-oss需要是全区唯一的值不存在时会自动创建</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alioss_http_domain">
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
</label>
<div class="layui-input-block">
<input id="storage.alioss_http_domain" type="text" name="storage.alioss_http_domain" value="{:sysconf('storage.alioss_http_domain')}" required vali-name="访问域名" placeholder="请输入阿里云OSS存储 Domain (访问域名)" class="layui-input">
<p class="help-block">填写阿里云OSS存储外部访问域名不需要填写访问协议static.alioss.thinkadmin.top</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alioss_access_key">
<b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
</label>
<div class="layui-input-block">
<input id="storage.alioss_access_key" type="text" name="storage.alioss_access_key" value="{:sysconf('storage.alioss_access_key')}" required vali-name="访问密钥" placeholder="请输入阿里云OSS存储 AccessKey (访问密钥)" class="layui-input">
<p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alioss_secret_key">
<b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
</label>
<div class="layui-input-block">
<input id="storage.alioss_secret_key" type="text" name="storage.alioss_secret_key" value="{:sysconf('storage.alioss_secret_key')}" maxlength="43" required vali-name="安全密钥" placeholder="请输入阿里云OSS存储 SecretKey (安全密钥)" class="layui-input">
<p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥</p>
</div>
</div>
<div class="hr-line-dashed margin-left-40"></div>
<input type="hidden" name="storage.type" value="alioss">
<div class="layui-form-item text-center padding-left-40">
<button class="layui-btn" type="submit">保存配置</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
</div>
</div>
</form>

View File

@ -0,0 +1,81 @@
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body padding-top-20">
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://alist.nn.ci/zh/">Alist</a> 自建存储,需要自行搭建 <a target="_blank" href="https://alist.nn.ci/zh/">Alist</a> 存储服务器。</p>
<p>Alist 是一个支持多种存储的文件列表程序,可将各种云盘及本地磁盘资源进行整合。</p>
<p>建议不要开放匿名用户访问,尽量使用独立账号管理,需要关闭 “签名所有” 让文件可以直接访问。</p>
</div>
{include file='config/storage-0'}
<div class="layui-form-item">
<label class="layui-form-label label-required">
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
</label>
<div class="layui-input-block">
{if !sysconf('storage.alist_http_protocol')}{php}sysconf('storage.alist_http_protocol','http');{/php}{/if}
<div class="layui-input help-checks">
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
<label class="think-radio">
{if sysconf('storage.alist_http_protocol') eq $protocol}
<input checked type="radio" name="storage.alist_http_protocol" value="{$protocol}" lay-ignore> {$remark}
{else}
<input type="radio" name="storage.alist_http_protocol" value="{$protocol}" lay-ignore> {$remark}
{/if}
</label>
{/foreach}
</div>
<p class="help-block">请选择 Alist 存储访问协议,其中 HTTPS 需要配置证书才能使用( AUTO 为相对协议 </p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alist_http_domain">
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
</label>
<div class="layui-input-block">
<input id="storage.alist_http_domain" type="text" name="storage.alist_http_domain" value="{:sysconf('storage.alist_http_domain')}" required vali-name="访问域名" placeholder="请输入 Alist 存储的访问域名" class="layui-input">
<p class="help-block">请填写 Alist 存储访问域名不需要填写访问协议storage.thinkadmin.top</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alist_savepath">
<b class="color-green">存储目录</b><br><span class="nowrap color-desc">Directory</span>
</label>
<div class="layui-input-block">
<input id="storage.alist_savepath" type="text" name="storage.alist_savepath" value="{:sysconf('storage.alist_savepath')}" required vali-name="存储目录" placeholder="请输入 Alist 存储目录" class="layui-input">
<p class="help-block">请填写 Alist 用户基本目录的相对存储位置,填写 / 表示用户基本目录( 需要拥有读写权限 </p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alist_username">
<b class="color-green">用户账号</b><br><span class="nowrap color-desc">Username</span>
</label>
<div class="layui-input-block">
<input id="storage.alist_username" name="storage.alist_username" value="{:sysconf('storage.alist_username')}" maxlength="100" required vali-name="用户账号" placeholder="请输入 Alist 存储的用户账号" class="layui-input">
<p class="help-block">请填写 Alist 用户账号,注意此账号需要拥有上面存储目录的访问权限。</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="storage.alist_password">
<b class="color-green">用户密码</b><br><span class="nowrap color-desc">Password</span>
</label>
<div class="layui-input-block">
<input id="storage.alist_password" name="storage.alist_password" value="{:sysconf('storage.alist_password')}" maxlength="100" required vali-name="用户密码" placeholder="请输入 Alist 存储的用户密码" class="layui-input">
<p class="help-block">请填写 Alist 用户登录密码,用于生成文件上传的接口认证令牌,如果填写错误将无法上传文件。</p>
</div>
</div>
<div class="hr-line-dashed margin-left-40"></div>
<input type="hidden" name="storage.type" value="alist">
<div class="layui-form-item text-center padding-left-40">
<button class="layui-btn" type="submit">保存配置</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
</div>
</div>
</form>

Some files were not shown because too many files have changed in this diff Show More