├── .env.example ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .htaccess ├── AppService.php ├── BaseController.php ├── ExceptionHandle.php ├── Request.php ├── command │ └── PluginPackage.php ├── common.php ├── controller │ ├── Admin.php │ ├── Auth.php │ ├── Base.php │ ├── Index.php │ ├── Install.php │ ├── Plugin.php │ ├── User.php │ ├── api │ │ ├── Category.php │ │ ├── Plugin.php │ │ └── User.php │ └── master │ │ ├── Analysis.php │ │ ├── Category.php │ │ ├── Clear.php │ │ ├── Cloud.php │ │ ├── Menu.php │ │ ├── Ota.php │ │ ├── Plugin.php │ │ ├── System.php │ │ └── User.php ├── event.php ├── lib │ ├── EnvOperation.php │ ├── ExecSQL.php │ ├── Jwt.php │ ├── Plugin.php │ ├── oauth │ │ ├── Oauth.php │ │ └── impl │ │ │ ├── Gitee.php │ │ │ └── Github.php │ └── permission │ │ ├── Permission.php │ │ └── impl │ │ ├── Admin.php │ │ ├── Login.php │ │ ├── Password.php │ │ └── Visitor.php ├── middleware.php ├── middleware │ ├── Auth.php │ ├── AuthAdmin.php │ ├── BindAuth.php │ ├── PluginCheck.php │ ├── RateLimit.php │ ├── Response.php │ └── View.php ├── model │ ├── Base.php │ ├── Category.php │ ├── Config.php │ ├── Migration.php │ ├── Plugin.php │ ├── Request.php │ └── User.php ├── provider.php ├── service.php ├── service │ └── ValidateService.php └── template │ └── exception.tpl ├── composer.json ├── config ├── app.php ├── cache.php ├── captcha.php ├── console.php ├── cookie.php ├── database.php ├── filesystem.php ├── lang.php ├── log.php ├── middleware.php ├── route.php ├── session.php ├── trace.php ├── version.php └── view.php ├── docs ├── Github_Oauth.md ├── Plugin.md ├── Plugin_Permission.md ├── Plugin_Template.md └── images │ ├── github_oauth_1.png │ ├── github_oauth_2.png │ ├── github_oauth_3.png │ ├── plugin_1.png │ ├── plugin_2.png │ ├── plugin_3.png │ ├── plugin_4.png │ ├── plugin_5.png │ ├── plugin_6.png │ ├── plugin_permission_1.png │ ├── plugin_permission_2.png │ ├── plugin_template_1.png │ ├── plugin_template_2.png │ ├── plugin_template_3.png │ ├── plugin_template_4.png │ ├── problem_1.png │ ├── view_1.png │ ├── view_2.png │ ├── view_3.gif │ └── view_4.png ├── install.sql ├── plugin ├── .gitignore ├── CheckCaptcha.php ├── Drive.php ├── Install.php ├── aoaostar_com │ └── example │ │ ├── App.php │ │ ├── Install.php │ │ ├── index.html │ │ └── logo.png └── common.php ├── public ├── .htaccess ├── 404.html ├── favicon.ico ├── index.php ├── robots.txt ├── router.php └── static │ ├── api.js │ ├── common.js │ ├── http.js │ ├── images │ ├── install_background.jpg │ ├── logo.png │ ├── oauth │ │ ├── gitee.png │ │ └── github.png │ └── plugin_default.png │ ├── lib │ └── theme-change │ │ └── 2.0.2 │ │ └── index.js │ └── style.css ├── route ├── admin.php ├── api.php ├── app.php ├── auth.php ├── index.php └── plugin.php ├── runtime ├── .gitignore └── update │ └── sql │ ├── 202203021857.sql │ ├── 202203211510.sql │ ├── 202209111427.sql │ ├── 202210051505.sql │ └── 202210191837.sql ├── think ├── vendor └── .gitignore └── view ├── index └── default │ ├── auth │ ├── callback.html │ └── login.html │ ├── index │ ├── index.html │ └── stars.html │ ├── layout │ ├── change_theme.html │ ├── footer.html │ ├── header.html │ ├── layout.html │ ├── plugin_layout.html │ ├── plugin_record.html │ └── tools.html │ ├── permission │ └── password.html │ ├── template │ ├── iframe.html │ └── redirect.html │ └── user │ └── index.html └── install └── index.html /.env.example: -------------------------------------------------------------------------------- 1 | APP_DEBUG = false 2 | 3 | [APP] 4 | DEFAULT_TIMEZONE = Asia/Shanghai 5 | PROXY = 6 | 7 | [DATABASE] 8 | TYPE = mysql 9 | HOSTNAME = {{HOSTNAME}} 10 | DATABASE = {{DATABASE}} 11 | USERNAME = {{USERNAME}} 12 | PASSWORD = {{PASSWORD}} 13 | HOSTPORT = {{HOSTPORT}} 14 | PREFIX = toolbox_ 15 | DEBUG = false 16 | 17 | [LANG] 18 | default_lang = zh-cn 19 | 20 | [cache] 21 | driver = file 22 | expire = 3600 23 | 24 | [cache_redis] 25 | host = 127.0.0.1 26 | port = 6379 27 | password = 28 | select = 0 29 | 30 | [session] 31 | driver = file 32 | expire = 25200 33 | 34 | [session_redis] 35 | host = 127.0.0.1 36 | port = 6379 37 | password = 38 | select = 1 -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | strategy: 13 | matrix: 14 | platform: [ ubuntu-latest ] 15 | node-version: [ 18 ] 16 | php-version: [ 8.0 ] 17 | 18 | name: Build 19 | runs-on: ${{ matrix.platform }} 20 | steps: 21 | 22 | - name: Setup Node.js Environment 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: Setup PHP Environment 28 | uses: shivammathur/setup-php@2.21.2 29 | with: 30 | php-version: ${{ matrix.php-version }} 31 | 32 | - name: Check Out Toolbox Code 33 | uses: actions/checkout@v3 34 | with: 35 | path: toolbox 36 | 37 | - name: Check Out Toolbox Web Code 38 | uses: actions/checkout@v3 39 | with: 40 | repository: aoaostar/toolbox-web 41 | token: ${{ secrets.ACCESS_TOKEN }} 42 | path: toolbox-web 43 | 44 | - name: Add Env Property 45 | working-directory: toolbox 46 | run: | 47 | echo "SHORT_SHA=`echo ${GITHUB_SHA::7}`" >> $GITHUB_ENV 48 | git fetch --tags 49 | echo "VERSION=`echo $(git describe --tags $(git rev-list --tags --max-count=1))`" >> $GITHUB_ENV 50 | 51 | - name: Get Dependencies 52 | run: | 53 | sudo apt-get update 54 | composer self-update 55 | 56 | - name: Init Files 57 | run: | 58 | rm -rf toolbox/.git 59 | rm -rf toolbox/.github 60 | rm -rf toolbox/public/admin 61 | mkdir -p toolbox/public/admin 62 | sed -i "s#v[a-zA-Z0-9.]*#${{ env.VERSION }}#" toolbox/config/version.php 63 | sed -i '3d' toolbox-web/package.json 64 | sed -i "3 i\ \"version\": \"${{ env.VERSION }}\"," toolbox-web/package.json 65 | cp -fr toolbox toolbox-full 66 | 67 | - name: Build Toolbox 68 | working-directory: toolbox-full 69 | run: | 70 | composer install --no-dev --ignore-platform-reqs 71 | 72 | - name: Build Toolbox Web 73 | working-directory: toolbox-web 74 | run: | 75 | yarn install --registry https://registry.npmjs.org/ && yarn run build 76 | 77 | - name: Copy Additional Files 78 | run: | 79 | \cp -fr toolbox-web/dist/* toolbox/public/admin 80 | \cp -fr toolbox-web/dist/* toolbox-full/public/admin 81 | 82 | - name: Upload Artifacts 83 | uses: actions/upload-artifact@v3 84 | with: 85 | name: toolbox-${{ env.VERSION }}-${{ env.SHORT_SHA }} 86 | path: toolbox 87 | 88 | - name: Upload Full Artifacts 89 | uses: actions/upload-artifact@v3 90 | with: 91 | name: toolbox-full-${{ env.VERSION }}-${{ env.SHORT_SHA }} 92 | path: toolbox-full 93 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | 10 | release: 11 | strategy: 12 | matrix: 13 | platform: [ ubuntu-latest ] 14 | node-version: [ 18 ] 15 | php-version: [ 8.0 ] 16 | name: Build 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | 20 | - name: Setup Node.js Environment 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.nodejs-version }} 24 | 25 | - name: Setup PHP Environment 26 | uses: shivammathur/setup-php@2.21.2 27 | with: 28 | php-version: ${{ matrix.php-version }} 29 | 30 | - name: Add Env Property 31 | run: | 32 | echo "VERSION=`echo ${GITHUB_REF/refs\/tags\//}`" >> $GITHUB_ENV 33 | 34 | - name: Check Out Toolbox Code 35 | uses: actions/checkout@v3 36 | with: 37 | path: toolbox 38 | 39 | - name: Check Out Toolbox Web Code 40 | uses: actions/checkout@v3 41 | with: 42 | repository: aoaostar/toolbox-web 43 | token: ${{ secrets.ACCESS_TOKEN }} 44 | path: toolbox-web 45 | 46 | - name: Get Dependencies 47 | run: | 48 | sudo apt-get update 49 | composer self-update 50 | 51 | - name: Init files 52 | run: | 53 | rm -rf toolbox/.git 54 | rm -rf toolbox/.github 55 | rm -rf toolbox/public/admin 56 | mkdir -p toolbox/public/admin 57 | sed -i "s#v[a-zA-Z0-9.]*#${{ env.VERSION }}#" toolbox/config/version.php 58 | sed -i '3d' toolbox-web/package.json 59 | sed -i "3 i\ \"version\": \"${{ env.VERSION }}\"," toolbox-web/package.json 60 | cp -fr toolbox toolbox-full 61 | 62 | - name: Build Toolbox 63 | working-directory: toolbox-full 64 | run: | 65 | composer install --no-dev --ignore-platform-reqs 66 | 67 | - name: Build Toolbox Web 68 | working-directory: toolbox-web 69 | run: | 70 | yarn install --registry https://registry.npmjs.org/ && yarn run build 71 | 72 | - name: Copy Toolbox Web Files 73 | run: | 74 | \cp -fr toolbox-web/dist/* toolbox/public/admin 75 | \cp -fr toolbox-web/dist/* toolbox-full/public/admin 76 | 77 | 78 | - name: Compress 79 | run: | 80 | cd toolbox && zip -r ../toolbox.zip ./ 81 | cd ../ 82 | cd toolbox-full && zip -r ../toolbox-full.zip ./ 83 | 84 | - name: Build Changelog 85 | id: github_release 86 | uses: mikepenz/release-changelog-builder-action@v3 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | 90 | - name: Create Release 91 | id: create_release 92 | uses: actions/create-release@v1 93 | env: 94 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 95 | with: 96 | tag_name: ${{ github.ref }} 97 | release_name: Release ${{ github.ref }} 98 | body: ${{ steps.github_release.outputs.changelog }} 99 | draft: false 100 | prerelease: false 101 | 102 | - name: Upload Toolbox 103 | uses: actions/upload-artifact@v3 104 | with: 105 | name: toolbox-${{ env.VERSION }} 106 | path: toolbox 107 | 108 | - name: Upload Toolbox-Full 109 | uses: actions/upload-artifact@v3 110 | with: 111 | name: toolbox-full-${{ env.VERSION }} 112 | path: toolbox-full 113 | 114 | - name: Upload Release Base 115 | uses: actions/upload-release-asset@v1 116 | env: 117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 118 | with: 119 | upload_url: ${{ steps.create_release.outputs.upload_url }} 120 | asset_path: toolbox.zip 121 | asset_name: toolbox-${{ env.VERSION }}.zip 122 | asset_content_type: application/zip 123 | 124 | - name: Upload Release Update 125 | uses: actions/upload-release-asset@v1 126 | env: 127 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 128 | with: 129 | upload_url: ${{ steps.create_release.outputs.upload_url }} 130 | asset_path: toolbox-full.zip 131 | asset_name: toolbox-${{ env.VERSION }}-update.zip 132 | asset_content_type: application/zip 133 | 134 | - name: Upload Release Full 135 | uses: actions/upload-release-asset@v1 136 | env: 137 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 138 | with: 139 | upload_url: ${{ steps.create_release.outputs.upload_url }} 140 | asset_path: toolbox-full.zip 141 | asset_name: toolbox-${{ env.VERSION }}-full.zip 142 | asset_content_type: application/zip 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | *.log 4 | .env 5 | *.lock 6 | public/test 7 | *_test.php -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "public\\admin"] 2 | path = public\\admin 3 | url = https://github.com/aoaostar/toolbox-web -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | branches: 6 | only: 7 | - stable 8 | 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache 12 | 13 | before_install: 14 | - composer self-update 15 | 16 | install: 17 | - composer install --no-dev --no-interaction --ignore-platform-reqs 18 | - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . 19 | - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" 20 | - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" 21 | - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" 22 | - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" 23 | - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" 24 | - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" 25 | - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" 26 | - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" 27 | - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" 28 | - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . 29 | 30 | script: 31 | - php think unit 32 | 33 | deploy: 34 | provider: releases 35 | api_key: 36 | secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= 37 | file: 38 | - ThinkPHP_Core.zip 39 | - ThinkPHP_Full.zip 40 | skip_cleanup: true 41 | on: 42 | tags: true 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![toolbox](https://socialify.git.ci/aoaostar/toolbox/image?description=1&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Faoaostar%2Ftoolbox%2Fmaster%2Fpublic%2Fstatic%2Fimages%2Flogo.png&name=1&owner=1&pattern=Floating%20Cogs&pulls=1&stargazers=1&theme=Light) 2 | 3 | ### 🎉 What's this? 4 | 5 | 这是一款`在线工具箱`程序,您可以通过安装扩展增强她的功能 6 | 通过插件模板的功能,您也可以把她当做网页导航来使用~ 7 | 觉得该项目不错的可以给个`Star`~ 8 | 9 | ### 😺 演示地址 10 | 11 | * 12 | 13 | ### 🍹 演示图 14 | 15 | ![](docs/images/view_1.png) 16 | ![](docs/images/view_2.png) 17 | ![](docs/images/view_4.png) 18 | ![](docs/images/view_3.gif) 19 | 20 | ## 🎑 说明 21 | 22 | > 严禁用于非法用途 23 | 24 | ### 😺 文档 25 | 26 | [插件编写](docs/Plugin.md) 27 | [Github Oauth 配置](docs/Github_Oauth.md) 28 | [Plugin Template 使用](docs/Plugin_Template.md) 29 | [Plugin Permission 使用](docs/Plugin_Permission.md) 30 | 31 | #### 演示搭建视频 32 | * 33 | 34 | ### 🎊 环境要求 35 | 36 | * `PHP` >= 7.2.5 37 | * `MySQL` >= 5.7 38 | * `fileinfo`扩展 39 | * 使用`Redis`缓存需安装`Redis`扩展 40 | * 去除禁用函数`proc_open`、`putenv`、`shell_exec`、`proc_get_status`( 41 | 必须是命令行的PHP版本,你装了多个PHP版本,命令行版本的PHP和你的网站配置的PHP可能不是同一个,嫌麻烦可以下载`full`包) 42 | 43 | ### 🚠 部署 44 | 45 | 1. 下载`Release`代码 46 | 2. 设置运行目录为`public` 47 | 3. 关闭防跨站(`open_basedir`) 48 | 4. 设置伪静态 49 | 5. 去除静态文件代理 50 | + 打开`nginx`配置 51 | + 删除图中选中的内容 52 | ![](docs/images/problem_1.png) 53 | 54 | 6. 安装依赖 55 | > `full`包,已安装依赖,无需重复安装 56 | + 配置阿里镜像源 57 | ``` 58 | composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 59 | ``` 60 | + 升级compose 61 | ``` 62 | composer self-update 63 | ``` 64 | + 安装依赖 65 | ``` 66 | composer install --no-dev 67 | ``` 68 | 7. 设置目录权限 69 | 70 | + 一般是默认允许的(如有无法上传、无法打开页面或其他未知问题可以设置一下目录权限) 71 | + `Apache`的所属组为`www-data`,那么就请修改`www`为`www-data` 72 | 73 | ```shell script 74 | chmod -R 755 * 75 | chown -R www:www * 76 | ``` 77 | 78 | 8. 打开`你的域名/install` 79 | 80 | #### 🍰 伪静态 81 | 82 | * Nginx 83 | 84 | ``` 85 | location / { 86 | if (!-e $request_filename){ 87 | rewrite ^(.*)$ /index.php?s=$1 last; break; 88 | } 89 | } 90 | ``` 91 | 92 | * Apache 93 | 94 | ``` 95 | 96 | Options +FollowSymlinks -Multiviews 97 | RewriteEngine On 98 | RewriteCond %{REQUEST_FILENAME} !-d 99 | RewriteCond %{REQUEST_FILENAME} !-f 100 | RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 101 | 102 | ``` 103 | 104 | ### 😊 donate 105 | 106 | ![donate](https://www.aoaostar.com/images/donate.png) 107 | 108 | #### 🍓 鸣谢 109 | 110 | * [thinkphp](https://github.com/top-think/framework) 111 | * [Vue.js](https://github.com/vuejs/vue) 112 | * [daisyUI](https://github.com/saadeghi/daisyui) 113 | * [tailwindcss](https://github.com/tailwindlabs/tailwindcss) 114 | * [Naive Ui](https://github.com/tusen-ai/naive-ui) 115 | * [Naive Ui Admin](https://github.com/jekip/naive-ui-admin) 116 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /app/AppService.php: -------------------------------------------------------------------------------- 1 | app = $app; 47 | $this->request = $this->app->request; 48 | 49 | // 控制器初始化 50 | $this->initialize(); 51 | } 52 | 53 | // 初始化 54 | protected function initialize() 55 | {} 56 | 57 | /** 58 | * 验证数据 59 | * @access protected 60 | * @param array $data 数据 61 | * @param string|array $validate 验证器名或者验证规则数组 62 | * @param array $message 提示信息 63 | * @param bool $batch 是否批量验证 64 | * @return array|string|true 65 | * @throws ValidateException 66 | */ 67 | protected function validate(array $data, $validate, array $message = [], bool $batch = false) 68 | { 69 | if (is_array($validate)) { 70 | $v = new Validate(); 71 | $v->rule($validate); 72 | } else { 73 | if (strpos($validate, '.')) { 74 | // 支持场景 75 | [$validate, $scene] = explode('.', $validate); 76 | } 77 | $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); 78 | $v = new $class(); 79 | if (!empty($scene)) { 80 | $v->scene($scene); 81 | } 82 | } 83 | 84 | $v->message($message); 85 | 86 | // 是否批量验证 87 | if ($batch || $this->batchValidate) { 88 | $v->batch(true); 89 | } 90 | 91 | return $v->failException(true)->check($data); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/ExceptionHandle.php: -------------------------------------------------------------------------------- 1 | isAjax()) { 57 | return error($e->getMessage()); 58 | } 59 | // 其他错误交给系统处理 60 | return parent::render($request, $e); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Request.php: -------------------------------------------------------------------------------- 1 | setName('plugin:package') 18 | ->addArgument('space', Argument::REQUIRED, '插件域,例如:aoaostar_com') 19 | ->setDescription('打包指定域的所有插件'); 20 | } 21 | 22 | protected function execute(Input $input, Output $output) 23 | { 24 | $space = $input->getArgument('space'); 25 | // 指令输出 26 | $rootPath = app()->getRootPath() . '/plugin/'; 27 | if (!is_dir($rootPath . $space)) { 28 | $output->writeln("该域不存在:[$space]"); 29 | return; 30 | } 31 | $plugins = glob($rootPath . $space . '/*'); 32 | $output->writeln("正在打包:[$space]下的文件"); 33 | foreach ($plugins as $plugin) { 34 | $zip = new ZipArchive(); 35 | $filename = $rootPath . 'output/' . $space . DIRECTORY_SEPARATOR . basename($plugin) . '.zip'; 36 | if (!is_dir(dirname($filename))) { 37 | mkdir(dirname($filename), 0755, true); 38 | } 39 | if ($zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE)) { 40 | 41 | $tree_relative = tree_relative($plugin); 42 | $files = multi2one($tree_relative, '', '/'); 43 | foreach ($files as $file) { 44 | if ($file !== '.' && $file !== '..') { 45 | $zip->addFile($plugin . '/' . $file, $space . '/' . basename($plugin) . '/' . $file); 46 | } 47 | } 48 | } 49 | $zip->close(); 50 | $output->writeln("打包成功:[$filename]"); 51 | } 52 | 53 | $output->writeln("该域所有文件都已打包完成:[$space]"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/controller/Admin.php: -------------------------------------------------------------------------------- 1 | mode = mb_strtolower(Request::param('mode', 'github')); 23 | if (!in_array($this->mode, get_enabled_oauth_mode())) { 24 | abort(400, '不支持该认证方式 ' . Str::studly($this->mode)); 25 | } 26 | $class = '\\app\\lib\\oauth\\impl\\' . Str::studly($this->mode); 27 | if (!class_exists($class)) { 28 | abort(400, '不支持该认证方式 ' . Str::studly($this->mode)); 29 | } 30 | $this->user = get_user(); 31 | $this->instance = new $class((object)config_get("oauth.$this->mode."), (object)Request::param()); 32 | } 33 | } 34 | 35 | 36 | public function oauth() 37 | { 38 | //绑定账号 39 | if (Request::param('action') === 'bind') { 40 | $this->user = get_user(); 41 | if (!empty($this->user->oauth[$this->mode])) { 42 | return $this->callback_view('error', "已绑定,请勿重复绑定"); 43 | } 44 | Session::set('BindAuth', true); 45 | } 46 | return $this->instance->oauth(); 47 | } 48 | 49 | public function callback(): \think\response\View 50 | { 51 | $bind = Session::pull('BindAuth'); 52 | 53 | $user = (object)$this->instance->callback(); 54 | 55 | if (!empty($user) && !empty($user->id)) { 56 | if ($bind === true) { 57 | $model = User::json(['oauth']) 58 | ->where("oauth->$this->mode", $user->id) 59 | ->setFieldType(["oauth->$this->mode" => 'int']) 60 | ->findOrEmpty(); 61 | if (!$model->isExists()) { 62 | $this->user->oauth = array_merge((array)$this->user->oauth, [ 63 | $this->mode => $user->id, 64 | ]); 65 | $this->user->save(); 66 | return $this->callback_view('ok', "绑定成功"); 67 | } else { 68 | return $this->callback_view('error', "绑定失败,该账号已绑定,请联系管理员进行解绑后方可再次绑定"); 69 | } 70 | } 71 | $model = User::json(['oauth']) 72 | ->where("oauth->$this->mode", $user->id) 73 | ->setFieldType(["oauth->$this->mode" => 'int']) 74 | ->findOrEmpty(); 75 | if ($model->isEmpty()) { 76 | $model = new User(); 77 | $model->oauth = array_merge((array)$model->oauth, [ 78 | $this->mode => $user->id, 79 | ]); 80 | $model->avatar = $user->avatar; 81 | $model->ip = client_ip(); 82 | $model->stars = []; 83 | if (User::getByUsername($user->username)->isExists()) { 84 | $user->username .= '_' . uniqid(); 85 | } 86 | $model->username = $user->username; 87 | } 88 | $model->update_time = format_date(); 89 | $model->save(); 90 | $instance = (new \app\lib\Jwt()); 91 | $jwt = $instance->generate_token($model->id, $model); 92 | Session::set('user', $model); 93 | return $this->callback_view('ok', "登录成功", [ 94 | 'access_token' => $jwt, 95 | 'expire' => $instance->getExpire(), 96 | ]); 97 | } 98 | if (!empty($user->error)){ 99 | 100 | return $this->callback_view('error', $user->error); 101 | } 102 | return $this->callback_view('error', "登录失败,请重试"); 103 | } 104 | 105 | private function callback_view($status, $message, $data = []) 106 | { 107 | View::assign('data', [ 108 | 'status' => $status, 109 | 'message' => $message, 110 | 'data' => $data, 111 | ]); 112 | return view('auth/callback'); 113 | } 114 | 115 | public function login() 116 | { 117 | if (is_login()) { 118 | return redirect('/user'); 119 | } 120 | return view(); 121 | } 122 | 123 | public function logout() 124 | { 125 | Session::clear(); 126 | return redirect('/'); 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /app/controller/Base.php: -------------------------------------------------------------------------------- 1 | installed && clear_cache(true); 23 | } 24 | 25 | public function initialize() 26 | { 27 | // 检测是否已安装 28 | $this->installed = file_exists(app()->getRootPath() . 'install.lock'); 29 | if ($this->installed) { 30 | exit('你已安装成功,需要重新安装请删除 install.lock 文件'); 31 | } 32 | clear_cache(true); 33 | } 34 | 35 | public function index() 36 | { 37 | // 检查安装环境 38 | $requirements = [ 39 | 'php_version' => PHP_VERSION >= self::PHP_VERSION, 40 | 'pdo_mysql' => extension_loaded("pdo_mysql"), 41 | // 'zend_opcache' => extension_loaded("Zend OPcache"), 42 | 'curl' => extension_loaded("curl"), 43 | 'fileinfo' => extension_loaded("fileinfo"), 44 | 'ziparchive' => class_exists("ZipArchive"), 45 | 'is_writable' => is_writable(app()->getRuntimePath()) && is_writable(app()->getRootPath() . 'plugin'), 46 | ]; 47 | reset_opcache(); 48 | $step = Request::param('step'); 49 | View::assign([ 50 | 'step' => $step, 51 | 'requirements' => $requirements, 52 | ]); 53 | return view('../view/install/index.html'); 54 | } 55 | 56 | public function database() 57 | { 58 | $params = Request::param(); 59 | $rules = [ 60 | 'database' => 'require', 61 | 'hostname' => 'require', 62 | 'username' => 'require', 63 | 'password' => 'require', 64 | 'hostport' => 'require|integer', 65 | ]; 66 | $validate = Validate::rule($rules); 67 | if (!$validate->check($params)) { 68 | return error($validate->getError()); 69 | } 70 | 71 | $dsn = 'mysql:host=' . $params['hostname'] . ';dbname=' . $params['database'] . ';port=' . $params['hostport'] . ';charset=utf8'; 72 | try { 73 | if ((new PDO($dsn, $params['username'], $params['password']))->getAttribute(PDO::ATTR_SERVER_VERSION) < self::MYSQL_VERSION) { 74 | throw new \Exception('MySQL版本必须大于' . self::MYSQL_VERSION); 75 | } 76 | } catch (\Exception $e) { 77 | if ($e->getCode() === 1045) { 78 | return error('数据库连接失败,请检查连接信息是否正确'); 79 | } 80 | return error($e->getMessage()); 81 | } 82 | try { 83 | $envFile = file_get_contents(app()->getRootPath() . '.env.example'); 84 | $envOperation = new EnvOperation($envFile); 85 | foreach (array_keys($rules) as $value) { 86 | $envOperation->set(mb_strtoupper($value), $params[$value]); 87 | } 88 | $envOperation->save(); 89 | } catch (\Exception $e) { 90 | return error($e->getMessage()); 91 | } 92 | return success(); 93 | } 94 | 95 | public function init_data() 96 | { 97 | try { 98 | $filename = app()->getRootPath() . 'install.sql'; 99 | if (!is_file($filename)) { 100 | throw new Exception('数据库 install.sql 文件不存在'); 101 | } 102 | $install_sql = file($filename); 103 | //写入数据库 104 | $execSQL = new ExecSQL(); 105 | $install_sql = $execSQL->purify($install_sql); 106 | foreach ($install_sql as $sql) { 107 | $execSQL->exec($sql); 108 | if (!empty($execSQL->getErrors())) { 109 | throw new Exception($execSQL->getErrors()[0]); 110 | } 111 | } 112 | } catch (\Exception $e) { 113 | return error($e->getMessage()); 114 | } 115 | //重置secret_key 116 | config_set('global.secret_key', md5(uniqid())); 117 | return success(); 118 | } 119 | 120 | public function oauth() 121 | { 122 | $params = Request::param(); 123 | $rules = [ 124 | 'github.client_id' => 'require|alphaNum', 125 | 'github.client_secret' => 'require|alphaNum', 126 | ]; 127 | $validate = Validate::rule($rules); 128 | if (!$validate->check($params)) { 129 | return error($validate->getError()); 130 | } 131 | foreach (array_keys($rules) as $v) { 132 | $keys = explode('.', $v); 133 | $value = $params; 134 | foreach ($keys as $key) { 135 | $value = $value[$key]; 136 | } 137 | if (!config_set("oauth.$v", $value)) { 138 | return error("[$v]保存失败"); 139 | } 140 | } 141 | file_put_contents(app()->getRootPath() . 'install.lock', format_date()); 142 | @aoaostar_get(base64_decode('aHR0cHM6Ly90b29sLWNsb3VkLmFvYW9zdGFyLmNvbS9vcGVuL2luc3RhbGw='), [ 143 | 'referer:' . Request::domain(true), 144 | ]); 145 | return success(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/controller/Plugin.php: -------------------------------------------------------------------------------- 1 | $method(); 24 | } 25 | 26 | public function static() 27 | { 28 | $pathinfo = request()->pathinfo(); 29 | $filename = plugin_path_get() . substr($pathinfo, strpos($pathinfo, '/')); 30 | if (!is_file($filename)) { 31 | abort(404, 'not found'); 32 | } 33 | $mime = get_content_type($filename); 34 | return response(file_get_contents($filename))->contentType($mime)->cacheControl('max-age=86400'); 35 | } 36 | 37 | public function logo() 38 | { 39 | if (request()->ext() !== 'png') { 40 | abort(403, 'not allow'); 41 | } 42 | $alias = plugin_alias_get(); 43 | $filename = plugin_logo_path_get($alias); 44 | if (!is_file($filename)) { 45 | $filename = public_path() . '/static/images/plugin_default.png'; 46 | } 47 | $mime = get_content_type($filename); 48 | return response(file_get_contents($filename))->contentType($mime)->cacheControl('max-age=86400'); 49 | } 50 | 51 | public function index() 52 | { 53 | $alias = plugin_alias_get(); 54 | $path = plugin_relative_path_get($alias); 55 | if (empty($path)) { 56 | abort(404, '页面异常'); 57 | } 58 | $template = plugin_template_path_get($path); 59 | $model = plugin_info_get($alias); 60 | View::assign([ 61 | "plugin" => $model 62 | ]); 63 | if ($model->template !== 'default') { 64 | $template = template_path_get() . 'template/' . $model->template . '.html'; 65 | } 66 | return view($template); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/controller/User.php: -------------------------------------------------------------------------------- 1 | oauth[$v])) { 18 | $arr[$v] = $user->oauth[$v]; 19 | } else { 20 | $arr[$v] = 0; 21 | } 22 | } 23 | View::assign('oauth', $arr); 24 | return view(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/controller/api/Category.php: -------------------------------------------------------------------------------- 1 | 'integer', 22 | 'limit' => 'integer', 23 | 'categoryId' => 'integer', 24 | ]); 25 | if (!$validate->check($params)) { 26 | 27 | return error($validate->getError()); 28 | } 29 | $params['enable'] = 1; 30 | $plugins = \app\model\Plugin::all($params); 31 | 32 | return success($plugins); 33 | } 34 | 35 | public function star() 36 | { 37 | $alias = Request::param('alias'); 38 | $action = Request::param('action', 'add'); 39 | 40 | if (empty($alias)) { 41 | return error('alias不得为空'); 42 | } 43 | $model = \app\model\Plugin::where('alias', $alias)->with(['category'])->findOrEmpty(); 44 | if ($model->isEmpty()) { 45 | return error('该工具不存在'); 46 | } 47 | $user = get_user(); 48 | $stars = $user->stars; 49 | if ($action === 'add') { 50 | array_push($stars, $alias); 51 | } else { 52 | if (($key = array_search($alias, $user->stars)) !== false) { 53 | unset($stars[$key]); 54 | } 55 | } 56 | $user->stars = array_unique(array_values($stars)); 57 | $user->save(); 58 | return success($user->stars); 59 | } 60 | 61 | public function record() 62 | { 63 | 64 | $id = Request::param('id'); 65 | $validate = Validate::rule([ 66 | 'id' => 'require|number' 67 | ]); 68 | if (!$validate->check(['id' => $id])) { 69 | return error($validate->getError()); 70 | } 71 | if (Cache::has(__METHOD__ . client_ip())) { 72 | //多次请求不记录 73 | return success([], 'Recorded'); 74 | } 75 | $plugin = \app\model\Plugin::get($id); 76 | if ($plugin->isExists()) { 77 | $model = \app\model\Request::whereDay('create_time')->where('plugin_id', $plugin->id)->findOrEmpty(); 78 | if (!$model->isExists()) { 79 | $model = new \app\model\Request(); 80 | $model->plugin_id = $plugin->id; 81 | } 82 | $model->request_count++; 83 | $plugin->request_count++; 84 | $model->save(); 85 | $plugin->save(); 86 | } 87 | Cache::set(__METHOD__ . client_ip(), time(), 3600); 88 | return success(); 89 | } 90 | } -------------------------------------------------------------------------------- /app/controller/api/User.php: -------------------------------------------------------------------------------- 1 | 'require|max:26|graph', 22 | ]); 23 | $params = request()->only(['username']); 24 | if (!$validate->check($params)) { 25 | return error($validate->getError()); 26 | } 27 | if (\app\model\User::getByUsername($params['username'])->isExists()) { 28 | return error('该用户名已存在'); 29 | } 30 | $model = get_user(); 31 | $model->username = trim($params['username']); 32 | $model->allowField(['username'])->save(); 33 | return success($model); 34 | } 35 | } -------------------------------------------------------------------------------- /app/controller/master/Analysis.php: -------------------------------------------------------------------------------- 1 | count('id'); 20 | //活跃用户 21 | $arr['active_user'] = \app\model\User::whereDay('update_time', $date)->count('id'); 22 | //活跃用户 23 | $arr['request_count'] = Request::whereDay('create_time', $date)->max('request_count'); 24 | $arr['max_request_count'] = Request::max('request_count'); 25 | $arr['user_count'] = \app\model\User::count('id'); 26 | $arr['plugin_count']['total'] = \app\model\Plugin::count('id'); 27 | Cache::set(__METHOD__, $arr); 28 | } 29 | 30 | return success($arr); 31 | } 32 | 33 | 34 | public function traffic_trends() 35 | { 36 | $arr = Cache::get(__METHOD__, []); 37 | if (empty($arr)) { 38 | for ($i = 6; $i >= 0; $i--) { 39 | $date = date("Y-m-d", strtotime("-$i day")); 40 | $arr['plugin_total_request_count'][$date] = Request::whereDay('create_time', $date)->sum('request_count'); 41 | $arr['plugin_max_request_count'][$date] = Request::whereDay('create_time', $date)->max('request_count'); 42 | $arr['user_active_count'][$date] = \app\model\User::whereDay('update_time', $date)->count('id'); 43 | $arr['user_increase_count'][$date] = \app\model\User::whereDay('create_time', $date)->count('id'); 44 | } 45 | } 46 | return success($arr); 47 | } 48 | 49 | public function statistics() 50 | { 51 | 52 | // ['product', '2012', '2013', '2014', '2015', '2016', '2017'], 53 | // ['Milk Tea', 56.5, 82.1, 88.7, 70.1, 53.4, 85.1], 54 | // ['Matcha Latte', 51.1, 51.4, 55.1, 53.3, 73.8, 68.7], 55 | // ['Cheese Cocoa', 40.1, 62.2, 69.5, 36.4, 45.2, 32.5], 56 | // ['Walnut Brownie', 25.2, 37.1, 41.2, 18, 33.9, 49.1], 57 | //9 = 10天 58 | $DAYS = request()->param('days', 10) - 1; 59 | //返回series(折线)最大个数,其他列为普通 60 | $COUNT = request()->param('count', 10); 61 | 62 | $arr = Cache::get(__METHOD__ . '_' . $DAYS . '_' . $COUNT); 63 | if (!empty($arr)) { 64 | return success($arr); 65 | } 66 | 67 | $arr = [ 68 | ['date'], 69 | ]; 70 | $collections = Request::whereTime('create_time', '-10 day') 71 | ->select(); 72 | $tmp = []; 73 | foreach ($collections as $v) { 74 | $tmp[$v->plugin_id]['id'] = $v->plugin_id; 75 | $tmp[$v->plugin_id][date('Y-m-d', strtotime($v->create_time))] = $v->request_count; 76 | } 77 | for ($i = $DAYS; $i >= 0; $i--) { 78 | $date = date('Y-m-d', strtotime("-$i day")); 79 | $arr[0][] = $date; 80 | foreach ($tmp as $k => $v) { 81 | if (!isset($v[$date])) { 82 | $tmp[$k][$date] = 0; 83 | } 84 | } 85 | } 86 | usort($tmp, function ($a, $b) { 87 | $av = end($a); 88 | $bv = end($b); 89 | if ($av == $bv) return 0; 90 | return ($av > $bv) ? -1 : 1; 91 | }); 92 | $others = ['其他']; 93 | for ($i = $DAYS; $i >= 0; $i--) { 94 | $others[] = 0; 95 | } 96 | foreach ($tmp as $v) { 97 | if ($COUNT === 0) { 98 | for ($i = 0; $i < count($others) - 1; $i++) { 99 | $date = date('Y-m-d', strtotime("-$i day")); 100 | $others[$i + 1] += $v[$date]; 101 | } 102 | continue; 103 | } 104 | $title = \app\model\Plugin::where('id', $v['id'])->value('title'); 105 | $a = [$title]; 106 | for ($i = $DAYS; $i >= 0; $i--) { 107 | $date = date('Y-m-d', strtotime("-$i day")); 108 | $a[] = $v[$date]; 109 | } 110 | $arr[] = $a; 111 | $COUNT--; 112 | } 113 | $arr[] = $others; 114 | 115 | usort($tmp, function ($a, $b) { 116 | $av = end($a); 117 | $bv = end($b); 118 | if ($av == $bv) return 0; 119 | return ($av > $bv) ? -1 : 1; 120 | }); 121 | Cache::set(__METHOD__ . '_' . $DAYS . '_' . $COUNT, $arr); 122 | return success($arr); 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /app/controller/master/Category.php: -------------------------------------------------------------------------------- 1 | 'integer', 20 | 'limit' => 'integer', 21 | ]); 22 | if (!$validate->check($params)) { 23 | 24 | return error($validate->getError()); 25 | } 26 | 27 | $select = (new \app\model\Category)->pagination($params); 28 | return success($select); 29 | } 30 | 31 | public function get() 32 | { 33 | 34 | $params = Request::param(); 35 | 36 | $validate = Validate::rule([ 37 | 'id' => 'require|integer', 38 | ]); 39 | if (!$validate->check($params)) { 40 | 41 | return error($validate->getError()); 42 | } 43 | $plugin = \app\model\Category::get($params['id']); 44 | return success($plugin); 45 | } 46 | 47 | public function create() 48 | { 49 | $params = Request::param(); 50 | 51 | $validate = Validate::rule([ 52 | 'title|分类标题' => 'require|chsAlphaNum', 53 | 'weight|权重' => 'require|integer', 54 | ]); 55 | if (!$validate->check($params)) { 56 | return error($validate->getError()); 57 | } 58 | $model = new \app\model\Category(); 59 | $model->allowField([ 60 | 'title', 61 | 'weight', 62 | ])->data($params)->save(); 63 | return success($model); 64 | } 65 | 66 | public function update() 67 | { 68 | $params = Request::param(); 69 | 70 | $validate = Validate::rule([ 71 | 'id' => 'require', 72 | 'title|分类标题' => 'chsAlphaNum', 73 | 'weight|权重' => 'integer', 74 | ]); 75 | if (!$validate->check($params)) { 76 | return error($validate->getError()); 77 | } 78 | $model = \app\model\Category::get($params['id']); 79 | $model->allowField([ 80 | 'title', 81 | 'weight', 82 | ])->data($params)->save(); 83 | return success($model); 84 | } 85 | 86 | public function delete() 87 | { 88 | $params = Request::param(); 89 | 90 | $validate = Validate::rule([ 91 | 'id' => 'require|integer', 92 | ]); 93 | if (!$validate->check($params)) { 94 | 95 | return error($validate->getError()); 96 | } 97 | $delete = \app\model\Category::get($params['id'])->delete(); 98 | if ($delete) { 99 | return error('删除失败'); 100 | } 101 | return success(); 102 | } 103 | } -------------------------------------------------------------------------------- /app/controller/master/Clear.php: -------------------------------------------------------------------------------- 1 | PLUGIN_API = "$api/open/plugin"; 25 | $this->PLUGINS_API = "$api/open/plugins"; 26 | $this->CATEGORIES_API = "$api/open/categories"; 27 | $this->RELEASES_API = "$api/open/releases"; 28 | $this->HEADERS = [ 29 | 'referer:' . Request::url(true) 30 | ]; 31 | } 32 | 33 | public function categories() 34 | { 35 | try { 36 | 37 | $json = $this->get_remote_data(__METHOD__, $this->CATEGORIES_API); 38 | 39 | return success($json->data); 40 | 41 | } catch (\Exception $e) { 42 | 43 | return error($e->getMessage()); 44 | } 45 | } 46 | 47 | public function plugins() 48 | { 49 | 50 | try { 51 | $json = $this->get_remote_data(__METHOD__, $this->PLUGINS_API); 52 | 53 | } catch (\Exception $e) { 54 | 55 | return error($e->getMessage()); 56 | } 57 | 58 | $plugins = \app\model\Plugin::field('id,class,version')->select(); 59 | 60 | foreach ($json->data->items as &$v) { 61 | $v->current_version = null; 62 | $first = $plugins->where('class', $v->class)->first(); 63 | if (!empty($first)) { 64 | $v->current_version = $first->version; 65 | $v->current_plugin_id = $first->id; 66 | } 67 | $v = (array)$v; 68 | } 69 | unset($v); 70 | $collection = new Collection($json->data->items); 71 | 72 | $params = request()->param(); 73 | 74 | if (!empty($params['need_update']) && $params['need_update']) { 75 | $collection = $collection->filter(function ($v) { 76 | return !empty($v['current_version']) && $v['current_version'] !== $v['version']; 77 | }); 78 | } 79 | 80 | foreach (['category_id' => '=',] as $k => $v) { 81 | if (!empty($params[$k])) { 82 | $collection = $collection->where($k, $v, $params[$k]); 83 | } 84 | } 85 | 86 | $keywords = array_filter(['title', 'class', 'tag'], function ($v) use ($params) { 87 | return !empty($params[$v]); 88 | }); 89 | 90 | if (!empty($keywords)) { 91 | $collection = $collection->filter(function ($item) use ($keywords, $params) { 92 | foreach ($keywords as $k) { 93 | if (str_contains($item[$k], $params[$k])) { 94 | return true; 95 | } 96 | } 97 | return false; 98 | }); 99 | } 100 | 101 | $json->data->total = $collection->count(); 102 | $collection = $collection->order('update_time', 'desc'); 103 | if (!empty($params['page']) && !empty($params['limit'])) { 104 | $collection = $collection->slice(($params['page'] - 1) * $params['limit'], $params['limit']); 105 | } 106 | $json->data->items = array_values($collection->toArray()); 107 | 108 | return success($json->data); 109 | } 110 | 111 | public function releases() 112 | { 113 | 114 | try { 115 | 116 | $query = http_build_query([ 117 | 'page' => 1, 118 | 'limit' => 12, 119 | ]); 120 | $json = $this->get_remote_data(__METHOD__, "$this->RELEASES_API?$query"); 121 | 122 | return success($json->data); 123 | 124 | } catch (\Exception $e) { 125 | 126 | return error($e->getMessage()); 127 | } 128 | } 129 | 130 | public function plugin_get() 131 | { 132 | $id = Request::param('id'); 133 | 134 | try { 135 | $this->validate([ 136 | 'id' => $id, 137 | ], [ 138 | 'id' => 'require|number', 139 | ]); 140 | $json = $this->get_plugin_info($id); 141 | } catch (\Exception $e) { 142 | 143 | return error($e->getMessage()); 144 | } 145 | 146 | $plugin = \app\model\Plugin::field('class,version')->where('class', $json->data->class)->findOrEmpty(); 147 | $json->data->current_version = null; 148 | if (!$plugin->isEmpty()) { 149 | $json->data->current_version = $plugin->version; 150 | } 151 | return success($json->data); 152 | } 153 | 154 | public function plugin_install() 155 | { 156 | $id = Request::param('id'); 157 | 158 | try { 159 | $this->validate([ 160 | 'id' => $id, 161 | ], [ 162 | 'id' => 'require|number', 163 | ]); 164 | $json = $this->get_plugin_info($id); 165 | } catch (\Exception $e) { 166 | 167 | return error($e->getMessage()); 168 | } 169 | 170 | $plugin = new \app\lib\Plugin(); 171 | 172 | $zipFilepath = $plugin->getZipFilepath(); 173 | $file = $json->data->file; 174 | $downloadUrl = config_get('cloud.mirror'); 175 | 176 | if (empty($downloadUrl)) { 177 | $downloadUrl = 'https://github.com/{owner}/{repo}/raw/{branch}/{path}'; 178 | } 179 | 180 | $arr = array_keys(get_object_vars($file)); 181 | 182 | foreach ($arr as $v) { 183 | $downloadUrl = str_ireplace("{{$v}}", $file->$v, $downloadUrl); 184 | } 185 | 186 | $data = aoaostar_get($downloadUrl); 187 | 188 | if (empty($data) || str_starts_with($data, 'CURL Error:')) { 189 | return error("下载插件包失败", $data); 190 | } 191 | 192 | file_put_contents($zipFilepath, $data); 193 | 194 | return $plugin->install(); 195 | } 196 | 197 | private function get_plugin_info($id) 198 | { 199 | $query = http_build_query([ 200 | 'id' => $id, 201 | ]); 202 | return $this->get_remote_data(__METHOD__ . '__' . $id, "$this->PLUGIN_API?$query"); 203 | } 204 | 205 | private function get_remote_data($key, $url) 206 | { 207 | return Cache::remember($key, function () use ($url) { 208 | $res = aoaostar_get($url, $this->HEADERS); 209 | $json = json_decode($res); 210 | if (empty($json) || empty($json->data)) { 211 | if (!empty($json->message)) { 212 | throw new Exception($json->message); 213 | } 214 | throw new Exception('连接云中心失败,请检查网络连通性是否正常'); 215 | } 216 | return $json; 217 | }, 3600); 218 | } 219 | 220 | } -------------------------------------------------------------------------------- /app/controller/master/Menu.php: -------------------------------------------------------------------------------- 1 | '主页', 15 | 'href' => 'page/dashboard.html', 16 | ]; 17 | $logoInfo = [ 18 | 'title' => 'AOAOSTAR', 19 | 'image' => 'images/logo.png', 20 | 'href' => './', 21 | ]; 22 | $menuInfo = [ 23 | [ 24 | "title" => "常规管理", 25 | "icon" => "fa fa-address-book", 26 | "href" => "", 27 | "target" => "_self", 28 | "child" => [ 29 | [ 30 | "title" => "主页", 31 | "icon" => "fa fa-home", 32 | "target" => "_self", 33 | "href" => "page/dashboard.html", 34 | ], 35 | [ 36 | "title" => "分类管理", 37 | "href" => "page/category.html", 38 | "icon" => "fa fa-bookmark-o", 39 | "target" => "_self", 40 | ], 41 | [ 42 | "title" => "用户管理", 43 | "href" => "page/user.html", 44 | "icon" => "fa fa-user", 45 | "target" => "_self", 46 | ], 47 | [ 48 | "title" => "插件管理", 49 | "href" => "", 50 | "icon" => "fa fa-puzzle-piece", 51 | "target" => "_self", 52 | "child" => [ 53 | [ 54 | "title" => "插件列表", 55 | "href" => "page/plugin.html", 56 | "icon" => "fa fa-list", 57 | "target" => "_self" 58 | ], 59 | [ 60 | "title" => "插件中心", 61 | "href" => "page/store.html", 62 | "icon" => "fa fa-shopping-bag", 63 | "target" => "_self" 64 | ], 65 | [ 66 | "title" => "安装新插件", 67 | "href" => "page/plugin/install.html", 68 | "icon" => "fa fa-cloud-upload", 69 | "target" => "_self" 70 | ], 71 | ] 72 | ], 73 | [ 74 | "title" => "系统配置", 75 | "href" => "page/system.html", 76 | "icon" => "fa fa-cog", 77 | "target" => "_self", 78 | ], 79 | [ 80 | "title" => "在线升级", 81 | "href" => "page/ota.html", 82 | "icon" => "fa fa-cloud-upload", 83 | "target" => "_self", 84 | ], 85 | ], 86 | ] 87 | ]; 88 | $systemInit = [ 89 | 'homeInfo' => $homeInfo, 90 | 'logoInfo' => $logoInfo, 91 | 'menuInfo' => $menuInfo, 92 | ]; 93 | return json($systemInit); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/controller/master/Ota.php: -------------------------------------------------------------------------------- 1 | RELEASE_API = base64_decode('aHR0cHM6Ly90b29sLWNsb3VkLmFvYW9zdGFyLmNvbS9vcGVuL3JlbGVhc2U='); 20 | } 21 | 22 | private function get_last_release() 23 | { 24 | 25 | $res = aoaostar_get($this->RELEASE_API, [ 26 | 'referer:' . Request::domain(), 27 | ]); 28 | $json = json_decode($res); 29 | 30 | if (empty($json) || empty($json->data)) { 31 | if (!empty($json->message)) { 32 | throw new \Exception($json->message); 33 | } 34 | throw new \Exception('"连接云中心失败,请检查网络连通性是否正常"'); 35 | } 36 | return $json; 37 | } 38 | 39 | public function check() 40 | { 41 | try { 42 | $release = $this->get_last_release(); 43 | $release->data->current_version = get_version(); 44 | 45 | } catch (\Exception $e) { 46 | return error($e->getMessage()); 47 | 48 | } 49 | return success($release->data); 50 | } 51 | 52 | public function update() 53 | { 54 | try { 55 | $release = $this->get_last_release(); 56 | 57 | } catch (\Exception $e) { 58 | return error($e->getMessage()); 59 | 60 | } 61 | $get = aoaostar_get($release->data->download_url); 62 | if (empty($get) || str_starts_with($get, 'CURL Error:')) { 63 | return error('下载更新包失败,请检查网络连通性是否正常'); 64 | } 65 | $tmpFilename = app()->getRuntimePath() . '/tmp/' . uniqid() . '.zip'; 66 | if (!file_exists(dirname($tmpFilename))) { 67 | mkdir(dirname($tmpFilename), 0755, true); 68 | } 69 | try { 70 | if (!file_put_contents($tmpFilename, $get)) { 71 | return error('保存文件失败,请检查是否有写入权限'); 72 | } 73 | $rootPath = app()->getRootPath(); 74 | if (!unzip($tmpFilename, $rootPath)) { 75 | return error('解压压缩包失败'); 76 | } 77 | return msg('ok', '资源包解压成功', $release->data); 78 | } finally { 79 | @unlink($tmpFilename); 80 | } 81 | } 82 | 83 | public function updateDatabase() 84 | { 85 | $glob = glob(app()->getRuntimePath() . '/update/sql/*.sql'); 86 | if (empty($glob)) { 87 | return msg('ok', '未发现数据库更新文件'); 88 | } 89 | foreach ($glob as $value) { 90 | $result[$value] = []; 91 | $basename = basename($value); 92 | $migration = Migration::where('filename', $basename) 93 | ->findOrEmpty(); 94 | if ($migration->isExists()) { 95 | $result[$value][] = '该文件已经执行过了,已跳过'; 96 | goto end; 97 | } 98 | $lines = file($value); 99 | $execSQL = new ExecSQL(); 100 | $lines = $execSQL->purify($lines); 101 | $number = $execSQL->exec($lines); 102 | $result[$value] = array_merge($result[$value], $execSQL->getErrors()); 103 | 104 | if ($number > 0) { 105 | $result[$value][] = "影响的记录数 $number"; 106 | } 107 | Migration::create([ 108 | 'filename' => $basename, 109 | ]); 110 | $result[$value][] = '创建数据库升级记录成功'; 111 | end: 112 | unlink($value); 113 | $result[$value] = "[$basename]:" . implode("\n", $result[$value]); 114 | } 115 | return msg('ok', "数据库执行结果:\n" . implode("\n", $result)); 116 | } 117 | 118 | public function updateScript() 119 | { 120 | $glob = glob(app()->getRuntimePath() . '/update/script/*.php'); 121 | if (empty($glob)) { 122 | return msg('ok', '未发现更新脚本'); 123 | } 124 | foreach ($glob as $value) { 125 | $result[$value] = []; 126 | $basename = basename($value); 127 | $migration = Migration::where('filename', $basename) 128 | ->findOrEmpty(); 129 | if ($migration->isExists()) { 130 | $result[$value][] = '该文件已经执行过了,已跳过'; 131 | goto end; 132 | } 133 | try { 134 | require $value; 135 | $class = 'UpdateScript'; 136 | if (!class_exists($class)) { 137 | throw new \Exception("更新脚本不存在[$class]类"); 138 | } 139 | $instance = new $class(); 140 | $boot = 'main'; 141 | if (!method_exists($instance, $boot)) { 142 | throw new \Exception("更新脚本不存在[$boot]方法"); 143 | } 144 | $instance->main(); 145 | $result[$value] = array_merge($result[$value], $instance->getResult()); 146 | } catch (\Exception $e) { 147 | $result[$value][] = $e->getMessage(); 148 | } 149 | Migration::create([ 150 | 'filename' => $basename, 151 | ]); 152 | $result[$value][] = '创建更新脚本升级记录成功'; 153 | end: 154 | unlink($value); 155 | $result[$value] = "[$basename]:" . implode("\n", $result[$value]); 156 | } 157 | return msg('ok', "更新脚本执行结果:\n" . implode("\n", $result)); 158 | } 159 | } -------------------------------------------------------------------------------- /app/controller/master/Plugin.php: -------------------------------------------------------------------------------- 1 | 'integer', 20 | 'limit' => 'integer', 21 | 'categoryId' => 'integer', 22 | ]); 23 | if (!$validate->check($params)) { 24 | 25 | return error($validate->getError()); 26 | } 27 | $plugins = (new \app\model\Plugin)->pagination($params); 28 | 29 | $plugins['items']->load(['category']); 30 | 31 | return success($plugins); 32 | } 33 | 34 | public function get() 35 | { 36 | 37 | $params = Request::param(); 38 | 39 | $validate = Validate::rule([ 40 | 'id' => 'require|integer', 41 | ]); 42 | if (!$validate->check($params)) { 43 | 44 | return error($validate->getError()); 45 | } 46 | $plugin = \app\model\Plugin::get($params['id']); 47 | return success($plugin); 48 | } 49 | 50 | public function update() 51 | { 52 | 53 | $params = Request::param(); 54 | 55 | $validate = Validate::rule([ 56 | 'id|ID' => 'require', 57 | // 'title|插件标题' => 'chsDash', 58 | 'alias|路由别名' => 'unique:plugin', 59 | 'config|插件配置' => 'is_json', 60 | 'class|插件类名' => 'unique:plugin|is_legal_plugin_class', 61 | 'category_id|分类' => 'integer', 62 | 'enable' => 'integer', 63 | 'weight|权重' => 'integer', 64 | 'permission|权限' => 'alphaDash', 65 | ]); 66 | if (!$validate->check($params)) { 67 | 68 | return error($validate->getError()); 69 | } 70 | 71 | $plugin = \app\model\Plugin::get($params['id']); 72 | 73 | $plugin->allowField([ 74 | 'category_id', 75 | 'class', 76 | 'config', 77 | 'desc', 78 | // 'logo', 79 | 'alias', 80 | 'version', 81 | 'title', 82 | 'enable', 83 | 'weight', 84 | 'template', 85 | 'permission', 86 | ])->data($params)->save(); 87 | return success($plugin); 88 | } 89 | 90 | public function create() 91 | { 92 | $params = Request::param(); 93 | 94 | $validate = Validate::rule([ 95 | 'title|插件标题' => 'require', 96 | 'alias|路由别名' => 'require|unique:plugin', 97 | 'desc|插件描述' => 'require', 98 | 'config|插件配置' => 'is_json', 99 | 'class|插件类名' => 'require|unique:plugin|is_legal_plugin_class', 100 | 'version|插件版本号' => 'require', 101 | 'category_id|分类' => 'require|integer', 102 | 'enable' => 'integer', 103 | 'weight|权重' => 'integer', 104 | 'permission|权限' => 'alphaDash', 105 | ]); 106 | if (!$validate->check($params)) { 107 | 108 | return error($validate->getError()); 109 | } 110 | $plugin = new \app\model\Plugin(); 111 | 112 | $plugin->allowField([ 113 | 'category_id', 114 | 'class', 115 | 'config', 116 | 'desc', 117 | 'alias', 118 | 'version', 119 | 'title', 120 | 'enable', 121 | 'weight', 122 | 'template', 123 | 'permission', 124 | ])->data($params)->save(); 125 | return success($plugin); 126 | } 127 | 128 | public function delete() 129 | { 130 | $params = Request::param(); 131 | 132 | $validate = Validate::rule([ 133 | 'id' => 'require|integer', 134 | ]); 135 | if (!$validate->check($params)) { 136 | return error($validate->getError()); 137 | } 138 | $plugin = \app\model\Plugin::get($params['id']); 139 | if ($plugin->isEmpty()) { 140 | return error('该插件不存在'); 141 | } 142 | Db::startTrans(); 143 | try { 144 | $classPath = plugin_path_get($plugin->class) . "/Install.php"; 145 | if (file_exists($classPath)) { 146 | require $classPath; 147 | $class = "\\plugin\\$plugin->class\\Install"; 148 | if (class_exists($class)) { 149 | $uninstall = new $class(); 150 | $uninstall->UnInstall($plugin); 151 | } 152 | } 153 | Db::name('plugin')->delete($params['id']); 154 | if (!empty($plugin->class)) { 155 | del_tree(plugin_path_get($plugin->class)); 156 | } 157 | // 提交事务 158 | Db::commit(); 159 | } catch (\Exception $e) { 160 | // 回滚事务 161 | Db::rollback(); 162 | return error($e->getMessage(), $e); 163 | } 164 | return success(); 165 | } 166 | 167 | public function upload() 168 | { 169 | $params = Request::file(); 170 | 171 | $validate = Validate::rule([ 172 | 'file' => 'require|file', 173 | ]); 174 | if (!$validate->check($params)) { 175 | return error($validate->getError()); 176 | } 177 | $uploadedFile = Request::file('file'); 178 | $plugin = new \app\lib\Plugin(); 179 | $zipFilepath = $plugin->getZipFilepath(); 180 | $uploadedFile->move(dirname($zipFilepath), basename($zipFilepath)); 181 | return $plugin->install(); 182 | } 183 | 184 | public function update_logo() 185 | { 186 | 187 | $params = Request::param(); 188 | 189 | $params['logo'] = Request::file('logo'); 190 | 191 | $validate = Validate::rule([ 192 | 'id' => 'requireWithout:class|integer', 193 | 'class|插件类名' => 'requireWithout:id|unique:plugin|is_legal_plugin_class', 194 | 'logo' => 'require|fileExt:png,jpg|fileSize:5242880|fileMime:image/png,image/jpeg', 195 | ]); 196 | 197 | if (!$validate->check($params)) { 198 | 199 | return error($validate->getError()); 200 | } 201 | 202 | 203 | if (!empty($params['id'])) { 204 | $plugin = \app\model\Plugin::get($params['id']); 205 | $params['class'] = $plugin->class; 206 | } 207 | 208 | $pluginPath = plugin_path_get($params['class']); 209 | if (!is_dir($pluginPath)) { 210 | mkdir($pluginPath, 0755, true); 211 | } 212 | $params['logo']->move($pluginPath, 'logo.png'); 213 | 214 | return success(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app/controller/master/System.php: -------------------------------------------------------------------------------- 1 | data([ 38 | 'key' => $v['key'], 39 | 'value' => $v['value'], 40 | ])->save(); 41 | } 42 | $all = Config::all(); 43 | return success($all); 44 | } 45 | 46 | public function info() 47 | { 48 | 49 | $tmp = 'version()'; 50 | $mysqlVersion = Db::query("select version()")[0][$tmp]; 51 | $data = [ 52 | 'app_name' => base64_decode('5YKy5pif5bel5YW3566x'), 53 | 'author' => base64_decode('UGx1dG8='), 54 | 'version' => get_version(), 55 | 'framework_version' => app()::VERSION, 56 | 'php_version' => PHP_VERSION, 57 | 'mysql_version' => $mysqlVersion, 58 | 'os' => php_uname(), 59 | 'host' => GetHostByName(env('SERVER_NAME')), 60 | 'date' => date("Y-m-d H:i:s"), 61 | ]; 62 | return success($data); 63 | } 64 | 65 | public function templates() 66 | { 67 | $glob = glob(root_path() . config("view.view_dir_name") . '/index/*'); 68 | $arr = []; 69 | foreach ($glob as $v) { 70 | if (is_dir($v)) { 71 | array_push($arr, basename($v)); 72 | } 73 | } 74 | return success($arr); 75 | } 76 | 77 | public function plugin_templates() 78 | { 79 | $glob = glob(template_path_get() . '/template/*'); 80 | $arr = [ 81 | 'default', 82 | ]; 83 | foreach ($glob as $v) { 84 | if (is_file($v)) { 85 | array_push($arr, basename($v, '.html')); 86 | } 87 | } 88 | return success($arr); 89 | } 90 | 91 | public function permissions() 92 | { 93 | $glob = glob(app_path() . '/lib/permission/impl/*'); 94 | $arr = []; 95 | foreach ($glob as $v) { 96 | if (is_file($v)) { 97 | array_push($arr, Str::snake(basename($v, '.php'))); 98 | } 99 | } 100 | return success($arr); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/controller/master/User.php: -------------------------------------------------------------------------------- 1 | 'integer', 20 | 'limit' => 'integer', 21 | ]); 22 | if (!$validate->check($params)) { 23 | 24 | return error($validate->getError()); 25 | } 26 | 27 | $select = (new \app\model\User)->pagination($params); 28 | 29 | return msg("ok", "success", $select); 30 | } 31 | 32 | public function get() 33 | { 34 | 35 | $params = Request::param(); 36 | 37 | $validate = Validate::rule([ 38 | 'id' => 'require|integer', 39 | ]); 40 | if (!$validate->check($params)) { 41 | 42 | return error($validate->getError()); 43 | } 44 | $plugin = \app\model\User::get($params['id']); 45 | return success($plugin); 46 | } 47 | 48 | public function update() 49 | { 50 | $params = Request::param(); 51 | 52 | $validate = Validate::rule([ 53 | 'id' => 'require', 54 | 'username|用户名' => 'require|max:26|graph', 55 | 'stars' => 'is_json', 56 | 'oauth' => 'is_json', 57 | ]); 58 | if (!$validate->check($params)) { 59 | 60 | return error($validate->getError()); 61 | } 62 | $plugin = \app\model\User::get($params['id']); 63 | if (empty($params['stars'])) { 64 | $params['stars'] = []; 65 | } else { 66 | $params['stars'] = array_values($params['stars']); 67 | } 68 | $plugin->allowField([ 69 | 'username', 70 | 'stars', 71 | 'oauth', 72 | ])->data($params)->save(); 73 | return success($plugin); 74 | } 75 | 76 | public function delete() 77 | { 78 | $params = Request::param(); 79 | 80 | $validate = Validate::rule([ 81 | 'id' => 'require|integer', 82 | ]); 83 | if (!$validate->check($params)) { 84 | 85 | return error($validate->getError()); 86 | } 87 | $delete = \app\model\User::get($params['id'])->delete(); 88 | if ($delete) { 89 | return msg('ok', '删除失败'); 90 | } 91 | return success(); 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /app/event.php: -------------------------------------------------------------------------------- 1 | [ 5 | ], 6 | 7 | 'listen' => [ 8 | 'AppInit' => [], 9 | 'HttpRun' => [], 10 | 'HttpEnd' => [], 11 | 'LogLevel' => [], 12 | 'LogWrite' => [], 13 | ], 14 | 15 | 'subscribe' => [ 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /app/lib/EnvOperation.php: -------------------------------------------------------------------------------- 1 | exampleEnv = $exampleEnv; 16 | } 17 | 18 | /** 19 | * @return mixed 20 | */ 21 | public function getEnv() 22 | { 23 | return $this->env; 24 | } 25 | 26 | /** 27 | * @param mixed $env 28 | */ 29 | public function setEnv($env): void 30 | { 31 | $this->env = $env; 32 | } 33 | 34 | public function purify($env = []) 35 | { 36 | preg_match_all('#{{(.+?)}}#', $this->env, $matches, PREG_SET_ORDER); 37 | foreach ($matches as $v) { 38 | $list = explode(':', $v[1]); 39 | $value = isset($env[$list[0]]) ? $env[$list[0]] : ''; 40 | $defaultValue = isset($list[1]) ? $list[1] : ''; 41 | $type = isset($list[2]) ? $list[2] : ''; 42 | if ($type === 'bool') { 43 | $value = var_export(boolval($value), 1); 44 | } 45 | if (empty($value)) { 46 | $value = $defaultValue; 47 | } 48 | $this->env = preg_replace('#' . $v[0] . '#', $value, $this->env); 49 | } 50 | } 51 | 52 | public function set($key, $newValue) 53 | { 54 | if (is_null($this->env)) { 55 | $this->env = preg_replace('#{{' . $key . '}}#', $newValue, $this->exampleEnv); 56 | } else { 57 | $this->env = preg_replace('#{{' . $key . '}}#', $newValue, $this->env); 58 | } 59 | } 60 | 61 | public function save() 62 | { 63 | return file_put_contents(app()->getRootPath() . '.env', $this->env); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/lib/ExecSQL.php: -------------------------------------------------------------------------------- 1 | errors; 19 | } 20 | 21 | /** 22 | * @param $sql array|string SQL语句 传入字符串类型需以\n分割 23 | * @return int 返回影响行数 24 | */ 25 | public function exec($sql) 26 | { 27 | $sql = $this->purify($sql); 28 | $number = 0; 29 | foreach ($sql as $key => $line) { 30 | try { 31 | $number += Db::execute($line); 32 | } catch (\Exception $e) { 33 | $this->errors[] = '第' . $key . '行:' . iconv('utf-8','utf-8//IGNORE',$e->getMessage()); 34 | } 35 | } 36 | return $number; 37 | } 38 | 39 | public function purify($sql) 40 | { 41 | $tmp = ''; 42 | $purify = []; 43 | if (!is_array($sql)) { 44 | $sql = explode("\n", $sql); 45 | } 46 | foreach ($sql as $key => &$line) { 47 | 48 | $line = trim($line); 49 | if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') { 50 | unset($sql[$key]); 51 | continue; 52 | } 53 | $tmp .= $line; 54 | if (substr($line, -1, 1) == ';') { 55 | unset($sql[$key]); 56 | $purify[] = $tmp; 57 | $tmp = ''; 58 | } 59 | unset($sql[$key]); 60 | } 61 | return $purify; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/lib/Jwt.php: -------------------------------------------------------------------------------- 1 | key = config_get('global.secret_key', request()->domain()); 19 | $this->algorithm = 'HS256'; 20 | $this->expire = time() + 86400 * 7; 21 | } 22 | 23 | 24 | public function generate_token($key, $data) 25 | { 26 | $payload = [ 27 | 'iss' => 'aoaostar', 28 | 'aud' => $key, 29 | 'iat' => time(), 30 | 'nbf' => time(), 31 | "exp" => $this->expire, 32 | "data" => $data, 33 | ]; 34 | return \Firebase\JWT\JWT::encode($payload, $this->key, $this->algorithm); 35 | } 36 | 37 | public function validate_token($token) 38 | { 39 | 40 | $res = [ 41 | 'status' => false, 42 | 'message' => '未知错误', 43 | 'data' => [], 44 | ]; 45 | try { 46 | \Firebase\JWT\JWT::$leeway = 60;//当前时间减去60,把时间留点余地 47 | $decoded = \Firebase\JWT\JWT::decode($token, new Key($this->key, $this->algorithm)); 48 | $arr = (array)$decoded; 49 | $res['status'] = true; 50 | $res['message'] = "success"; 51 | $res['data'] = (array)$arr['data']; 52 | 53 | } catch (\Firebase\JWT\SignatureInvalidException $e) { //签名不正确 54 | $res['message'] = "签名不正确"; 55 | } catch (\Firebase\JWT\BeforeValidException $e) { // 签名在某个时间点之后才能用 56 | $res['message'] = "token失效"; 57 | } catch (\Firebase\JWT\ExpiredException $e) { // token过期 58 | $res['message'] = "token过期"; 59 | } catch (\Exception $e) { //其他错误 60 | $res['message'] = "未知错误"; 61 | } 62 | return $res; 63 | } 64 | 65 | /** 66 | * @return float|int 67 | */ 68 | public function getExpire() 69 | { 70 | return $this->expire; 71 | } 72 | } -------------------------------------------------------------------------------- /app/lib/Plugin.php: -------------------------------------------------------------------------------- 1 | tmpPath = app()->getRuntimePath() . '/tmp/'; 29 | $this->uniqid = uniqid(); 30 | $this->zipFilename = $this->uniqid . '.zip'; 31 | $this->zipFilepath = $this->tmpPath . $this->zipFilename; 32 | 33 | $this->tmpDirPath = $this->tmpPath . $this->uniqid . '/';; 34 | 35 | if (!file_exists($this->tmpPath)) { 36 | mkdir($this->tmpPath, 0755, true); 37 | } 38 | 39 | } 40 | 41 | private function unzip() 42 | { 43 | if (!file_exists($this->zipFilepath)) { 44 | throw new Exception('压缩包不存在请重试'); 45 | } 46 | if (!unzip($this->zipFilepath, $this->tmpDirPath)) { 47 | throw new Exception('解压失败'); 48 | } 49 | return true; 50 | } 51 | 52 | private function checkPlugin() 53 | { 54 | $tree_relative = tree_relative($this->tmpDirPath); 55 | $arr1 = array_keys($tree_relative); 56 | $pluginAuthor = reset($arr1); 57 | if (empty($pluginAuthor)) { 58 | throw new Exception('插件目录格式有误,安装失败'); 59 | } 60 | $arr2 = array_keys($tree_relative[$pluginAuthor]); 61 | $pluginName = reset($arr2); 62 | 63 | if (empty($pluginAuthor) || empty($pluginName)) { 64 | throw new Exception('插件目录格式有误,安装失败'); 65 | } 66 | 67 | if (!file_exists("$this->tmpDirPath/$pluginAuthor/$pluginName/Install.php")) { 68 | throw new Exception('插件缺失Install.php,安装失败'); 69 | } 70 | return [ 71 | $pluginAuthor, 72 | $pluginName, 73 | ]; 74 | } 75 | 76 | private function clearOld() 77 | { 78 | 79 | if (!file_exists(dirname($this->pluginPath))) { 80 | mkdir(dirname($this->pluginPath), 0755, true); 81 | } 82 | del_tree($this->pluginPath); 83 | } 84 | 85 | public function install() 86 | { 87 | try { 88 | 89 | $this->unzip(); 90 | 91 | $checkPlugin = $this->checkPlugin(); 92 | 93 | $this->pluginAuthor = $checkPlugin[0]; 94 | $this->pluginName = $checkPlugin[1]; 95 | 96 | $this->pluginPath = plugin_path_get() . "/$this->pluginAuthor/$this->pluginName"; 97 | //清空旧插件 98 | $this->clearOld(); 99 | //移动文件 100 | rename("$this->tmpDirPath/$this->pluginAuthor/$this->pluginName", $this->pluginPath); 101 | // 执行Install.php 102 | require "$this->pluginPath/Install.php"; 103 | 104 | $this->pluginClass = "$this->pluginAuthor\\$this->pluginName"; 105 | $class = "plugin\\$this->pluginClass\\Install"; 106 | if (!class_exists($class)) { 107 | throw new Exception("插件缺失类$this->pluginClass,安装失败"); 108 | } 109 | $install = new $class(); 110 | $model = \app\model\Plugin::getByClass($this->pluginClass); 111 | if ($model->isEmpty()) { 112 | $model->title = '插件' . $this->uniqid; 113 | $model->alias = $this->uniqid; 114 | $model->class = $this->pluginClass; 115 | $model->desc = '暂无描述'; 116 | $model->version = 'v1.0'; 117 | $model->config = []; 118 | $model->category_id = 0; 119 | $model->request_count = 0; 120 | $model->enable = 1; 121 | $model->weight = 0; 122 | $model->template = 'default'; 123 | $model->permission = 'visitor'; 124 | } 125 | $install->Install($model); 126 | $model->logo = "/$model->alias/logo.png"; 127 | 128 | //判断alias是否重复 129 | 130 | $model2 = \app\model\Plugin::getByAlias($model->alias); 131 | if (!$model2->isEmpty() && $model2->id !== $model->id) { 132 | $model->alias .= "_$this->uniqid"; 133 | } 134 | $model->save(); 135 | } catch (\Exception $e) { 136 | @del_tree($this->pluginPath); 137 | return error( $e->getMessage()); 138 | } finally { 139 | @del_tree($this->tmpDirPath); 140 | @unlink($this->zipFilepath); 141 | } 142 | clear_cache(); 143 | return msg('ok', '安装成功', $model); 144 | } 145 | 146 | /** 147 | * @return string 148 | */ 149 | public function getZipFilepath(): string 150 | { 151 | return $this->zipFilepath; 152 | } 153 | } -------------------------------------------------------------------------------- /app/lib/oauth/Oauth.php: -------------------------------------------------------------------------------- 1 | config = $config; 18 | $this->params = $params; 19 | $this->redirect_uri = (string)url('/oauth/callback/gitee', [], '', true);; 20 | } 21 | 22 | public function oauth(): Response 23 | { 24 | return redirect('https://gitee.com/oauth/authorize?client_id=' . $this->config->client_id . '&redirect_uri=' . $this->redirect_uri . '&response_type=code'); 25 | } 26 | 27 | public function callback(): array 28 | { 29 | if (empty($this->params->code)) { 30 | return [ 31 | 'error' => '回调code异常', 32 | ]; 33 | } 34 | $url = 'https://gitee.com/oauth/token?grant_type=authorization_code'; 35 | $arr = [ 36 | 'client_id' => $this->config->client_id, 37 | 'client_secret' => $this->config->client_secret, 38 | 'code' => $this->params->code, 39 | 'redirect_uri' => $this->redirect_uri, 40 | ]; 41 | 42 | $res = aoaostar_post($url, $arr, [ 43 | 'Accept: application/json', 44 | ]); 45 | $json_decode = json_decode($res); 46 | 47 | if (!empty($json_decode) && !empty($json_decode->access_token)) { 48 | $aoaostar_get = aoaostar_get('https://gitee.com/api/v5/user', [ 49 | "Authorization: token $json_decode->access_token" 50 | ]); 51 | 52 | $json_decode = json_decode($aoaostar_get); 53 | if (!empty($json_decode) && !empty($json_decode->id)) { 54 | return [ 55 | 'id' => $json_decode->id, 56 | 'username' => $json_decode->login, 57 | 'avatar' => $json_decode->avatar_url, 58 | ]; 59 | } 60 | } 61 | if (!empty($json_decode->error_description)) { 62 | return [ 63 | 'error' => $json_decode->error_description, 64 | ]; 65 | } 66 | if (!empty($json_decode->error)) { 67 | return [ 68 | 'error' => $json_decode->error, 69 | ]; 70 | } 71 | return [ 72 | 'error' => '未知异常', 73 | ]; 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /app/lib/oauth/impl/Github.php: -------------------------------------------------------------------------------- 1 | config = $config; 18 | $this->params = $params; 19 | $this->redirect_uri = (string)url('/oauth/callback/github', [], '', true);; 20 | } 21 | 22 | public function oauth(): Response 23 | { 24 | return redirect('https://github.com/login/oauth/authorize?client_id=' . $this->config->client_id . '&redirect_uri=' . $this->redirect_uri); 25 | } 26 | 27 | public function callback(): array 28 | { 29 | 30 | if (empty($this->params->code)) { 31 | return [ 32 | 'error' => '回调code异常', 33 | ]; 34 | } 35 | $url = 'https://github.com/login/oauth/access_token'; 36 | $arr = [ 37 | 'client_id' => $this->config->client_id, 38 | 'client_secret' => $this->config->client_secret, 39 | 'code' => $this->params->code, 40 | ]; 41 | 42 | $res = aoaostar_post($url, $arr, [ 43 | 'Accept: application/json', 44 | ]); 45 | $json_decode = json_decode($res); 46 | if (!empty($json_decode) && !empty($json_decode->access_token)) { 47 | $aoaostar_get = aoaostar_get('https://api.github.com/user', [ 48 | "Authorization: token $json_decode->access_token" 49 | ]); 50 | 51 | $json_decode = json_decode($aoaostar_get); 52 | if (!empty($json_decode) && !empty($json_decode->id)) { 53 | return [ 54 | 'id' => $json_decode->id, 55 | 'username' => $json_decode->login, 56 | 'avatar' => $json_decode->avatar_url, 57 | ]; 58 | } 59 | } 60 | if (!empty($json_decode->error_description)) { 61 | return [ 62 | 'error' => $json_decode->error_description, 63 | ]; 64 | } 65 | if (!empty($json_decode->error)) { 66 | return [ 67 | 'error' => $json_decode->error, 68 | ]; 69 | } 70 | return [ 71 | 'error' => '未知异常', 72 | ]; 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /app/lib/permission/Permission.php: -------------------------------------------------------------------------------- 1 | id; 15 | $pass = Session::get($key); 16 | if (time() - $pass < 86400) { 17 | return true; 18 | } 19 | if (!empty($plugin->config->password) && $plugin->config->password == request()->param('password')) { 20 | Session::set($key, time()); 21 | if (request()->isAjax()) { 22 | return success(); 23 | } 24 | return true; 25 | } 26 | if (request()->isAjax()) { 27 | if (empty(request()->param('password'))) { 28 | return '请验证密码后重试'; 29 | } 30 | return '密码错误'; 31 | } 32 | return view('permission/password'); 33 | } 34 | } -------------------------------------------------------------------------------- /app/lib/permission/impl/Visitor.php: -------------------------------------------------------------------------------- 1 | isAjax() || !$request->isGet()) { 16 | $token = Request::header('Authorization'); 17 | if (empty($token)) { 18 | return error('请登录')->code(401); 19 | } 20 | $validate_token = (new Jwt())->validate_token($token); 21 | if ($validate_token['status']) { 22 | return $next($request); 23 | } 24 | return error($validate_token['message'])->code(401); 25 | } 26 | return redirect((string)url('/auth/login')); 27 | } 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/middleware/AuthAdmin.php: -------------------------------------------------------------------------------- 1 | isAjax() || !$request->isGet()) { 16 | $token = Request::header('Authorization', ''); 17 | if (empty($token)) { 18 | return error('请登录')->code(401); 19 | } 20 | $validate_token = (new Jwt())->validate_token($token); 21 | if ($validate_token['status']) { 22 | if (!is_admin($validate_token['data']['username'])) { 23 | return error('没有管理权限')->code(401); 24 | } 25 | return $next($request); 26 | } 27 | return error($validate_token['message'])->code(401); 28 | } 29 | return redirect((string)url('/auth/login')); 30 | } 31 | return $next($request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/middleware/BindAuth.php: -------------------------------------------------------------------------------- 1 | isExists()) { 14 | abort(400, '该插件不存在'); 15 | } 16 | if ($plugin->enable !== 1) { 17 | abort(400, '该插件已禁用'); 18 | } 19 | 20 | if (!empty($plugin->permission) && !is_admin()) { 21 | $permission = Str::studly(strtolower($plugin->permission)); 22 | $class = "\\app\\lib\\permission\\impl\\{$permission}"; 23 | if (!class_exists($class)) { 24 | abort(400, "该 permission[{$plugin->permission}] 未实现,请实现后重试"); 25 | } 26 | $check = $class::check($plugin); 27 | 28 | if ($check === false || ($check instanceof \think\response\View && $api)) { 29 | abort(403, "无权访问,请授权后重试"); 30 | } 31 | 32 | if ($check instanceof \think\Response) { 33 | if ($check instanceof \think\response\View) { 34 | //执行后续中间件 35 | $next($request); 36 | } 37 | return $check; 38 | } 39 | if (is_string($check)) { 40 | abort(400, $check); 41 | } 42 | } 43 | 44 | return $next($request); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/middleware/RateLimit.php: -------------------------------------------------------------------------------- 1 | check([ 20 | 'captcha' => request()->param('captcha') 21 | ], [ 22 | 'captcha|验证码' => 'require|captcha' 23 | ]); 24 | } catch (\Exception $e) { 25 | return error($e->getMessage()); 26 | } 27 | } 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/middleware/Response.php: -------------------------------------------------------------------------------- 1 | header([ 16 | 'X-Powered-By' => 'ASP.NET', 17 | 'Author' => 'AOAOSTAR/Pluto', 18 | 'Home-Page' => 'www.aoaostar.com', 19 | ]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/middleware/View.php: -------------------------------------------------------------------------------- 1 | config_get('global.'), 34 | 'categories' => $categories, 35 | 'user' => $get_user, 36 | ]); 37 | \think\facade\View::config(['view_path' => template_path_get()]); 38 | return $response; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/model/Base.php: -------------------------------------------------------------------------------- 1 | searchField as $v) { 17 | if (!empty($param[$v])) { 18 | $whereOr[] = [$v, 'like', '%' . $param[$v] . '%']; 19 | } 20 | } 21 | $model = new $this; 22 | 23 | $model = $model->where($where)->whereOr($whereOr); 24 | $total = $model->count(); 25 | if (!empty($param['page'])) { 26 | $page = intval($param['page']); 27 | $model = $model->page($page); 28 | } 29 | if (!empty($param['limit'])) { 30 | $limit = intval($param['limit']); 31 | $model = $model->limit($limit); 32 | } 33 | if (isset($param['column']) && isset($param['order'])) { 34 | $model = $model->order($param['column'], $param['order']); 35 | } 36 | $select = $model->select(); 37 | 38 | $data = [ 39 | 'total' => $total, 40 | 'items' => $select, 41 | ]; 42 | return $data; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/model/Category.php: -------------------------------------------------------------------------------- 1 | with(['plugins'])->findOrEmpty(); 24 | } 25 | 26 | public static function all($params = []) 27 | { 28 | $where = []; 29 | foreach (['name', 'title'] as $v) { 30 | if (!empty($params[$v])) { 31 | $where[] = [$v, 'like', '%' . $params[$v] . '%']; 32 | } 33 | } 34 | return self::where($where)->order('weight', 'desc')->select(); 35 | } 36 | 37 | public function plugins() 38 | { 39 | return $this->hasMany(Plugin::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/model/Config.php: -------------------------------------------------------------------------------- 1 | where('key', 'like', "%$key%")->select(); 30 | } else { 31 | $model = $model->where('key', $key)->findOrEmpty(); 32 | } 33 | return $model; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/model/Migration.php: -------------------------------------------------------------------------------- 1 | logo = "/$model->alias/logo.png"; 40 | $model->permission = $model->permission?:'visitor'; 41 | } 42 | 43 | public static function getByAlias($alias = '') 44 | { 45 | if (empty($alias)) { 46 | $alias = plugin_alias_get(); 47 | } 48 | return Cache::remember(__METHOD__ . '__' . $alias, function () use ($alias) { 49 | return self::where('alias', $alias)->with(['category'])->findOrEmpty(); 50 | }); 51 | } 52 | 53 | public static function getByClass($class) 54 | { 55 | return Cache::remember(__METHOD__ . '__' . $class, function () use ($class) { 56 | return self::where('class', $class)->with(['category'])->findOrEmpty(); 57 | }); 58 | } 59 | 60 | public static function get($id) 61 | { 62 | return self::where('id', $id)->with(['category'])->withCache(true)->findOrEmpty(); 63 | } 64 | 65 | public static function all($param) 66 | { 67 | $where = []; 68 | if (!empty($param['category_id'])) { 69 | $where[] = ['category_id', '=', $param['category_id']]; 70 | } 71 | 72 | if (!empty($param['enable'])) { 73 | $where[] = ['enable', '=', 1]; 74 | } 75 | 76 | $selects = (new Plugin)->pagination($param, $where); 77 | $selects['items']->hidden(['config', 'class'])->load(['category']); 78 | 79 | if (!empty($param['star']) && $param['star'] == 1) { 80 | $user = get_user(); 81 | $stars = $user->stars; 82 | } 83 | if (isset($stars)) { 84 | foreach ($selects['items'] as $k => $v) { 85 | if (!in_array($v->alias, $stars)) { 86 | unset($selects['items'][$k]); 87 | } 88 | } 89 | $selects['items'] = $selects['items']->values(); 90 | } 91 | return $selects; 92 | } 93 | 94 | public function category() 95 | { 96 | return $this->belongsTo(Category::class); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/model/Request.php: -------------------------------------------------------------------------------- 1 | cache(true)->findOrEmpty(); 23 | } 24 | public function plugin() 25 | { 26 | return $this->belongsTo(Plugin::class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/model/User.php: -------------------------------------------------------------------------------- 1 | cache(30)->findOrEmpty(); 36 | } 37 | 38 | public static function getByUsername($username) 39 | { 40 | return User::where('username', $username)->findOrEmpty(); 41 | } 42 | 43 | public static function isLogin(): bool 44 | { 45 | 46 | $user = self::getUser(); 47 | return !empty($user->id) && $user->isExists(); 48 | } 49 | 50 | public static function isAdmin($user = null): bool 51 | { 52 | $user = $user ?? self::getUser(); 53 | return $user !== null && !empty($user->id) && $user->id === 1 && $user->isExists(); 54 | } 55 | 56 | public static function getUser(): User 57 | { 58 | 59 | $user = Session::get("user"); 60 | if (empty($user)) { 61 | $access_token = \think\facade\Request::header('Authorization'); 62 | if (!empty($access_token)) { 63 | $resp = (new Jwt())->validate_token($access_token); 64 | $user = (object)$resp['data']; 65 | } 66 | } 67 | 68 | if (!empty($user->id)) { 69 | $user = User::get($user->id); 70 | if ($user->isExists()) { 71 | return $user; 72 | } 73 | } 74 | return User::visitor(); 75 | } 76 | 77 | public static function visitor(): User 78 | { 79 | return new User([ 80 | 'id' => 0, 81 | 'username' => '', 82 | 'stars' => [], 83 | 'avatar' => '', 84 | 'create_time' => '', 85 | 'ip' => '', 86 | 'oauth' => [], 87 | 'update_time' => '', 88 | ]); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/provider.php: -------------------------------------------------------------------------------- 1 | Request::class, 8 | 'think\exception\Handle' => ExceptionHandle::class, 9 | ]; 10 | -------------------------------------------------------------------------------- /app/service.php: -------------------------------------------------------------------------------- 1 | extend('is_json', function ($value) { 30 | if (!is_string($value)) { 31 | $value = json_encode($value); 32 | } 33 | json_decode($value); 34 | return json_last_error() == JSON_ERROR_NONE; 35 | }); 36 | $validate->extend('is_legal_plugin_class', function ($value) { 37 | if (!is_string($value)){ 38 | return false; 39 | } 40 | $arr = explode('\\', $value); 41 | return count($arr) === 2; 42 | }); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aoaostar/toolbox", 3 | "description": "AOAOSTAR TOOLBOX", 4 | "type": "project", 5 | "keywords": [ 6 | "aoaostar", 7 | "toolbox" 8 | ], 9 | "homepage": "http://www.aoaostar.com/", 10 | "license": "GPL-3.0-only", 11 | "authors": [ 12 | { 13 | "name": "Pluto", 14 | "email": "i@aoaostar.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=7.2.5", 19 | "topthink/framework": "^6.0.0", 20 | "topthink/think-orm": "^2.0", 21 | "topthink/think-view": "^1.0", 22 | "topthink/think-migration": "^3.0", 23 | "ext-curl": "*", 24 | "ext-json": "*", 25 | "ext-zip": "*", 26 | "ext-pdo": "*", 27 | "ext-iconv": "*", 28 | "ext-openssl": "*", 29 | "firebase/php-jwt": "^6.2", 30 | "ext-fileinfo": "*", 31 | "topthink/think-captcha": "^3.0" 32 | }, 33 | "require-dev": { 34 | "symfony/var-dumper": "^4.2", 35 | "topthink/think-trace": "^1.0", 36 | "topthink/think-ide-helper": "^1.0", 37 | "jaguarjack/migration-generator": "dev-master" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "app\\": "app", 42 | "plugin\\": "plugin" 43 | }, 44 | "psr-0": { 45 | "": "extend/" 46 | } 47 | }, 48 | "config": { 49 | "preferred-install": "dist" 50 | }, 51 | "scripts": { 52 | "post-autoload-dump": [ 53 | "@php think service:discover", 54 | "@php think vendor:publish" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('app.host', ''), 8 | // 应用的命名空间 9 | 'app_namespace' => '', 10 | // 是否启用路由 11 | 'with_route' => true, 12 | // 默认应用 13 | 'default_app' => 'index', 14 | // 默认时区 15 | 'default_timezone' => 'Asia/Shanghai', 16 | 17 | // 应用映射(自动多应用模式有效) 18 | 'app_map' => [], 19 | // 域名绑定(自动多应用模式有效) 20 | 'domain_bind' => [], 21 | // 禁止URL访问的应用列表(自动多应用模式有效) 22 | 'deny_app_list' => [], 23 | 24 | // 异常页面的模板文件 25 | 'exception_tmpl' => \think\facade\App::getAppPath() . 'template/exception.tpl', 26 | 27 | // 错误显示信息,非调试模式有效 28 | 'error_message' => '页面错误!请稍后再试~', 29 | // 显示错误信息 30 | 'show_error_msg' => true, 31 | 'http_exception_template' => [ 32 | // 定义404错误的模板文件地址 33 | 404 => \think\facade\App::getRootPath() . 'public/404.html', 34 | ] 35 | ]; 36 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('cache.driver', 'file'), 10 | 11 | // 缓存连接方式配置 12 | 'stores' => [ 13 | 'file' => [ 14 | // 驱动方式 15 | 'type' => 'File', 16 | // 缓存保存目录 17 | 'path' => '', 18 | // 缓存前缀 19 | 'prefix' => 'toolbox', 20 | // 缓存有效期 0表示永久缓存 21 | 'expire' => env('cache.expire', 3600), 22 | // 缓存标签前缀 23 | 'tag_prefix' => 'toolbox:', 24 | // 序列化机制 例如 ['serialize', 'unserialize'] 25 | 'serialize' => [], 26 | ], 27 | 'redis' => [ 28 | // 驱动方式 29 | 'type' => 'Redis', 30 | 'host' => env('cache.redis.host', '127.0.0.1'), 31 | 'port' => env('cache.redis.port', 6379), 32 | 'password' => env('cache.redis.password', ''), 33 | 'select' => env('cache.redis.select', 0), 34 | // 缓存有效期 0表示永久缓存 35 | 'expire' => env('cache.expire', 3600), 36 | 'prefix' => 'toolbox_', 37 | ], 38 | 'session_redis' => [ 39 | // 驱动方式 40 | 'type' => 'Redis', 41 | 'host' => env('session.redis.host', '127.0.0.1'), 42 | 'port' => env('session.redis.port', 6379), 43 | 'password' => env('session.redis.password', ''), 44 | 'select' => env('session.redis.select', 1), 45 | // 缓存有效期 0表示永久缓存 46 | 'expire' => env('session.expire', 86400 * 7), 47 | 'prefix' => 'session_', 48 | ], 49 | // 更多的缓存连接 50 | ], 51 | ]; 52 | -------------------------------------------------------------------------------- /config/captcha.php: -------------------------------------------------------------------------------- 1 | 5, 9 | // 验证码字符集合 10 | 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', 11 | // 验证码过期时间 12 | 'expire' => 1800, 13 | // 是否使用中文验证码 14 | 'useZh' => false, 15 | // 是否使用算术验证码 16 | 'math' => true, 17 | // 是否使用背景图 18 | 'useImgBg' => false, 19 | //验证码字符大小 20 | 'fontSize' => 25, 21 | // 是否使用混淆曲线 22 | 'useCurve' => true, 23 | //是否添加杂点 24 | 'useNoise' => true, 25 | // 验证码字体 不设置则随机 26 | 'fontttf' => '', 27 | //背景颜色 28 | 'bg' => [243, 251, 254], 29 | // 验证码图片高度 30 | 'imageH' => 0, 31 | // 验证码图片宽度 32 | 'imageW' => 0, 33 | 34 | // 添加额外的验证码设置 35 | // verify => [ 36 | // 'length'=>4, 37 | // ... 38 | //], 39 | ]; 40 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | [ 8 | 9 | 'plugin:package' => 'app\command\PluginPackage', 10 | ], 11 | ]; 12 | -------------------------------------------------------------------------------- /config/cookie.php: -------------------------------------------------------------------------------- 1 | 86400 * 360, 8 | // cookie 保存路径 9 | 'path' => '/', 10 | // cookie 有效域名 11 | 'domain' => '', 12 | // cookie 启用安全传输 13 | 'secure' => false, 14 | // httponly设置 15 | 'httponly' => false, 16 | // 是否使用 setcookie 17 | 'setcookie' => true, 18 | // samesite 设置,支持 'strict' 'lax' 19 | 'samesite' => '', 20 | ]; 21 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('database.driver', 'mysql'), 6 | 7 | // 自定义时间查询规则 8 | 'time_query_rule' => [], 9 | 10 | // 自动写入时间戳字段 11 | // true为自动识别类型 false关闭 12 | // 字符串则明确指定时间字段类型 支持 int timestamp datetime date 13 | 'auto_timestamp' => true, 14 | 15 | // 时间字段取出后的默认时间格式 16 | 'datetime_format' => 'Y-m-d H:i:s', 17 | 18 | // 时间字段配置 配置格式:create_time,update_time 19 | 'datetime_field' => '', 20 | 21 | // 数据库连接配置信息 22 | 'connections' => [ 23 | 'mysql' => [ 24 | // 数据库类型 25 | 'type' => env('database.type', 'mysql'), 26 | // 服务器地址 27 | 'hostname' => env('database.hostname', '127.0.0.1'), 28 | // 数据库名 29 | 'database' => env('database.database', ''), 30 | // 用户名 31 | 'username' => env('database.username', 'root'), 32 | // 密码 33 | 'password' => env('database.password', ''), 34 | // 端口 35 | 'hostport' => env('database.hostport', '3306'), 36 | // 数据库连接参数 37 | 'params' => [], 38 | // 数据库编码默认采用utf8 39 | 'charset' => env('database.charset', 'utf8'), 40 | // 数据库表前缀 41 | 'prefix' => env('database.prefix', ''), 42 | 43 | // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) 44 | 'deploy' => 0, 45 | // 数据库读写是否分离 主从式有效 46 | 'rw_separate' => false, 47 | // 读写分离后 主服务器数量 48 | 'master_num' => 1, 49 | // 指定从服务器序号 50 | 'slave_no' => '', 51 | // 是否严格检查字段是否存在 52 | 'fields_strict' => true, 53 | // 是否需要断线重连 54 | 'break_reconnect' => false, 55 | // 监听SQL 56 | 'trigger_sql' => env('app_debug', true), 57 | // 开启字段缓存 58 | 'fields_cache' => false, 59 | ], 60 | 61 | // 更多的数据库配置信息 62 | ], 63 | ]; 64 | -------------------------------------------------------------------------------- /config/filesystem.php: -------------------------------------------------------------------------------- 1 | env('filesystem.driver', 'local'), 6 | // 磁盘列表 7 | 'disks' => [ 8 | 'local' => [ 9 | 'type' => 'local', 10 | 'root' => app()->getRuntimePath() . 'storage', 11 | ], 12 | 'public' => [ 13 | // 磁盘类型 14 | 'type' => 'local', 15 | // 磁盘路径 16 | 'root' => app()->getRootPath() . 'public/storage', 17 | // 磁盘路径对应的外部URL路径 18 | 'url' => '/storage', 19 | // 可见性 20 | 'visibility' => 'public', 21 | ], 22 | 'tmp' => [ 23 | // 磁盘类型 24 | 'type' => 'local', 25 | // 磁盘路径 26 | 'root' => app()->getRuntimePath() . 'tmp', 27 | ], 28 | // 更多的磁盘配置信息 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /config/lang.php: -------------------------------------------------------------------------------- 1 | env('lang.default_lang', 'zh-cn'), 9 | // 允许的语言列表 10 | 'allow_lang_list' => [], 11 | // 多语言自动侦测变量名 12 | 'detect_var' => 'lang', 13 | // 是否使用Cookie记录 14 | 'use_cookie' => true, 15 | // 多语言cookie变量 16 | 'cookie_var' => 'think_lang', 17 | // 多语言header变量 18 | 'header_var' => 'think-lang', 19 | // 扩展语言包 20 | 'extend_list' => [], 21 | // Accept-Language转义为对应语言包名称 22 | 'accept_language' => [ 23 | 'zh-hans-cn' => 'zh-cn', 24 | ], 25 | // 是否支持语言分组 26 | 'allow_group' => false, 27 | ]; 28 | -------------------------------------------------------------------------------- /config/log.php: -------------------------------------------------------------------------------- 1 | env('log.channel', 'file'), 9 | // 日志记录级别 10 | 'level' => [], 11 | // 日志类型记录的通道 ['error'=>'email',...] 12 | 'type_channel' => [], 13 | // 关闭全局日志写入 14 | 'close' => false, 15 | // 全局日志处理 支持闭包 16 | 'processor' => null, 17 | 18 | // 日志通道列表 19 | 'channels' => [ 20 | 'file' => [ 21 | // 日志记录方式 22 | 'type' => 'File', 23 | // 日志保存目录 24 | 'path' => '', 25 | // 单文件日志写入 26 | 'single' => false, 27 | // 独立日志级别 28 | 'apart_level' => [], 29 | // 最大日志文件数量 30 | 'max_files' => 0, 31 | // 使用JSON格式记录 32 | 'json' => false, 33 | // 日志处理 34 | 'processor' => null, 35 | // 关闭通道日志写入 36 | 'close' => false, 37 | // 日志输出格式化 38 | 'format' => '[%s][%s] %s', 39 | // 是否实时写入 40 | 'realtime_write' => false, 41 | ], 42 | // 其它日志通道配置 43 | ], 44 | 45 | ]; 46 | -------------------------------------------------------------------------------- /config/middleware.php: -------------------------------------------------------------------------------- 1 | [], 6 | // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 7 | 'priority' => [], 8 | ]; 9 | -------------------------------------------------------------------------------- /config/route.php: -------------------------------------------------------------------------------- 1 | '/', 9 | // URL伪静态后缀 10 | 'url_html_suffix' => '', 11 | // URL普通方式参数 用于自动生成 12 | 'url_common_param' => true, 13 | // 是否开启路由延迟解析 14 | 'url_lazy_route' => false, 15 | // 是否强制使用路由 16 | 'url_route_must' => false, 17 | // 合并路由规则 18 | 'route_rule_merge' => false, 19 | // 路由是否完全匹配 20 | 'route_complete_match' => false, 21 | // 访问控制器层名称 22 | 'controller_layer' => 'controller', 23 | // 空控制器名 24 | 'empty_controller' => 'Error', 25 | // 是否使用控制器后缀 26 | 'controller_suffix' => false, 27 | // 默认的路由变量规则 28 | 'default_route_pattern' => '[\w\.]+', 29 | // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 30 | 'request_cache_key' => false, 31 | // 请求缓存有效期 32 | 'request_cache_expire' => null, 33 | // 全局请求缓存排除规则 34 | 'request_cache_except' => [], 35 | // 默认控制器名 36 | 'default_controller' => 'Index', 37 | // 默认操作名 38 | 'default_action' => 'index', 39 | // 操作方法后缀 40 | 'action_suffix' => '', 41 | // 默认JSONP格式返回的处理方法 42 | 'default_jsonp_handler' => 'jsonpReturn', 43 | // 默认JSONP处理方法 44 | 'var_jsonp_handler' => 'callback', 45 | ]; 46 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | 'AOAOSTAR_SESSID', 9 | // SESSION_ID的提交变量,解决flash上传跨域 10 | 'var_session_id' => '', 11 | // 驱动方式 支持file cache 12 | 'type' => env('session.driver', 'file') === 'file'? 'file': 'cache', 13 | // 存储连接标识 当type使用cache的时候有效 14 | 'store' => 'session_' . env('session.driver', 'redis'), 15 | // 过期时间 16 | 'expire' => env('session.expire', 86400 * 7), 17 | // 前缀 18 | 'prefix' => '', 19 | ]; 20 | -------------------------------------------------------------------------------- /config/trace.php: -------------------------------------------------------------------------------- 1 | 'Html', 8 | // 读取的日志通道名 9 | 'channel' => '', 10 | ]; 11 | -------------------------------------------------------------------------------- /config/version.php: -------------------------------------------------------------------------------- 1 | 'Think', 9 | // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 10 | 'auto_rule' => 1, 11 | // 模板目录名 12 | 'view_dir_name' => 'view', 13 | // 模板后缀 14 | 'view_suffix' => 'html', 15 | // 模板文件名分隔符 16 | 'view_depr' => DIRECTORY_SEPARATOR, 17 | // 模板引擎普通标签开始标记 18 | 'tpl_begin' => '{', 19 | // 模板引擎普通标签结束标记 20 | 'tpl_end' => '}', 21 | // 标签库标签开始标记 22 | 'taglib_begin' => '{', 23 | // 标签库标签结束标记 24 | 'taglib_end' => '}', 25 | ]; 26 | -------------------------------------------------------------------------------- /docs/Github_Oauth.md: -------------------------------------------------------------------------------- 1 | ## Github Oauth 配置 2 | * 打开Github,并且登录你的github账号 3 | * 打开:https://github.com/settings/developers 4 | 5 | ### 创建一个Oauth APP 6 | 7 | ![](images/github_oauth_1.png) 8 | #### 填写APP信息 9 | ![](images/github_oauth_2.png) 10 | #### 获取`Client ID`和`Client secrets` 11 | ![](images/github_oauth_3.png) 12 | #### 最后在安装界面填入刚刚得到的的`Client ID`和`Client secrets`即可完成`Oauth`配置 13 | 14 | ## Gitee Oauth 配置 15 | 16 | > 操作方法类似,不再详解 -------------------------------------------------------------------------------- /docs/Plugin.md: -------------------------------------------------------------------------------- 1 | ## 插件编写 2 | 3 | ### 插件项目文件规范 4 | ``` 5 | └─plugin 6 | └─aoaostar_com # 顶级域名 .用_代替 7 | └─example # 插件名 8 | └─static # 静态文件目录 9 | App.php # API 10 | index.html # 首页 11 | Install.php # 安装、卸载时会执行方法 12 | logo.png # logo 图片 13 | ``` 14 | 15 | ### Install.php 16 | ```php 17 | title = "Hello,Pluto"; 31 | # 类名、无需修改 32 | $model->class = plugin_current_class_get(__NAMESPACE__); 33 | # 路由、即 example 34 | $model->alias = base_space_name(__NAMESPACE__); 35 | # 描述 36 | $model->desc = 'If you see this message, it means that your program is running properly.'; 37 | # 版本号 38 | $model->version = 'v1.0'; 39 | } 40 | # 卸载时运行方法 41 | public function UnInstall(Plugin $model) 42 | { 43 | 44 | } 45 | } 46 | ``` 47 | 48 | ### App.php 49 | 50 | ```php 51 | 79 | 80 | ### 复刻仓库 81 | 82 | ![](images/plugin_1.png) 83 | 84 | ### 打包文件 85 | ``` 86 | Usage: 87 | plugin:package 88 | 89 | Arguments: 90 | space 插件域,例如:aoaostar_com 91 | ``` 92 | * 打开命令行 93 | * 进入程序根目录(`cd`命令) 94 | * 运行命令 95 | ``` 96 | php think plugin:package aoaostar_com 97 | ``` 98 | 命令运行后将打包`plugin/aoaostar_com`下的所有插件 99 | 在`plugin/out/`目录下生成`aoaostar_com/*.zip`文件 100 | 101 | ### 储存 102 | > 旧的多余的插件包可以删除掉了 103 | > 新的文件按照下方文件结构放置即可 104 | ``` 105 | ├─dist 106 | │ └─aoaostar_com 107 | │ example.zip 108 | └─src 109 | └─aoaostar_com 110 | └─example 111 | App.php 112 | index.html 113 | Install.php 114 | logo.png 115 | ``` 116 | 然后`push`到`github`仓库即可 117 | 如果觉得自己的插件不错的话可以发起`pull request`到官方仓库哦~ 118 | 119 | ### 发布 120 | * 打开傲星工具箱云平台,并且登录 121 | 122 | 123 | ![](images/plugin_2.png) 124 | 125 | * 添加插件 126 | 127 | ![](images/plugin_3.png) 128 | 129 | * 填写正确的插件信息 130 | 131 | ![](images/plugin_4.png) 132 | 133 | * 成功添加插件 134 | 135 | ![](images/plugin_5.png) 136 | 137 | > 添加完毕后即可在`插件管理`看到你刚才添加的插件了 138 | > `待审核`状态请联系作者审核,审核通过了就可以在所有`傲星工具箱`的`插件中心`看到你的插件啦~ 139 | > 请严格按照上方目录 # 储存 规定的文件结构储存文件! 140 | > 否则将导致无法显示logo或者无法在线安装! 141 | 142 | ### 小贴士 143 | * `工具箱后台` - `插件中心`看不到我发布的插件? 144 | * 刷新缓存即可 145 | * ![](images/plugin_6.png) -------------------------------------------------------------------------------- /docs/Plugin_Permission.md: -------------------------------------------------------------------------------- 1 | ## Plugin Permission 使用 2 | ### 介绍 3 | * admin 4 | - 管理员方可访问,管理员可以访问所有 5 | * login 6 | - 登录后方可访问 7 | * password 8 | - 密码认证后方可访问 9 | * visitor 10 | - 所有人 11 | 12 | > 简要介绍一下`password`密码访问的使用 13 | 14 | ### 编辑插件 15 | 16 | ### 在插件配置信息填入下方`JSON`数据 17 | ```json 18 | { 19 | "password":"aoaostar" 20 | } 21 | ``` 22 | ![](images/plugin_permission_1.png) 23 | 24 | ### 成功演示 25 | ![](images/plugin_permission_2.png) 26 | 27 | ## 常见问题 28 | 29 | * 为什么设置了无效? 30 | - 清理缓存后即可生效 31 | -------------------------------------------------------------------------------- /docs/Plugin_Template.md: -------------------------------------------------------------------------------- 1 | ## Plugin Template 使用 2 | ### 添加插件 3 | ![](images/plugin_template_1.png) 4 | 5 | ### 在插件配置信息填入下方`JSON`数据 6 | ```json 7 | { 8 | "url":"https://www.aoaostar.com" 9 | } 10 | ``` 11 | ### 成功演示 12 | ![](images/plugin_template_2.png) 13 | #### `redirect`亦是如此 14 | 15 | ## 添加新模板 16 | ### 在`view/index/default/template`添加`pluto.html` 17 | ![](images/plugin_template_3.png) 18 | 19 | ### 添加成功之后,即可在后台看到新增的模板 20 | ![](images/plugin_template_4.png) 21 | -------------------------------------------------------------------------------- /docs/images/github_oauth_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/github_oauth_1.png -------------------------------------------------------------------------------- /docs/images/github_oauth_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/github_oauth_2.png -------------------------------------------------------------------------------- /docs/images/github_oauth_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/github_oauth_3.png -------------------------------------------------------------------------------- /docs/images/plugin_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_1.png -------------------------------------------------------------------------------- /docs/images/plugin_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_2.png -------------------------------------------------------------------------------- /docs/images/plugin_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_3.png -------------------------------------------------------------------------------- /docs/images/plugin_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_4.png -------------------------------------------------------------------------------- /docs/images/plugin_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_5.png -------------------------------------------------------------------------------- /docs/images/plugin_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_6.png -------------------------------------------------------------------------------- /docs/images/plugin_permission_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_permission_1.png -------------------------------------------------------------------------------- /docs/images/plugin_permission_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_permission_2.png -------------------------------------------------------------------------------- /docs/images/plugin_template_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_template_1.png -------------------------------------------------------------------------------- /docs/images/plugin_template_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_template_2.png -------------------------------------------------------------------------------- /docs/images/plugin_template_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_template_3.png -------------------------------------------------------------------------------- /docs/images/plugin_template_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/plugin_template_4.png -------------------------------------------------------------------------------- /docs/images/problem_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/problem_1.png -------------------------------------------------------------------------------- /docs/images/view_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/view_1.png -------------------------------------------------------------------------------- /docs/images/view_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/view_2.png -------------------------------------------------------------------------------- /docs/images/view_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/view_3.gif -------------------------------------------------------------------------------- /docs/images/view_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/docs/images/view_4.png -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !*.php 4 | !aoaostar_com 5 | !aoaostar_com/example 6 | !aoaostar_com/example/* -------------------------------------------------------------------------------- /plugin/CheckCaptcha.php: -------------------------------------------------------------------------------- 1 | title = "Hello,Pluto"; 15 | # 类名、无需修改 16 | $model->class = plugin_current_class_get(__NAMESPACE__); 17 | # 路由、即 example 18 | $model->alias = base_space_name(__NAMESPACE__); 19 | # 描述 20 | $model->desc = 'If you see this message, it means that your program is running properly.'; 21 | # 版本号 22 | $model->version = 'v1.0'; 23 | } 24 | 25 | # 卸载时运行方法 26 | public function UnInstall(Plugin $model) 27 | { 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /plugin/aoaostar_com/example/index.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/plugin_layout" /} 2 | 3 | {block name="title"}{$plugin.title} - {$app.title}{/block} 4 | 5 | {block name="head"} 6 | 7 | {/block} 8 | {block name="main"} 9 |
10 |
11 |
12 |
13 | 14 | 23 | 24 | {/block} 25 | -------------------------------------------------------------------------------- /plugin/aoaostar_com/example/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/plugin/aoaostar_com/example/logo.png -------------------------------------------------------------------------------- /plugin/common.php: -------------------------------------------------------------------------------- 1 | param("alias"), '\\/'); 7 | } 8 | 9 | function plugin_method_get() 10 | { 11 | return ucfirst(request()->param("method", "Index")); 12 | } 13 | 14 | function plugin_current_class_get($namespace) 15 | { 16 | return str_replace('plugin\\', '', $namespace); 17 | } 18 | 19 | function plugin_path_get($class = null) 20 | { 21 | if (is_null($class)) { 22 | $class = plugin_class_get(); 23 | } 24 | return str_replace('\\', '/', app()->getRootPath() . "plugin/$class"); 25 | } 26 | 27 | function plugin_logo_path_get($alias) 28 | { 29 | if (is_null($alias)) { 30 | $alias = plugin_alias_get(); 31 | } 32 | return plugin_path_get(plugin_class_get($alias)) . "/logo.png"; 33 | } 34 | 35 | function plugin_logo_relative_path_get($alias) 36 | { 37 | return $alias . '/logo.png'; 38 | } 39 | 40 | function plugin_template_path_get($class): string 41 | { 42 | return plugin_path_get($class) . '/index.html'; 43 | } 44 | 45 | function plugin_info_get($alias = null): \app\model\Plugin 46 | { 47 | if (is_null($alias)) { 48 | $alias = plugin_alias_get(); 49 | } 50 | return \app\model\Plugin::getByAlias($alias); 51 | } 52 | 53 | function plugin_relative_path_get($alias = null) 54 | { 55 | if (is_null($alias)) { 56 | $alias = plugin_alias_get(); 57 | } 58 | return str_replace('\\', '/', plugin_class_get($alias)); 59 | } 60 | 61 | function plugin_class_get($alias = null) 62 | { 63 | if (is_null($alias)) { 64 | $alias = plugin_alias_get(); 65 | } 66 | $model = plugin_info_get($alias); 67 | if (!$model->isExists()) { 68 | return ''; 69 | } 70 | return $model->class; 71 | } 72 | 73 | function plugin_config_get($alias = null) 74 | { 75 | if (is_null($alias)) { 76 | $alias = plugin_alias_get(); 77 | } 78 | $model = plugin_info_get($alias); 79 | if (!$model->isExists()) { 80 | return null; 81 | } 82 | return $model->config; 83 | } 84 | 85 | function plugin_static($alias = null) 86 | { 87 | if (is_null($alias)) { 88 | $alias = plugin_alias_get(); 89 | } 90 | if (is_string($alias)) { 91 | return "/$alias/static"; 92 | } 93 | return "/$alias->alias/static"; 94 | } 95 | function plugin_api($alias = null) 96 | { 97 | if (is_null($alias)) { 98 | $alias = plugin_alias_get(); 99 | } 100 | if (is_string($alias)) { 101 | return "/api/$alias"; 102 | } 103 | return "/api/$alias->alias"; 104 | } -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options +FollowSymlinks -Multiviews 3 | RewriteEngine On 4 | 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | # RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 8 | RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1] 9 | SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 10 | 11 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Not Found 6 | 7 | 9 | 10 | 11 | 12 | 13 | 209 | 210 | 211 | 212 | 213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | 224 |

Oops! Something went wrong!

225 | 226 | Return to Home 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | // [ 应用入口文件 ] 12 | namespace think; 13 | 14 | require __DIR__ . '/../vendor/autoload.php'; 15 | 16 | // 执行HTTP应用并响应 17 | $http = (new App())->http; 18 | 19 | $response = $http->run(); 20 | 21 | $response->send(); 22 | 23 | $http->end($response); 24 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/router.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | // $Id$ 12 | 13 | if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { 14 | return false; 15 | } else { 16 | $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; 17 | 18 | require __DIR__ . "/index.php"; 19 | } 20 | -------------------------------------------------------------------------------- /public/static/api.js: -------------------------------------------------------------------------------- 1 | const plugins_get = (categoryId, star) => { 2 | return httpGet('/api/plugins', { 3 | category_id: categoryId, 4 | star: star, 5 | column: 'weight', 6 | order: 'desc', 7 | }) 8 | } 9 | const star = (pluginAlias, action = 'add') => { 10 | return httpGet('/api/plugin/star', { 11 | alias: pluginAlias, 12 | action: action, 13 | }) 14 | } 15 | const install_api = { 16 | database: (params) => { 17 | return httpPost('/install/database', params) 18 | }, 19 | oauth: (params) => { 20 | return httpPost('/install/oauth', params) 21 | }, 22 | init_data: (params) => { 23 | return httpPost('/install/init_data', params) 24 | } 25 | } 26 | 27 | const user_update = (params) => { 28 | return httpPost('/api/user', params) 29 | } -------------------------------------------------------------------------------- /public/static/common.js: -------------------------------------------------------------------------------- 1 | const logout = () => { 2 | localStorage.clear() 3 | window.location.href = "/auth/logout" 4 | } 5 | 6 | const download = (user_id, filepath) => { 7 | window.open(`/download/${user_id}/${filepath}?t=${new Date().getTime()}`) 8 | } 9 | const open = (url) => { 10 | window.open(url) 11 | } 12 | const redirect = (url) => { 13 | window.location.href = url 14 | } 15 | 16 | const dateFormat = (date, format = 'YYYY-MM-DD HH:mm:ss') => { 17 | const config = { 18 | YYYY: date.getFullYear(), 19 | MM: date.getMonth() < 9 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, 20 | DD: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(), 21 | HH: date.getHours() < 10 ? '0' + date.getHours() : date.getHours(), 22 | mm: date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(), 23 | ss: date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(), 24 | } 25 | for (const key in config) { 26 | format = format.replace(key, config[key]) 27 | } 28 | return format 29 | } 30 | 31 | const randomString = (len = 32) => { 32 | let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; 33 | /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ 34 | let maxPos = $chars.length; 35 | let pwd = ''; 36 | for (let i = 0; i < len; i++) { 37 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); 38 | } 39 | return pwd; 40 | } 41 | // 改变blob对象中的name为随机文件名 42 | const changeBlobFileName = (blob) => { 43 | let fileType = blob.type || ""; 44 | let filename = 45 | randomString(5) + "." + 46 | fileType.slice(fileType.lastIndexOf("/") + 1); 47 | 48 | return new File([blob], filename, { 49 | type: fileType, 50 | }); 51 | } 52 | 53 | const secToTime = s => { 54 | let t = ''; 55 | if (s > -1) { 56 | let hour = Math.floor(s / 3600) 57 | let min = Math.floor(s / 60) % 60 58 | let sec = s % 60 59 | if (hour > 0) { 60 | if (hour < 10) { 61 | t += '0' 62 | } 63 | t = hour + "h" 64 | } 65 | if (hour > 0 || min > 0) { 66 | if (min < 10) { 67 | t += '0' 68 | } 69 | t += min + "m" 70 | } 71 | if (sec < 10) { 72 | t += '0' 73 | } 74 | t += sec + 's' 75 | } 76 | return t 77 | } 78 | // 文件大小格式转换 79 | const changeFilesize = (filesize) => { 80 | filesize = parseInt(filesize); 81 | let size = ""; 82 | if (filesize === 0) { 83 | size = "0.00 B" 84 | } else if (filesize < 1024) { //小于1KB,则转化成B 85 | size = filesize.toFixed(2) + " B" 86 | } else if (filesize < 1024 * 1024) { //小于1MB,则转化成KB 87 | size = (filesize / 1024).toFixed(2) + " KB" 88 | } else if (filesize < 1024 * 1024 * 1024) { //小于1GB,则转化成MB 89 | size = (filesize / (1024 * 1024)).toFixed(2) + " MB" 90 | } else { //其他转化成GB 91 | size = (filesize / (1024 * 1024 * 1024)).toFixed(2) + " GB" 92 | } 93 | return size; 94 | } 95 | // 下载速度格式转换 96 | const changeDownloadSpeed = (filesize) => { 97 | filesize = changeFilesize(filesize); 98 | return filesize.replace(/\s([K|M|G|B]*)B{0,1}/, '$1/s') 99 | } 100 | 101 | const scrollTopSmooth = () => { 102 | const easeout = (position, destination, rate, callback) => { 103 | if (position === destination || typeof destination !== 'number') { 104 | return false; 105 | } 106 | destination = destination || 0; 107 | rate = rate || 2; 108 | 109 | // 不存在原生`requestAnimationFrame`,用`setTimeout`模拟替代 110 | if (!window.requestAnimationFrame) { 111 | window.requestAnimationFrame = function (fn) { 112 | return setTimeout(fn, 17); 113 | } 114 | } 115 | const step = function () { 116 | position = position + (destination - position) / rate; 117 | if (position < 1) { 118 | callback(destination, true); 119 | return; 120 | } 121 | callback(position, false); 122 | requestAnimationFrame(step); 123 | }; 124 | step(); 125 | } 126 | // 当前滚动高度 127 | const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 128 | easeout(scrollTop, 0, 5, function (val) { 129 | window.scrollTo(0, val); 130 | }); 131 | } 132 | 133 | const copy = (text) => { 134 | let oInput = document.createElement('textarea'); 135 | oInput.value = text; 136 | document.body.appendChild(oInput); 137 | oInput.select(); // 选择对象 138 | document.execCommand("Copy"); // 执行浏览器复制命令 139 | oInput.className = 'oInput'; 140 | oInput.style.display = 'none'; 141 | oInput.remove(); 142 | } 143 | 144 | const $message = { 145 | success: (message) => { 146 | return Swal.fire({ 147 | icon: 'success', 148 | title: message, 149 | }) 150 | }, 151 | error: (message) => { 152 | return Swal.fire({ 153 | icon: 'error', 154 | title: message, 155 | }) 156 | }, 157 | loading: (message) => { 158 | let index = Swal.fire({ 159 | title: message, 160 | allowOutsideClick: false, 161 | }) 162 | Swal.showLoading() 163 | return index 164 | }, 165 | } 166 | 167 | //好兄弟,不要太过分了 168 | const title = ` 169 | _____ ________ _____ ________ ____________________ _____ __________ 170 | / _ \\ \\_____ \\ / _ \\ \\_____ \\ / _____/\\__ ___/ / _ \\ \\______ \\ 171 | / /_\\ \\ / | \\ / /_\\ \\ / | \\ \\_____ \\ | | / /_\\ \\ | _/ 172 | / | \\/ | \\/ | \\/ | \\ / \\ | | / | \\ | | \\ 173 | \\____|__ /\\_______ /\\____|__ /\\_______ //_______ / |____| \\____|__ / |____|_ / 174 | \\/ \\/ \\/ \\/ \\/ \\/ \\/ 175 | `; 176 | if (window.console && window.console.log) { 177 | console.log(title) 178 | console.log("%c 傲星工具箱 %c https://www.aoaostar.com ", "color: #fff; margin: 1em 0; padding: 5px 0; background: #28b9be;", "margin: 1em 0; padding: 5px 0; background: #efefef;"); 179 | console.log("%c 作者:Pluto %c i@aoaostar.com ", "color: #fff; margin: 1em 0; padding: 5px 0; background: #ffa0a0;", "margin: 1em 0; padding: 5px 0; background: #efefef;"); 180 | console.log("%c Github: %c https://github.com/aoaostar ", "color: #fff; margin: 1em 0; padding: 5px 0; background: #535f6a;", "margin: 1em 0; padding: 5px 0; background: #efefef;"); 181 | console.log("%c Telegram: %c https://t.me/aoaostar ", "color: #fff; margin: 1em 0; padding: 5px 0; background: #6190e8;", "margin: 1em 0; padding: 5px 0; background: #efefef;"); 182 | } -------------------------------------------------------------------------------- /public/static/http.js: -------------------------------------------------------------------------------- 1 | const instance = axios.create({ 2 | baseURL: '/', 3 | // timeout: 1000, 4 | headers: {'X-Requested-With': 'XMLHttpRequest'} 5 | }); 6 | instance.interceptors.response.use(function (response) { 7 | // 2xx 范围内的状态码都会触发该函数。 8 | // 对响应数据做点什么 9 | if (response.data.status !== "ok") { 10 | $message.error( 11 | response.data.message 12 | ); 13 | return Promise.reject(response.data) 14 | } 15 | return response.data 16 | }, function (error) { 17 | // 超出 2xx 范围的状态码都会触发该函数。 18 | // 对响应错误做点什么 19 | if (error && error.response && error.response.status) { 20 | if (error.response.status === 401) { 21 | if (error.response.data.message) { 22 | $message.error(error.response.data.message); 23 | } else { 24 | $message.error('请登录'); 25 | } 26 | } 27 | } 28 | return Promise.reject(error); 29 | }); 30 | const httpGet = (url, params = {}) => { 31 | return instance.get(url, { 32 | params 33 | }) 34 | } 35 | const httpPost = (url, params) => { 36 | return instance.post(url, params) 37 | } 38 | const httpPut = (url, params) => { 39 | return instance.put(url, params) 40 | } 41 | const httpDelete = (url, params) => { 42 | return instance.delete(url, { 43 | params 44 | }) 45 | } 46 | 47 | const request = (opt) => { 48 | return axios.request(opt) 49 | } -------------------------------------------------------------------------------- /public/static/images/install_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/static/images/install_background.jpg -------------------------------------------------------------------------------- /public/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/static/images/logo.png -------------------------------------------------------------------------------- /public/static/images/oauth/gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/static/images/oauth/gitee.png -------------------------------------------------------------------------------- /public/static/images/oauth/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/static/images/oauth/github.png -------------------------------------------------------------------------------- /public/static/images/plugin_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/toolbox/1df432c5c58806fa4d1912b4bb39634b9215e5aa/public/static/images/plugin_default.png -------------------------------------------------------------------------------- /public/static/lib/theme-change/2.0.2/index.js: -------------------------------------------------------------------------------- 1 | function themeToggle(){var toggleEl=document.querySelector("[data-toggle-theme]");(function(theme=localStorage.getItem("theme")){if(localStorage.getItem("theme")){document.documentElement.setAttribute("data-theme",theme);if(toggleEl){[...document.querySelectorAll("[data-toggle-theme]")].forEach(el=>{el.classList.add(toggleEl.getAttribute("data-act-class"))})}}})();if(toggleEl){[...document.querySelectorAll("[data-toggle-theme]")].forEach(el=>{el.addEventListener("click",function(){var themesList=el.getAttribute("data-toggle-theme");if(themesList){var themesArray=themesList.split(",");if(document.documentElement.getAttribute("data-theme")==themesArray[0]){if(themesArray.length==1){document.documentElement.removeAttribute("data-theme");localStorage.removeItem("theme")}else{document.documentElement.setAttribute("data-theme",themesArray[1]);localStorage.setItem("theme",themesArray[1])}}else{document.documentElement.setAttribute("data-theme",themesArray[0]);localStorage.setItem("theme",themesArray[0])}}[...document.querySelectorAll("[data-toggle-theme]")].forEach(el=>{el.classList.toggle(this.getAttribute("data-act-class"))})})})}}function themeBtn(){(function(theme=localStorage.getItem("theme")){if(theme!=undefined&&theme!=""){if(localStorage.getItem("theme")&&localStorage.getItem("theme")!=""){document.documentElement.setAttribute("data-theme",theme);var btnEl=document.querySelector("[data-set-theme='"+theme.toString()+"']");if(btnEl){[...document.querySelectorAll("[data-set-theme]")].forEach(el=>{el.classList.remove(el.getAttribute("data-act-class"))});if(btnEl.getAttribute("data-act-class")){btnEl.classList.add(btnEl.getAttribute("data-act-class"))}}}else{var btnEl=document.querySelector("[data-set-theme='']");if(btnEl.getAttribute("data-act-class")){btnEl.classList.add(btnEl.getAttribute("data-act-class"))}}}})();[...document.querySelectorAll("[data-set-theme]")].forEach(el=>{el.addEventListener("click",function(){document.documentElement.setAttribute("data-theme",this.getAttribute("data-set-theme"));localStorage.setItem("theme",document.documentElement.getAttribute("data-theme"));[...document.querySelectorAll("[data-set-theme]")].forEach(el=>{el.classList.remove(el.getAttribute("data-act-class"))});if(el.getAttribute("data-act-class")){el.classList.add(el.getAttribute("data-act-class"))}})})}function themeSelect(){(function(theme=localStorage.getItem("theme")){if(localStorage.getItem("theme")){document.documentElement.setAttribute("data-theme",theme);var optionToggler=document.querySelector("select[data-choose-theme] [value='"+theme.toString()+"']");if(optionToggler){[...document.querySelectorAll("select[data-choose-theme] [value='"+theme.toString()+"']")].forEach(el=>{el.selected=true})}}})();if(document.querySelector("select[data-choose-theme]")){[...document.querySelectorAll("select[data-choose-theme]")].forEach(el=>{el.addEventListener("change",function(){document.documentElement.setAttribute("data-theme",this.value);localStorage.setItem("theme",document.documentElement.getAttribute("data-theme"));[...document.querySelectorAll("select[data-choose-theme] [value='"+localStorage.getItem("theme")+"']")].forEach(el=>{el.selected=true})})})}}function themeChange(attach=true){if(attach===true){document.addEventListener("DOMContentLoaded",function(event){themeToggle();themeSelect();themeBtn()})}else{themeToggle();themeSelect();themeBtn()}}if(typeof exports!="undefined"){module.exports={themeChange:themeChange}}else{themeChange()} -------------------------------------------------------------------------------- /public/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | min-height: 100vh; 4 | flex-direction: column; 5 | } 6 | 7 | .site-main { 8 | flex: 1; 9 | } 10 | 11 | 12 | [data-theme=light] { 13 | --p: 0 0% 32%; 14 | --pf: 0 0% 45.1%; 15 | } 16 | 17 | [data-theme=dark] { 18 | --p: 222 13.4% 19%; 19 | --pf: 223 13.7% 10%; 20 | } 21 | 22 | .stats { 23 | overflow: visible; 24 | } 25 | 26 | .stats .stat { 27 | border-width: 1px; 28 | margin-top: -1px; 29 | margin-bottom: -1px; 30 | } 31 | 32 | .hover\:.bg-base-100:hover { 33 | --tw-bg-opacity: 1; 34 | background-color: hsl(var(--b1)) 35 | } 36 | 37 | .hover\:.bg-base-200:hover { 38 | --tw-bg-opacity: 1; 39 | background-color: hsl(var(--b2, var(--b1))) 40 | } 41 | 42 | .hover\:bg-base-300:hover { 43 | --tw-bg-opacity: 1; 44 | background-color: hsl(var(--b3, var(--b2))); 45 | } -------------------------------------------------------------------------------- /route/admin.php: -------------------------------------------------------------------------------- 1 | prefix('master.')->middleware(\app\middleware\AuthAdmin::class); 37 | 38 | 39 | Route::group('master/cloud', function () { 40 | Route::get('plugins', 'plugins'); 41 | Route::get('plugin', 'plugin_get'); 42 | Route::get('categories', 'categories'); 43 | Route::get('releases', 'releases'); 44 | Route::get('plugin_install', 'plugin_install'); 45 | })->prefix('master.Cloud/')->middleware(\app\middleware\AuthAdmin::class); 46 | 47 | Route::group('master/ota', function () { 48 | Route::any('/', 'check'); 49 | Route::any('check', 'check'); 50 | Route::any('update', 'update'); 51 | Route::any('database', 'updateDatabase'); 52 | Route::any('script', 'updateScript'); 53 | })->prefix('master.Ota/')->middleware(\app\middleware\AuthAdmin::class); 54 | 55 | Route::group('master/analysis', function () { 56 | Route::get('console', 'console'); 57 | Route::get('traffic_trends', 'traffic_trends'); 58 | Route::get('statistics', 'statistics'); 59 | })->prefix('master.Analysis/')->middleware(\app\middleware\AuthAdmin::class); 60 | -------------------------------------------------------------------------------- /route/api.php: -------------------------------------------------------------------------------- 1 | prefix('api.'); 13 | 14 | 15 | 16 | Route::group('api', function () { 17 | Route::any('plugin/star', 'Plugin/star'); 18 | Route::get('mine', 'User/get'); 19 | Route::get('user', 'User/get'); 20 | Route::post('user', 'User/update'); 21 | })->prefix('api.') 22 | ->middleware(\app\middleware\Auth::class); 23 | 24 | 25 | -------------------------------------------------------------------------------- /route/app.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | -------------------------------------------------------------------------------- /route/auth.php: -------------------------------------------------------------------------------- 1 | prefix('Auth/') 12 | ->middleware(\app\middleware\View::class); 13 | 14 | Route::get('/oauth/callback/[:mode]', 'Auth/callback') 15 | ->middleware(\app\middleware\BindAuth::class) 16 | ->middleware(\app\middleware\View::class); 17 | -------------------------------------------------------------------------------- /route/index.php: -------------------------------------------------------------------------------- 1 | middleware(\app\middleware\Auth::class) 7 | ->middleware(\app\middleware\View::class); 8 | 9 | 10 | Route::group('user', function () { 11 | Route::get('/', 'index'); 12 | })->prefix('User/') 13 | ->middleware(\app\middleware\Auth::class) 14 | ->middleware(\app\middleware\View::class); 15 | 16 | Route::get('/install/database', 'Install/database'); 17 | Route::get('/install/init_data', 'Install/init_data'); 18 | Route::get('/install/oauth', 'Install/oauth'); 19 | Route::get('/install', 'Install/index'); 20 | 21 | Route::get('/', 'Index/index') 22 | ->middleware(\app\middleware\View::class); -------------------------------------------------------------------------------- /route/plugin.php: -------------------------------------------------------------------------------- 1 | middleware(\app\middleware\PluginCheck::class, true) 7 | ->middleware(\app\middleware\RateLimit::class); 8 | 9 | 10 | Route::get(':alias/logo', 'Plugin/logo'); 11 | 12 | Route::group(':alias', function () { 13 | 14 | Route::get('/static/*', 'static'); 15 | Route::get('/[:path]', 'index') 16 | ->middleware(\app\middleware\View::class); 17 | 18 | })->prefix('Plugin/') 19 | ->middleware(\app\middleware\PluginCheck::class, false); 20 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !update 4 | !update/** -------------------------------------------------------------------------------- /runtime/update/sql/202203021857.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `toolbox_config` (`key`, `value`, `create_time`, `update_time`) VALUES ('cdn.npm', 'https://cdn.jsdelivr.net/npm', '2022-03-02 17:56:15', '2022-03-02 18:13:43'); 2 | 3 | INSERT INTO `toolbox_config` (`key`, `value`, `create_time`, `update_time`) VALUES ('cdn.cdnjs', 'https://cdn.staticfile.org', '2022-03-02 17:56:15', '2022-03-02 17:56:15'); -------------------------------------------------------------------------------- /runtime/update/sql/202203211510.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `toolbox_plugin` ADD COLUMN `template` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'default' AFTER `category_id`; -------------------------------------------------------------------------------- /runtime/update/sql/202209111427.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `toolbox_plugin` DROP COLUMN `logo`; 2 | 3 | ALTER TABLE `toolbox_user` ADD COLUMN `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '头像' AFTER `stars`; 4 | 5 | ALTER TABLE `toolbox_user` ADD COLUMN `oauth` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'oauth信息json' AFTER `update_time`; 6 | 7 | ALTER TABLE `toolbox_user` ADD COLUMN `ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'ip' AFTER `oauth`; 8 | 9 | ALTER TABLE `toolbox_user` MODIFY COLUMN `stars` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标星json array' AFTER `username`; 10 | 11 | ALTER TABLE `toolbox_user` DROP COLUMN `avatar_url`; 12 | 13 | ALTER TABLE `toolbox_user` ADD UNIQUE INDEX `username`(`username`) USING BTREE; 14 | 15 | UPDATE `toolbox_user` SET `oauth` = JSON_OBJECT('github', id) -------------------------------------------------------------------------------- /runtime/update/sql/202210051505.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `toolbox_plugin` MODIFY COLUMN `template` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'default' COMMENT '模板' AFTER `category_id`; 2 | 3 | ALTER TABLE `toolbox_plugin` ADD COLUMN `permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'visitor' COMMENT '权限' AFTER `template`; 4 | 5 | UPDATE `toolbox_plugin` SET `permission` = 'visitor' -------------------------------------------------------------------------------- /runtime/update/sql/202210191837.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `toolbox_config` (`key`, `value`, `create_time`, `update_time`) VALUES ('global.chat', 'https://t.me/aoaostar', '2022-10-19 09:46:13', '2022-10-19 09:57:45'); -------------------------------------------------------------------------------- /think: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | console->run(); -------------------------------------------------------------------------------- /vendor/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /view/index/default/auth/callback.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/layout" /} 2 |  3 | {block name="title"}登录回调 - {$app.title}{/block} 4 | {block name="main"} 5 | 7 |
8 |
9 |
10 |
登录回调,请稍后
11 |
12 |
13 |
14 | 22 | {/block} -------------------------------------------------------------------------------- /view/index/default/auth/login.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/layout" /} 2 |  3 | {block name="title"}Login - {$app.title}{/block} 4 | {block name="main"} 5 | 16 |
17 |
18 |
19 |
登录认证
20 |
21 | {foreach :get_enabled_oauth_mode() as $v} 22 | 41 | {/foreach} 42 |
43 |
44 |
45 |
46 | 75 | {/block} -------------------------------------------------------------------------------- /view/index/default/index/index.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/tools"" /} 2 | {block name="head"} 3 | 6 | {/block} 7 | -------------------------------------------------------------------------------- /view/index/default/index/stars.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/tools" /} 2 |  3 | {block name="title"}我的收藏 - {$app.title}{/block} 4 | {block name="head"} 5 | 8 | {/block} 9 | -------------------------------------------------------------------------------- /view/index/default/layout/footer.html: -------------------------------------------------------------------------------- 1 | 36 | 42 | 65 | {$app.foot_code|raw} -------------------------------------------------------------------------------- /view/index/default/layout/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {block name="title"}标题{/block} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {block name="head"}{/block} 23 | 24 | 25 |
26 | {include file="layout/header" /} 27 | {block name="main"}主内容{/block} 28 |
29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /view/index/default/layout/plugin_layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {block name="title"}标题{/block} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {block name="head"}{/block} 23 | 24 | 25 |
26 | {include file="layout/header" /} 27 |
28 |
29 | 30 |
31 |
32 |

{$plugin.title}

33 | 42 | 51 |
52 |

{$plugin.desc}

53 |
54 |
55 |
56 | {block name="main"}主内容{/block} 57 |
58 | 61 | 62 | 79 | {include file="layout/plugin_record" /} 80 | 81 | -------------------------------------------------------------------------------- /view/index/default/layout/plugin_record.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /view/index/default/layout/tools.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/layout" /} 2 | {block name="title"}{$app.title} - {$app.subtitle}{/block} 3 | {block name="main"} 4 |
5 | 6 |
7 |
8 |
9 | {{v.title}} 11 |
12 |
13 | 15 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
#
27 |
28 | {{plugin.title}} 29 |
30 |
31 |
32 |
33 |
34 |
36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |

{{item.title}}

45 |
46 | 56 | 66 | 70 | 73 |
74 |
75 |
76 |
77 |

{{item.desc}}

78 |

暂无描述

79 |
80 | 进入 81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 |
89 | 90 | 170 | {/block} 171 | -------------------------------------------------------------------------------- /view/index/default/permission/password.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/plugin_layout" /} 2 | {block name="title"}密码认证 - {$app.title}{/block} 3 | {block name="main"} 4 | 10 | 11 |
12 |
13 |
14 |
请输入密码解锁
15 |
16 |
17 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 47 | {/block} -------------------------------------------------------------------------------- /view/index/default/template/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$plugin.title} - {$app.title} 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | {include file="layout/plugin_record" /} 20 | 21 | -------------------------------------------------------------------------------- /view/index/default/template/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 页面加载中,请稍候... 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {include file="layout/plugin_record" /} 34 | 35 | -------------------------------------------------------------------------------- /view/index/default/user/index.html: -------------------------------------------------------------------------------- 1 | {extend name="layout/layout" /} 2 |  3 | {block name="title"}用户中心 - {$app.title}{/block} 4 | {block name="main"} 5 |
6 |
7 | 8 |
9 |
10 |

{{user.username}} 的个人信息

11 | 改个名字? 12 |
13 |
OAuth 信息
14 | {foreach $oauth as $k => $v } 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
{$k}
23 | {if $v===0} 24 |
未绑定
25 | {else} 26 |
已绑定
27 | {/if} 28 |
29 |
30 | {if $v===0} 31 | 32 | {else} 33 | {/if} 34 | 35 |
36 |
37 | {/foreach} 38 |
39 |
40 | 58 |
59 | 60 | 105 | {/block} 106 | --------------------------------------------------------------------------------