Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae378a2b47 | ||
|
|
e24f285fac | ||
|
|
d54584f70d | ||
|
|
fad7690c0c |
-129
@@ -1,129 +0,0 @@
|
||||
# docs
|
||||
docs/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Kiro IDE
|
||||
.kiro
|
||||
.vscode
|
||||
|
||||
.git/
|
||||
|
||||
# 忽略子目录的 .git 目录(避免被当作 git 子模块)
|
||||
fastmovie-admin/.git/
|
||||
fastmovie-vue/.git/
|
||||
/readme_assets
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-575
@@ -1,575 +0,0 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="./readme_assets/banner.png" alt="FastMovieAI Banner" width="100%" />
|
||||
|
||||
# FastMovieAI
|
||||
|
||||
**Commercial-Ready All-in-One Short Drama Creation Platform**
|
||||
|
||||
[Live Demo](https://fastmovie.ai) | [Official Website](https://www.fastmovieai.com) | [简体中文](./README.md)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 📖 Introduction
|
||||
|
||||
FastMovieAI is a feature-complete open-source short drama/video creation platform with a decoupled frontend-backend architecture, providing AI-driven video content creation capabilities. The platform integrates user management, payment systems, content management, video generation, and other complete functional modules, suitable for content creators and video production teams.
|
||||
|
||||
## 🎯 Our Vision
|
||||
|
||||
FastMovieAI is committed to building the **world's leading open-source short drama creation ecosystem**, comprehensively benchmarking against industry-leading commercial short drama creation platforms such as SenseTime Seko, Huanzhou, Jurilou, Kemeng, Oii, Mashu, Flova, Baidu Intelligent Creation, Volcano Engine, and Alibaba Cloud Video AI.
|
||||
|
||||
### Why Choose FastMovieAI?
|
||||
|
||||
🌟 **True Open Source Freedom** - Unlike closed-source commercial platforms' black-box operations, we provide completely open source code, allowing every developer to deeply customize and redevelop to create their own exclusive short drama creation tools.
|
||||
|
||||
💰 **Zero-Cost Commercial Use** - No need to pay expensive SaaS subscription fees, no worries about astronomical pay-as-you-go bills. Deploy once, use forever, truly achieving cost reduction and efficiency improvement.
|
||||
|
||||
🚀 **Continuous Evolution** - We believe in the power of open source. Through community-driven approaches, we rapidly iterate features and respond to user needs, keeping the platform technologically advanced.
|
||||
|
||||
🤝 **Co-Creation and Win-Win** - We sincerely invite film production companies, short drama teams, MCN agencies, independent creators, and technical developers to join our ecosystem, polish product details together, share industry experience, and make FastMovieAI a tool that truly understands creators.
|
||||
|
||||
### Our Commitments
|
||||
|
||||
- ✅ **Open and Transparent** - All code is open source, all decisions are public, subject to community supervision
|
||||
- ✅ **User First** - Take every feedback seriously, respond quickly to every need
|
||||
- ✅ **Technological Leadership** - Keep up with AI technology frontiers, continuously introduce the latest video generation, voice synthesis, and intelligent editing capabilities
|
||||
- ✅ **Ecosystem Co-construction** - Provide comprehensive plugin mechanisms, support third-party developers to contribute functional modules
|
||||
- ✅ **Long-term Maintenance** - Commit to long-term maintenance and updates, will not abandon the open-source version due to commercialization
|
||||
|
||||
### Join Us
|
||||
|
||||
Whether you are:
|
||||
- 🎬 **Film Companies** - Seeking solutions to reduce production costs and improve creative efficiency
|
||||
- 📱 **Short Drama Teams** - Need batch and industrialized content production tools
|
||||
- 🎨 **Individual Creators** - Independent producers who want to realize creative ideas at low cost
|
||||
- 💻 **Technical Developers** - Engineers interested in AI video technology
|
||||
|
||||
We welcome you to join! Let's use the power of open source to redefine the future of short drama creation and enable everyone to become an excellent content creator!
|
||||
|
||||
<div align="center">
|
||||
<img src="./readme_assets/111@1x.png" alt="FastMovieAI Platform Showcase" width="100%" style="border-radius: 5px;" />
|
||||
</div>
|
||||
|
||||
### ✨ Core Features
|
||||
|
||||
- 🎬 **AI Video Creation** - AI-based short drama video generation and editing capabilities
|
||||
- 🎭 **Character Management** - Virtual character creation, configuration, and management
|
||||
- 🎙️ **Voice Synthesis** - Multi-language voice synthesis and dubbing functions
|
||||
- 📝 **Script Editing** - Visual script editor and storyboard management
|
||||
- 💰 **Payment Integration** - Support for Alipay, WeChat Pay, and other payment methods
|
||||
- 👥 **User System** - Complete user registration, login, and VIP membership system
|
||||
- 💎 **Points System** - Flexible points recharge and consumption mechanism
|
||||
- 📱 **WeChat Integration** - Support for WeChat Official Account and Mini Program integration
|
||||
- 🔌 **Plugin Architecture** - Modular plugin system, easy to extend
|
||||
- 🌍 **Multi-language Support** - Chinese and English interface switching
|
||||
|
||||
### 🎯 Use Cases
|
||||
|
||||
- Short video content creation platform
|
||||
- AI-driven video production tools
|
||||
- Short drama creation and distribution system
|
||||
- Content creator community platform
|
||||
|
||||
## 🏗️ Technical Architecture
|
||||
|
||||
### Backend Stack
|
||||
|
||||
- **Framework**: Webman v2.1+ (High-performance PHP framework based on Workerman)
|
||||
- **PHP Version**: ≥8.1
|
||||
- **Database**: MySQL ≥8.0, Redis
|
||||
- **ORM**: ThinkORM v2.1+
|
||||
- **Template Engine**: ThinkTemplate v3.0+
|
||||
- **Payment**: Yansongda/Pay v3.7+ (Alipay, WeChat Pay)
|
||||
- **Video Processing**: php-ffmpeg v1.3+
|
||||
- **WebSocket**: webman/push v1.1+
|
||||
|
||||
### Frontend Stack
|
||||
|
||||
- **Framework**: Vue 3.5+ (Composition API)
|
||||
- **Build Tool**: Vite 7.1+
|
||||
- **Language**: TypeScript 5.9+
|
||||
- **UI Framework**: Element Plus 2.11+
|
||||
- **State Management**: Pinia 3.0+
|
||||
- **Router**: Vue Router 4.5+
|
||||
- **Video Processing**: @webav/av-cliper, mp4box
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
fastmovieai/
|
||||
├── fastmovie-admin/ # Backend application (PHP/Webman)
|
||||
│ ├── app/ # Application code
|
||||
│ ├── config/ # Configuration files
|
||||
│ ├── plugin/ # Plugin system
|
||||
│ ├── public/ # Public resources
|
||||
│ └── start.php # Entry point
|
||||
├── fastmovie-vue/ # Frontend application (Vue3/TypeScript)
|
||||
│ ├── src/ # Source code
|
||||
│ ├── public/ # Static resources
|
||||
│ └── vite.config.ts # Vite configuration
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
## 📦 Requirements
|
||||
|
||||
### Backend Requirements
|
||||
|
||||
- PHP ≥ 8.1
|
||||
- MySQL ≥ 8.0
|
||||
- Redis
|
||||
- Required PHP Extensions:
|
||||
- PDO, PDO_MySQL, MySQLi
|
||||
- Redis
|
||||
- cURL, OpenSSL
|
||||
- GD, Fileinfo
|
||||
- MBString, JSON
|
||||
- Event (recommended for better performance)
|
||||
|
||||
### Frontend Requirements
|
||||
|
||||
- Node.js (LTS version recommended)
|
||||
- npm or yarn
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Clone the Project
|
||||
|
||||
This project consists of two independent Git repositories that need to be cloned separately:
|
||||
|
||||
```bash
|
||||
# Create project directory
|
||||
mkdir fastmovieai
|
||||
cd fastmovieai
|
||||
|
||||
# Clone backend repository
|
||||
git clone https://gitee.com/yc_open/ai-short-play.git fastmovie-admin
|
||||
|
||||
# Clone frontend repository
|
||||
git clone https://gitee.com/yc_open/ai-short-play-vue.git fastmovie-vue
|
||||
```
|
||||
|
||||
After cloning, the directory structure will be:
|
||||
```
|
||||
fastmovieai/
|
||||
├── fastmovie-admin/ # Backend repository
|
||||
└── fastmovie-vue/ # Frontend repository
|
||||
```
|
||||
|
||||
### 2. Backend Installation
|
||||
|
||||
#### Using Web Installation Wizard (Recommended)
|
||||
|
||||
**Step 1: Configure Site**
|
||||
|
||||
If using BT Panel:
|
||||
1. Create a site and set the root directory to `fastmovie-admin/public`
|
||||
2. Select PHP version 8.1 or higher
|
||||
|
||||
**Step 2: Access Installation Wizard**
|
||||
|
||||
Visit `http://your-domain.com/install` and follow the wizard to complete installation:
|
||||
|
||||
1. **License Agreement** - Read and agree to the open source license
|
||||
2. **Environment Check** - Automatically detect PHP version, extensions, permissions, etc.
|
||||
3. **Parameter Configuration** - Configure database and Redis connection, set admin account
|
||||
4. **Start Installation** - Automatically complete database initialization and configuration file generation
|
||||
|
||||
**After Installation:**
|
||||
|
||||
The installation wizard will automatically complete the following tasks:
|
||||
- Generate `.env` configuration file (based on `.env.example`)
|
||||
- Automatically configure database connection pool parameters
|
||||
- Generate random PUSH_KEY and PUSH_SECRET (32-character strings)
|
||||
- Synchronize and update PUSH_KEY in `nginx.example`
|
||||
- Create `install.lock` file to prevent duplicate installation
|
||||
|
||||
After installation, the page will prompt you to complete the following operations:
|
||||
|
||||
**1. Configure URL Rewrite Rules**
|
||||
- Copy the entire content of `fastmovie-admin/nginx.example` file
|
||||
- Paste and save in BT Panel's "Site Settings → URL Rewrite"
|
||||
- PUSH_KEY has been automatically updated to the actual generated random value
|
||||
|
||||
**2. Start Backend Service**
|
||||
```bash
|
||||
cd fastmovie-admin
|
||||
php start.php start -d
|
||||
```
|
||||
Or configure process daemon in BT Panel (recommended)
|
||||
|
||||
**3. Delete Installation Directory**
|
||||
```bash
|
||||
# Delete installation directory (Important!)
|
||||
rm -rf public/install
|
||||
```
|
||||
|
||||
⚠️ **Important Notes**:
|
||||
- Change the default admin password immediately after first login
|
||||
- Ensure `.env` file permissions are secure and not exposed to the public
|
||||
- `install.lock` file is used to prevent duplicate installation, do not delete
|
||||
|
||||
#### Manual Installation (Advanced Users)
|
||||
|
||||
If not using the Web installation wizard, you can configure manually:
|
||||
|
||||
```bash
|
||||
cd fastmovie-admin
|
||||
|
||||
# 1. Copy environment configuration file
|
||||
copy .env.example .env
|
||||
|
||||
# 2. Edit .env file and configure the following:
|
||||
# Database configuration
|
||||
# - DATABASE_HOST=127.0.0.1
|
||||
# - DATABASE_PORT=3306
|
||||
# - DATABASE_NAME=your_database
|
||||
# - DATABASE_USERNAME=your_username
|
||||
# - DATABASE_PASSWORD=your_password
|
||||
# - DATABASE_PREFIX=php_
|
||||
#
|
||||
# Database connection pool configuration (required)
|
||||
# - DATABASE_MAX_CONNECTIONS=10
|
||||
# - DATABASE_MIN_CONNECTIONS=1
|
||||
# - DATABASE_WAIT_TIMEOUT=3
|
||||
# - DATABASE_IDLE_TIMEOUT=60
|
||||
# - DATABASE_HEARTBEAT_INTERVAL=50
|
||||
#
|
||||
# Redis configuration
|
||||
# - REDIS_HOST=127.0.0.1
|
||||
# - REDIS_PORT=6379
|
||||
# - REDIS_PASSWORD=
|
||||
# - REDIS_DATABASE=2
|
||||
#
|
||||
# WebSocket push configuration
|
||||
# - PUSH_KEY=generate 32-character random string
|
||||
# - PUSH_SCERET=generate 32-character random string
|
||||
# - PUSH_API_PORT=37000
|
||||
# - PUSH_WSS_PORT=37001
|
||||
|
||||
# 3. Import database
|
||||
mysql -u root -p your_database < database.sql
|
||||
|
||||
# 4. Update PUSH_KEY in nginx.example
|
||||
# Replace /app/PUSH_KEY with /app/your_actual_PUSH_KEY_value
|
||||
```
|
||||
|
||||
### 3. Frontend Installation
|
||||
|
||||
```bash
|
||||
cd fastmovie-vue
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Configure backend API address
|
||||
# Edit .env.development file, set VITE_API_URL
|
||||
```
|
||||
|
||||
### 4. Start Services
|
||||
|
||||
**Backend Service**
|
||||
|
||||
```bash
|
||||
cd fastmovie-admin
|
||||
|
||||
# Development mode
|
||||
php start.php start
|
||||
|
||||
# Daemon mode (production)
|
||||
php start.php start -d
|
||||
|
||||
# Stop service
|
||||
php start.php stop
|
||||
|
||||
# Restart service
|
||||
php start.php restart
|
||||
|
||||
# Check status
|
||||
php start.php status
|
||||
```
|
||||
|
||||
**Frontend Service**
|
||||
|
||||
```bash
|
||||
cd fastmovie-vue
|
||||
|
||||
# Start development server (default port 36310)
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 5. Access the System
|
||||
|
||||
- **Frontend**: http://localhost:36310
|
||||
- **Backend API**: http://localhost:36999
|
||||
- **Default Admin Account**: admin
|
||||
- **Default Password**: 123456
|
||||
|
||||
⚠️ **Security Notice**: Please change the default password immediately after first login!
|
||||
|
||||
## 🔧 Production Deployment
|
||||
|
||||
### BT Panel Deployment (Recommended)
|
||||
|
||||
1. **Create Site**
|
||||
- Create a new site in BT Panel
|
||||
- Set site root directory to `fastmovie-admin/public`
|
||||
- Configure PHP version to 8.1 or higher
|
||||
|
||||
2. **Configure URL Rewrite**
|
||||
|
||||
Add URL rewrite rules in site settings:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Start Backend Service**
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/your-site/fastmovie-admin
|
||||
php start.php start -d
|
||||
```
|
||||
|
||||
4. **Configure Process Daemon**
|
||||
|
||||
Add in BT Panel's "Process Daemon":
|
||||
- Name: FastMovieAI
|
||||
- Start Command: `cd /www/wwwroot/your-site/fastmovie-admin && php start.php start`
|
||||
- Working Directory: `/www/wwwroot/your-site/fastmovie-admin`
|
||||
|
||||
5. **Access Installation Wizard**
|
||||
|
||||
Visit `http://your-domain.com/install` to complete installation configuration.
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
#### Backend Deployment
|
||||
|
||||
1. **Configure Nginx Reverse Proxy**
|
||||
|
||||
Refer to `fastmovie-admin/nginx.example` configuration file:
|
||||
|
||||
```nginx
|
||||
upstream fastmovie {
|
||||
server 127.0.0.1:36999;
|
||||
keepalive 10240;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
root /path/to/fastmovie-admin/public;
|
||||
|
||||
location / {
|
||||
proxy_pass http://fastmovie;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Start Backend Service**
|
||||
|
||||
```bash
|
||||
cd fastmovie-admin
|
||||
php start.php start -d
|
||||
```
|
||||
|
||||
3. **Configure Process Daemon**
|
||||
|
||||
Use systemd or supervisor to manage processes and ensure automatic service restart.
|
||||
|
||||
#### Frontend Deployment
|
||||
|
||||
1. **Build for Production**
|
||||
|
||||
```bash
|
||||
cd fastmovie-vue
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. **Deploy Build Files**
|
||||
|
||||
After building, deploy the generated files to the `fastmovie-admin/public/assets/` directory, or configure an independent static file server.
|
||||
|
||||
3. **Configure Nginx**
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
root /path/to/fastmovie-admin/public;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:36999;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Development Documentation
|
||||
|
||||
- [Backend Development Guide](./docs/backend-development.md) - PHP/Webman backend development guide
|
||||
- [Frontend Development Guide](./docs/frontend-development.md) - Vue3/TypeScript frontend development guide
|
||||
|
||||
### Plugin System
|
||||
|
||||
FastMovieAI adopts a modular plugin architecture. Core plugins include:
|
||||
|
||||
- **user** - User management and authentication
|
||||
- **finance** - Payment and financial management
|
||||
- **marketing** - Marketing and promotion tools
|
||||
- **article** - Content management system
|
||||
- **shortplay** - Short drama creation core functionality
|
||||
- **model** - AI model management
|
||||
- **notification** - Notification and message push
|
||||
- **control** - Platform control and configuration
|
||||
|
||||
Each plugin is an independent functional module that can be enabled or disabled as needed.
|
||||
|
||||
### Common Commands
|
||||
|
||||
**Backend Commands**
|
||||
|
||||
```bash
|
||||
# View all commands
|
||||
php webman
|
||||
|
||||
# Database migration (if supported)
|
||||
php webman migrate
|
||||
|
||||
# Clear cache
|
||||
php webman cache:clear
|
||||
```
|
||||
|
||||
**Frontend Commands**
|
||||
|
||||
```bash
|
||||
# Development server
|
||||
npm run dev
|
||||
|
||||
# Production build
|
||||
npm run build
|
||||
|
||||
# Preview build result
|
||||
npm run preview
|
||||
|
||||
# Type checking
|
||||
vue-tsc --noEmit
|
||||
```
|
||||
|
||||
### Port Configuration
|
||||
|
||||
- **Backend Service**: 36999 (configurable via SERVER_PORT in .env)
|
||||
- **Frontend Development**: 36310
|
||||
- **WebSocket Push**: 37000
|
||||
- **WebSocket WSS**: 37001
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome all forms of contribution, including but not limited to:
|
||||
|
||||
1. 🐛 Submit bug reports
|
||||
2. 💡 Propose new feature suggestions
|
||||
3. 📝 Improve documentation
|
||||
4. 🔧 Submit code fixes or new features
|
||||
|
||||
### Contribution Process
|
||||
|
||||
1. Fork the corresponding repository (backend or frontend)
|
||||
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Submit a Pull Request
|
||||
|
||||
**Note**: Backend and frontend are independent repositories. Please submit PRs to the corresponding repository based on your changes.
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
- Backend code follows PSR-12 coding standards
|
||||
- Frontend code uses ESLint and Prettier formatting
|
||||
- Commit messages should clearly describe changes
|
||||
- Add necessary code comments and documentation
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the [Apache License 2.0](./LICENSE).
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Live Demo](https://fastmovie.ai)
|
||||
- [Backend Repository (Gitee)](https://gitee.com/yc_open/ai-short-play)
|
||||
- [Frontend Repository (Gitee)](https://gitee.com/yc_open/ai-short-play-vue)
|
||||
- [Backend Development Guide](./docs/backend-development.md)
|
||||
- [Frontend Development Guide](./docs/frontend-development.md)
|
||||
- [Issue Tracker](https://gitee.com/yc_open/ai-short-play/issues)
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
This project is for learning and research purposes only. Users are responsible for all consequences arising from the use of this project. Please comply with relevant laws and regulations and do not use it for illegal purposes.
|
||||
|
||||
## 💬 Contact
|
||||
|
||||
For questions or suggestions, please contact us through:
|
||||
|
||||
- Submit Issue:
|
||||
- Backend issues: [ai-short-play Issues](https://gitee.com/yc_open/ai-short-play/issues)
|
||||
- Frontend issues: [ai-short-play-vue Issues](https://gitee.com/yc_open/ai-short-play-vue/issues)
|
||||
- Email: 416716328@qq.com
|
||||
- Live Demo: [https://fastmovie.ai](https://fastmovie.ai)
|
||||
|
||||
### Join the Community
|
||||
|
||||
<div align="center">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="./readme_assets/qrcode_2.png" width="200" alt="User Community" />
|
||||
<br />
|
||||
<b>User Community</b>
|
||||
<br />
|
||||
<span>Scan to join and communicate with creators</span>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./readme_assets/qrcode_1.png" width="200" alt="Business Cooperation" />
|
||||
<br />
|
||||
<b>Technical Consultation & Business Cooperation</b>
|
||||
<br />
|
||||
<span>Scan to contact for cooperation</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**If this project helps you, please give us a ⭐️ Star!**
|
||||
|
||||
Made with ❤️ by FastMovieAI Team
|
||||
|
||||
</div>
|
||||
@@ -1,39 +0,0 @@
|
||||
DEBUG = false
|
||||
|
||||
SERVER_NAME = AiShortPlay
|
||||
SERVER_PORT = 36300
|
||||
SERVER_ADMIN_PATH = admin
|
||||
|
||||
DATABASE_HOST = 127.0.0.1
|
||||
DATABASE_PORT = 3306
|
||||
DATABASE_NAME =
|
||||
DATABASE_USERNAME =
|
||||
DATABASE_PASSWORD =
|
||||
DATABASE_CHARSET = utf8mb4
|
||||
DATABASE_PREFIX = php_
|
||||
DATABASE_MAX_CONNECTIONS = 10
|
||||
DATABASE_MIN_CONNECTIONS = 1
|
||||
DATABASE_WAIT_TIMEOUT = 3
|
||||
DATABASE_IDLE_TIMEOUT = 60
|
||||
DATABASE_HEARTBEAT_INTERVAL = 50
|
||||
DATABASE_MAX_CONNECTIONS = 100
|
||||
DATABASE_MIN_CONNECTIONS = 20
|
||||
DATABASE_WAIT_TIMEOUT = 20
|
||||
DATABASE_IDLE_TIMEOUT = 60
|
||||
DATABASE_HEARTBEAT_INTERVAL = 50
|
||||
|
||||
REDIS_HOST = 127.0.0.1
|
||||
REDIS_PORT = 6379
|
||||
REDIS_PASSWORD =
|
||||
REDIS_DATABASE = 4
|
||||
REDIS_MAX_CONNECTIONS = 100
|
||||
REDIS_MIN_CONNECTIONS = 20
|
||||
REDIS_WAIT_TIMEOUT = 20
|
||||
REDIS_IDLE_TIMEOUT = 60
|
||||
REDIS_HEARTBEAT_INTERVAL = 50
|
||||
|
||||
|
||||
PUSH_KEY =
|
||||
PUSH_SCERET =
|
||||
PUSH_API_PORT = 36301
|
||||
PUSH_WSS_PORT = 36302
|
||||
@@ -1,22 +0,0 @@
|
||||
/runtime
|
||||
/.idea
|
||||
/.vscode
|
||||
*.log
|
||||
.env
|
||||
/tests/tmp
|
||||
/tests/.phpunit.result.cache
|
||||
*.pem
|
||||
/uploads
|
||||
/public/uploads
|
||||
.well-known
|
||||
*.ini
|
||||
/config/**/*.json
|
||||
/update/VERSION
|
||||
# Kiro IDE
|
||||
.kiro
|
||||
.vscode
|
||||
/vendor
|
||||
/public/drama
|
||||
/public/install
|
||||
/public/assets
|
||||
/public/fastmovie
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<html>
|
||||
<style>
|
||||
.btlink {
|
||||
color: #20a53a;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<head><title>404 Not Found</title></head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr>
|
||||
<div style="text-align: center;font-size: 15px" >Power by <a class="btlink" href="https://www.bt.cn/?from=404" target="_blank">堡塔 (免费,高效和安全的托管控制面板)</a></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM php:8.1-cli
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libpng-dev libjpeg-dev libfreetype6-dev libzip-dev libonig-dev \
|
||||
libcurl4-openssl-dev libssl-dev unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install -j$(nproc) pdo_mysql mysqli pcntl posix gd zip bcmath mbstring
|
||||
|
||||
RUN pecl install redis && docker-php-ext-enable redis
|
||||
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
||||
|
||||
EXPOSE 36300 36301 36302
|
||||
|
||||
CMD ["php", "start.php", "start"]
|
||||
@@ -1,30 +0,0 @@
|
||||
LoongAdmin遵循Apache2开源协议发布,并提供免费使用。
|
||||
版权所有Copyright © 2024 by Renloong (https://www.renloong.com)
|
||||
All rights reserved。
|
||||
|
||||
Apache Licence是著名的非盈利开源组织Apache采用的协议。
|
||||
该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
|
||||
允许代码修改,再作为开源或商业软件发布。需要满足
|
||||
的条件:
|
||||
1. 需要给代码的用户一份Apache Licence ;
|
||||
2. 如果你修改了代码,需要在被修改的文件中说明;
|
||||
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
|
||||
带有原来代码中的协议,商标,专利声明和其他原来作者规
|
||||
定需要包含的说明;
|
||||
4. 如果再发布的产品中包含一个Notice文件,则在Notice文
|
||||
件中需要带有本协议内容。你可以在Notice中增加自己的
|
||||
许可,但不可以表现为对Apache Licence构成更改。
|
||||
具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,50 +0,0 @@
|
||||
# LoongAdmin
|
||||
|
||||
#### 教程
|
||||
|
||||
```binsh
|
||||
composer create-project loong/admin
|
||||
```
|
||||
|
||||
```binsh
|
||||
git clone https://github.com/RenLoong/loong-admin.git
|
||||
```
|
||||
|
||||
手动导入根目录下的database.sql文件
|
||||
|
||||
将根目录的.env.example修改为.env
|
||||
|
||||
```ini
|
||||
; 进程名
|
||||
SERVER_NAME = LoongAdmin
|
||||
; 监听端口号
|
||||
SERVER_PORT = 36999
|
||||
; 自定义后台入口
|
||||
SERVER_ADMIN_PATH = admin
|
||||
; 数据库配置
|
||||
DATABASE_HOST = 127.0.0.1
|
||||
DATABASE_PORT = 3306
|
||||
DATABASE_NAME =
|
||||
DATABASE_USERNAME =
|
||||
DATABASE_PASSWORD =
|
||||
DATABASE_CHARSET = utf8mb4
|
||||
DATABASE_PREFIX = php_
|
||||
; Redis配置
|
||||
REDIS_HOST = 127.0.0.1
|
||||
REDIS_PORT = 6379
|
||||
REDIS_PASSWORD =
|
||||
REDIS_DATABASE = 2
|
||||
; webman/push插件配置
|
||||
PUSH_KEY = 32位字符串
|
||||
PUSH_SCERET = 32位字符串
|
||||
PUSH_API_PORT = 37000
|
||||
PUSH_WSS_PORT = 37001
|
||||
```
|
||||
|
||||
后台账号:admin
|
||||
后台密码:123456
|
||||
|
||||
#### 联系我
|
||||
|
||||
- QQ群:
|
||||
[826867889](https://h5.qun.qq.com/h5/qun-share-page/?_wv=1027&k=FhjcGB-hY66iJSPySAoJs-vxtT0zqbtR&authKey=qdPxxwMiApBT4tU8mk5dubgDODo8tSYemfW36kY6k0uT5lPFgS9nQCSQn4niYJlm&market_channel_source=826867889_1&noverify=0&group_code=826867889)
|
||||
@@ -1,2 +0,0 @@
|
||||
1034
|
||||
0.0.34
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app;
|
||||
|
||||
use app\expose\trait\Json;
|
||||
use support\Request;
|
||||
|
||||
class Basic
|
||||
{
|
||||
protected $model;
|
||||
use Json;
|
||||
/**
|
||||
* 更新指定字段
|
||||
* @method POST
|
||||
*/
|
||||
public function indexUpdateField(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$field = $request->post('field');
|
||||
$value = $request->post('value');
|
||||
$model = $this->model->where(['id' => $id])->find();
|
||||
if (!$model) {
|
||||
return $this->fail('数据不存在');
|
||||
}
|
||||
$model->{$field} = $value;
|
||||
if ($model->save()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
/**
|
||||
* 更新状态字段
|
||||
* @method POST
|
||||
*/
|
||||
public function indexUpdateState(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$field = $request->post('field');
|
||||
$value = $request->post('value');
|
||||
$model = $this->model->where(['id' => $id])->find();
|
||||
if (!$model) {
|
||||
return $this->fail('数据不存在');
|
||||
}
|
||||
$model->{$field} = $value;
|
||||
if ($model->save()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
[
|
||||
{
|
||||
"title": "Dashboard",
|
||||
"icon": "House",
|
||||
"path": "Index",
|
||||
"sort": 0,
|
||||
"children": [
|
||||
{
|
||||
"title": "Console",
|
||||
"icon": "ElementPlus",
|
||||
"path": "Index/control",
|
||||
"component": "databoard"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Functional",
|
||||
"icon": "Stopwatch",
|
||||
"path": "Functional",
|
||||
"children": [
|
||||
{
|
||||
"title": "Administrator",
|
||||
"icon": "User",
|
||||
"path": "Admin/group",
|
||||
"component": "table",
|
||||
"children": [
|
||||
{
|
||||
"title": "Staff List",
|
||||
"path": "Admin/index",
|
||||
"component": "table",
|
||||
"children": [
|
||||
{
|
||||
"title": "Create Administrator",
|
||||
"path": "Admin/create",
|
||||
"show": false,
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Edit Administrator",
|
||||
"show": false,
|
||||
"path": "Admin/update",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Delete Administrator",
|
||||
"show": false,
|
||||
"path": "Admin/delete"
|
||||
},
|
||||
{
|
||||
"title": "Update Status",
|
||||
"show": false,
|
||||
"path": "Admin/indexUpdateState"
|
||||
},
|
||||
{
|
||||
"title": "Move Role",
|
||||
"component": "form",
|
||||
"show": false,
|
||||
"path": "Admin/moveRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Role Management",
|
||||
"icon": "PriceTag",
|
||||
"path": "AdminRole/group",
|
||||
"component": "table",
|
||||
"children": [
|
||||
{
|
||||
"title": "Role List",
|
||||
"path": "AdminRole/index",
|
||||
"component": "table",
|
||||
"children": [
|
||||
{
|
||||
"title": "Create Role",
|
||||
"path": "AdminRole/create",
|
||||
"show": false,
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Edit Role",
|
||||
"show": false,
|
||||
"path": "AdminRole/update",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Delete Role",
|
||||
"show": false,
|
||||
"path": "AdminRole/delete"
|
||||
},
|
||||
{
|
||||
"title": "Update Status",
|
||||
"show": false,
|
||||
"path": "AdminRole/indexUpdateState"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Systems",
|
||||
"icon": "Setting",
|
||||
"path": "System",
|
||||
"children": [
|
||||
{
|
||||
"title": "Basic Settings",
|
||||
"icon": "Setting",
|
||||
"path": "System/basic",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Upload Settings",
|
||||
"icon": "UploadFilled",
|
||||
"path": "System/upload",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "SMS Settings",
|
||||
"icon": "Message",
|
||||
"path": "System/sms",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Payment Management",
|
||||
"icon": "Wallet",
|
||||
"path": "Payment/group",
|
||||
"show": false,
|
||||
"children": [
|
||||
{
|
||||
"title": "Payment Settings",
|
||||
"path": "Payment/index",
|
||||
"show": false,
|
||||
"component": "payment-form"
|
||||
},
|
||||
{
|
||||
"title": "Payment Template",
|
||||
"path": "PaymentTemplate/index",
|
||||
"component": "table",
|
||||
"show": false,
|
||||
"children": [
|
||||
{
|
||||
"title": "Create Payment Template",
|
||||
"path": "PaymentTemplate/create",
|
||||
"show": false,
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Edit Payment Template",
|
||||
"path": "PaymentTemplate/update",
|
||||
"show": false,
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Delete Payment Template",
|
||||
"path": "PaymentTemplate/delete",
|
||||
"show": false,
|
||||
"component": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Public Permissions",
|
||||
"path": "Public",
|
||||
"show": false,
|
||||
"children": [
|
||||
{
|
||||
"title": "Edit Profile",
|
||||
"show": false,
|
||||
"path": "Admin/updateSelf",
|
||||
"component": "form"
|
||||
},
|
||||
{
|
||||
"title": "Get Profile",
|
||||
"show": false,
|
||||
"path": "Admin/getSelfInfo",
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Logout",
|
||||
"show": false,
|
||||
"path": "Public/outLogin",
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Get Upload Categories",
|
||||
"path": "Uploads/getUploadClassify",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Edit Upload Category",
|
||||
"path": "Uploads/saveClassify",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Delete Upload Category",
|
||||
"path": "Uploads/deleteClassify",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Upload File",
|
||||
"path": "Uploads/upload",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Delete Upload File",
|
||||
"path": "Uploads/deleteUploads",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Edit Upload File",
|
||||
"path": "Uploads/updateUploads",
|
||||
"show": false,
|
||||
"component": ""
|
||||
},
|
||||
{
|
||||
"title": "Get Upload File List",
|
||||
"path": "Uploads/getList",
|
||||
"show": false,
|
||||
"component": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\api;
|
||||
|
||||
use app\expose\api\Install as ApiInstall;
|
||||
|
||||
class Install
|
||||
{
|
||||
use ApiInstall;
|
||||
public function __construct()
|
||||
{
|
||||
$this->path = __DIR__;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 自定义Admin入口
|
||||
* @authors 余心(RenLoong)
|
||||
* @home https://github.com/RenLoong/loong-admin
|
||||
*/
|
||||
use Webman\Route;
|
||||
|
||||
$admin_path = getenv('SERVER_ADMIN_PATH');
|
||||
if ($admin_path && $admin_path != 'admin') {
|
||||
Route::disableDefaultRoute('', 'admin');
|
||||
$controllersClass = glob(app_path('admin') . '/controller/*Controller.php');
|
||||
$len = count($controllersClass);
|
||||
$routes = [];
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$value = $controllersClass[$i];
|
||||
$controllerName = str_replace('Controller', '', basename($value, '.php'));
|
||||
$classStr = 'app' . str_replace([app_path(), '.php', '/'], ['', '', '\\'], $value);
|
||||
$reflection = new \ReflectionClass('\\'.$classStr);
|
||||
|
||||
// 忽略抽象类、接口
|
||||
if ($reflection->isAbstract() || $reflection->isInterface()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
$actionsLen = count($actions);
|
||||
for ($n = 0; $n < $actionsLen; $n++) {
|
||||
$name = $actions[$n]->name;
|
||||
if (!str_starts_with($name, '__')) {
|
||||
$routes["/{$controllerName}/{$name}"] = [$classStr, $name];
|
||||
}
|
||||
}
|
||||
}
|
||||
$controllersVersionClass = glob(app_path('admin') . '/controller/**/*Controller.php');
|
||||
if (!empty($controllersVersionClass)) {
|
||||
$len = count($controllersVersionClass);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$value = $controllersVersionClass[$i];
|
||||
$version=basename(dirname($value));
|
||||
$controllerName = str_replace('Controller', '', basename($value, '.php'));
|
||||
$classStr = 'app' . str_replace([app_path(), '.php', '/'], ['', '', '\\'], $value);
|
||||
$reflection = new \ReflectionClass('\\' . $classStr);
|
||||
|
||||
// 忽略抽象类、接口
|
||||
if ($reflection->isAbstract() || $reflection->isInterface()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
$actionsLen = count($actions);
|
||||
for ($n = 0; $n < $actionsLen; $n++) {
|
||||
$name = $actions[$n]->name;
|
||||
|
||||
if (!str_starts_with($name, '__')) {
|
||||
$routes["/{$version}/{$controllerName}/{$name}"] = [$classStr, $name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Route::group('/' . $admin_path, function ()use($routes) {
|
||||
foreach ($routes as $key => $value) {
|
||||
if(in_array($value[1],['indexUpdateField','indexUpdateState'])){
|
||||
Route::post($key,$value);
|
||||
}else if(strpos($value[1],'GetTable')){
|
||||
Route::get($key,$value);
|
||||
}else{
|
||||
Route::any($key,$value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,601 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\build\builder\TableBuilder;
|
||||
use app\expose\enum\Action;
|
||||
use app\expose\enum\ResponseEvent;
|
||||
use app\expose\enum\State;
|
||||
use app\expose\enum\Week;
|
||||
use app\model\Admin;
|
||||
use app\model\AdminRole;
|
||||
use app\validate\Admin as ValidateAdmin;
|
||||
use support\Request;
|
||||
use think\facade\Db;
|
||||
|
||||
class AdminController extends Basic
|
||||
{
|
||||
public function indexGetTable(Request $request)
|
||||
{
|
||||
$builder = new TableBuilder;
|
||||
$builder->addAction('操作', [
|
||||
'width' => '200px',
|
||||
'fixed' => 'right'
|
||||
]);
|
||||
$builder->addTableAction('编辑', [
|
||||
'path' => 'Admin/update',
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'title' => '编辑《{nickname}》管理员'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addTableAction('删除', [
|
||||
'model' => Action::COMFIRM['value'],
|
||||
'path' => 'Admin/delete',
|
||||
'where' => [
|
||||
['is_system', '!=', 1]
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'error',
|
||||
'message' => '确定要删除《{nickname}》管理员吗?',
|
||||
'confirmButtonClass' => 'el-button--danger'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'danger',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addFooter();
|
||||
$builder->addFooterAction('移动到', [
|
||||
'model' => Action::DIALOG['value'],
|
||||
'path' => 'Admin/moveRole',
|
||||
'props' => [
|
||||
'title' => '移动到',
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addHeader();
|
||||
$builder->addHeaderAction('创建管理员', [
|
||||
'path' => 'Admin/create',
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
'title' => '创建管理员'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$formBuilder = new FormBuilder;
|
||||
$formBuilder->add('username', '账号', 'input', '', [
|
||||
'col' => [
|
||||
'xs' => 24,
|
||||
'sm' => 12,
|
||||
'md' => 8,
|
||||
'lg' => 6,
|
||||
'xl' => 4
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => '账号搜索',
|
||||
'clearable' => true
|
||||
]
|
||||
]);
|
||||
$formBuilder->add('mobile', '手机号', 'input', '', [
|
||||
'col' => [
|
||||
'xs' => 24,
|
||||
'sm' => 12,
|
||||
'md' => 8,
|
||||
'lg' => 6,
|
||||
'xl' => 4
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => '手机号搜索',
|
||||
'clearable' => true
|
||||
]
|
||||
]);
|
||||
$builder->addScreen($formBuilder);
|
||||
$builder->add('id', 'ID', [
|
||||
'props' => [
|
||||
'width' => '80px'
|
||||
]
|
||||
]);
|
||||
$builder->add('userinfo', '管理员', [
|
||||
'component' => [
|
||||
'name' => 'table-userinfo',
|
||||
'props' => [
|
||||
'nickname' => 'nickname',
|
||||
'avatar' => 'headimg',
|
||||
'info' => 'username',
|
||||
'nicknameTags' => [
|
||||
[
|
||||
'field' => 'new_text',
|
||||
'props' => [
|
||||
'type' => 'danger',
|
||||
'size' => 'small'
|
||||
]
|
||||
],
|
||||
[
|
||||
'field' => 'new_text1',
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
],
|
||||
'tags' => [
|
||||
[
|
||||
'field' => 'role_name',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'minWidth' => '300px'
|
||||
]
|
||||
]);
|
||||
$builder->add('contact', '联系方式', [
|
||||
'props' => [
|
||||
'width' => '280px'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'table-times',
|
||||
'props' => [
|
||||
'group' => [
|
||||
[
|
||||
'field' => 'mobile',
|
||||
'label' => '手机号'
|
||||
],
|
||||
[
|
||||
'field' => 'email',
|
||||
'label' => '邮箱'
|
||||
],
|
||||
[
|
||||
'field' => 'wx_openid',
|
||||
'label' => 'OpenID'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->add('allow_week', '工作日', [
|
||||
'component' => [
|
||||
'name' => 'tag',
|
||||
'options' => Week::getOptions()
|
||||
],
|
||||
'props' => [
|
||||
'width' => '240px'
|
||||
]
|
||||
]);
|
||||
$builder->add('allow_work', '工作时间', [
|
||||
'component' => [
|
||||
'name' => 'tag',
|
||||
'options' => [
|
||||
[
|
||||
'index' => 0,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
],
|
||||
[
|
||||
'index' => 1,
|
||||
'props' => [
|
||||
'type' => 'danger'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '200px'
|
||||
]
|
||||
]);
|
||||
$builder->add('online_time', '活动', [
|
||||
'props' => [
|
||||
'width' => '200px'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'table-times',
|
||||
'props' => [
|
||||
'group' => [
|
||||
[
|
||||
'field' => 'online_time',
|
||||
'label' => '在线'
|
||||
],
|
||||
[
|
||||
'field' => 'login_time',
|
||||
'label' => '登录'
|
||||
],
|
||||
[
|
||||
'component' => 'tag',
|
||||
'field' => 'login_ip',
|
||||
'label' => '登录IP',
|
||||
'props' => [
|
||||
'size' => 'small',
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->add('state', '状态', [
|
||||
'component' => [
|
||||
'name' => 'switch',
|
||||
'api' => 'Admin/indexUpdateState',
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '100px'
|
||||
]
|
||||
]);
|
||||
$builder->add('create_time', '时间', [
|
||||
'props' => [
|
||||
'width' => '200px'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'table-times',
|
||||
'props' => [
|
||||
'group' => [
|
||||
[
|
||||
'field' => 'create_time',
|
||||
'label' => '创建'
|
||||
],
|
||||
[
|
||||
'field' => 'update_time',
|
||||
'label' => '更新'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder = $builder->builder();
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
$limit = $request->get('limit', 10);
|
||||
$where = [];
|
||||
$username = $request->get('username');
|
||||
if ($username) {
|
||||
$where[] = ['username', 'like', "%{$username}%"];
|
||||
}
|
||||
$mobile = $request->get('mobile');
|
||||
if ($mobile) {
|
||||
$where[] = ['mobile', 'like', "%{$mobile}%"];
|
||||
}
|
||||
$list = Admin::alias('a')->where($where)
|
||||
->join('admin_role b', 'b.id=a.role_id')
|
||||
->field('a.*,b.name as role_name,b.is_system')
|
||||
->order('a.id desc')->paginate($limit)->each(function ($item) {
|
||||
$item->username = "UserName:" . $item->username;
|
||||
$item->new_text = '新';
|
||||
$item->new_text1 = 'T';
|
||||
$allow_work = [];
|
||||
$allow_work[] = $item->allow_time_start;
|
||||
$allow_work[] = $item->allow_time_end;
|
||||
$item->allow_work = $allow_work;
|
||||
});
|
||||
return $this->resData($list);
|
||||
}
|
||||
public function indexUpdateState(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$field = $request->post('field');
|
||||
$value = $request->post('value');
|
||||
$Admin = Admin::where(['id' => $id])->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
$Role = AdminRole::where(['id' => $Admin->role_id])->find();
|
||||
if ($Role && $Role->is_system && !$value) {
|
||||
return $this->fail('系统管理员不允许操作');
|
||||
}
|
||||
$Admin->{$field} = $value;
|
||||
if ($Admin->save()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$Admin = Admin::where(['id' => $id])->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
$Role = AdminRole::where(['id' => $Admin->role_id])->find();
|
||||
if ($Role && $Role->is_system) {
|
||||
return $this->fail('系统管理员不允许操作');
|
||||
}
|
||||
if ($Admin->delete()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
public function create(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
try {
|
||||
$validate = new ValidateAdmin;
|
||||
$validate->check($D);
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
$Role = AdminRole::where(['id' => $D['role_id']])->find();
|
||||
if (!$Role) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
$Admin = new Admin;
|
||||
$Admin->role_id = $D['role_id'];
|
||||
$Admin->username = $D['username'];
|
||||
$Admin->nickname = $D['nickname'];
|
||||
$Admin->headimg = $D['headimg'];
|
||||
$Admin->mobile = $D['mobile'];
|
||||
$Admin->email = $D['email'];
|
||||
$Admin->wx_openid = $D['wx_openid'];
|
||||
if ($D['password']) {
|
||||
$Admin->password = $D['password'];
|
||||
}
|
||||
if ($Role->is_system) {
|
||||
$Admin->allow_time_start = '00:00:00';
|
||||
$Admin->allow_time_end = '23:59:59';
|
||||
$Admin->allow_week = Week::getValues();
|
||||
} else {
|
||||
$Admin->allow_time_start = $D['allow_time'][0];
|
||||
$Admin->allow_time_end = $D['allow_time'][1];
|
||||
$Admin->allow_week = $D['allow_week'];
|
||||
}
|
||||
if ($Admin->save()) {
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$builder = $this->getFormBuilder();
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function update(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
try {
|
||||
$validate = new ValidateAdmin;
|
||||
$validate->check($D);
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
$Admin = Admin::where(['id' => $D['id']])->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
$Role = AdminRole::where(['id' => $D['role_id']])->find();
|
||||
if (!$Role) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
$Admin->role_id = $D['role_id'];
|
||||
$Admin->username = $D['username'];
|
||||
$Admin->nickname = $D['nickname'];
|
||||
$Admin->headimg = $D['headimg'];
|
||||
$Admin->mobile = $D['mobile'];
|
||||
$Admin->email = $D['email'];
|
||||
$Admin->wx_openid = $D['wx_openid'];
|
||||
if ($D['password']) {
|
||||
$Admin->password = $D['password'];
|
||||
}
|
||||
$Admin->allow_time_start = $D['allow_time'][0];
|
||||
$Admin->allow_time_end = $D['allow_time'][1];
|
||||
$Admin->allow_week = $D['allow_week'];
|
||||
if ($Admin->save()) {
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$id = $request->get('id');
|
||||
$Admin = Admin::where(['id' => $id])->withoutField('password')->find();
|
||||
$Role = AdminRole::where(['id' => $Admin->role_id])->find();
|
||||
if (!$Role) {
|
||||
$Admin->role_id = '';
|
||||
}
|
||||
$is_system = $Role && $Role->is_system;
|
||||
$builder = $this->getFormBuilder($is_system);
|
||||
$Admin->allow_time = [
|
||||
$Admin->allow_time_start,
|
||||
$Admin->allow_time_end
|
||||
];
|
||||
$builder->setData($Admin->toArray());
|
||||
return $this->resData($builder);
|
||||
}
|
||||
private function getFormBuilder($is_system = false)
|
||||
{
|
||||
$builder = new FormBuilder;
|
||||
$builder->add('role_id', '所属角色', 'cascader', null, [
|
||||
'required' => true,
|
||||
'props' => [
|
||||
'options' => AdminRole::options(),
|
||||
'filterable' => true,
|
||||
'props' => [
|
||||
'checkStrictly' => true,
|
||||
'emitPath' => false
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->add('username', '账号', 'input', '', [
|
||||
'required' => true,
|
||||
'props' => [
|
||||
'maxlength' => 30,
|
||||
'show-word-limit' => true
|
||||
]
|
||||
]);
|
||||
$builder->add('password', '密码', 'input', '', [
|
||||
'props' => [
|
||||
'placeholder' => '不修改密码请留空',
|
||||
'maxlength' => 30,
|
||||
'show-word-limit' => true
|
||||
]
|
||||
]);
|
||||
$builder->add('nickname', '昵称', 'input', '', [
|
||||
'required' => true,
|
||||
'maxlength' => 30,
|
||||
'show-word-limit' => true
|
||||
]);
|
||||
$builder->add('headimg', '头像', 'bundle', '', [
|
||||
'props' => [
|
||||
'accept' => 'image/*',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('mobile', '手机号', 'input', '', [
|
||||
'props' => [
|
||||
'maxlength' => 11,
|
||||
'show-word-limit' => true
|
||||
]
|
||||
]);
|
||||
$builder->add('email', '邮箱', 'input', '', [
|
||||
'props' => [
|
||||
'maxlength' => 50,
|
||||
'show-word-limit' => true
|
||||
]
|
||||
]);
|
||||
$SystemRoleId = AdminRole::systemRole('id');
|
||||
$builder->add('allow_time', '工作时间范围', 'time-picker', $is_system ? ['00:00:00', '23:59:59'] : ['08:00:00', '18:00:00'], [
|
||||
'where' => [
|
||||
['role_id', 'not in', $SystemRoleId]
|
||||
],
|
||||
'props' => [
|
||||
'is-range' => true,
|
||||
'value-format' => 'HH:mm:ss',
|
||||
'disabled' => $is_system
|
||||
]
|
||||
]);
|
||||
$builder->add('allow_week', '工作日', 'checkbox', [], [
|
||||
'options' => Week::getOptions(),
|
||||
'where' => [
|
||||
['role_id', 'not in', $SystemRoleId]
|
||||
],
|
||||
'subProps' => [
|
||||
'border' => true,
|
||||
'disabled' => $is_system
|
||||
]
|
||||
]);
|
||||
$builder->add('wx_openid', 'OpenID', 'input', '', []);
|
||||
return $builder;
|
||||
}
|
||||
public function moveRole(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
if (!$D['role_id']) {
|
||||
return $this->fail('请选择角色');
|
||||
}
|
||||
$Role = AdminRole::where(['id' => $D['role_id']])->find();
|
||||
if (!$Role) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
$ids = explode(',', $D['id']);
|
||||
$Admin = Admin::whereIn('id', $ids)->select();
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($Admin as $item) {
|
||||
$item->role_id = $D['role_id'];
|
||||
$item->save();
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Throwable $th) {
|
||||
Db::rollback();
|
||||
return $this->exception($th);
|
||||
}
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
$builder = new FormBuilder(null,null,[
|
||||
'labelPosition' => 'right',
|
||||
'label-width' => "400px",
|
||||
'class' => 'w-80 mx-auto',
|
||||
'size' => 'large',
|
||||
]);
|
||||
$builder->add('role_id', '所属角色', 'cascader', null, [
|
||||
'required' => true,
|
||||
'props' => [
|
||||
'options' => AdminRole::options(),
|
||||
'filterable' => true,
|
||||
'props' => [
|
||||
'checkStrictly' => true,
|
||||
'emitPath' => false
|
||||
]
|
||||
]
|
||||
]);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function updateSelf(Request $request)
|
||||
{
|
||||
$id = $request->admin_uid;
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$D['id'] = $id;
|
||||
try {
|
||||
$validate = new ValidateAdmin;
|
||||
$validate->scene('self')->check($D);
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
$Admin = Admin::where(['id' => $D['id']])->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
$Admin->username = $D['username'];
|
||||
$Admin->nickname = $D['nickname'];
|
||||
$Admin->headimg = $D['headimg'];
|
||||
$Admin->mobile = $D['mobile'];
|
||||
$Admin->email = $D['email'];
|
||||
$Admin->wx_openid = $D['wx_openid'];
|
||||
if ($D['password']) {
|
||||
$Admin->password = $D['password'];
|
||||
}
|
||||
if ($Admin->save()) {
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$Admin = Admin::where(['id' => $id])->withoutField('password')->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
$builder = $this->getFormBuilder();
|
||||
$builder->remove('role_id');
|
||||
$builder->remove('allow_time');
|
||||
$builder->remove('allow_week');
|
||||
$builder->setData($Admin->toArray());
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function getSelfInfo(Request $request)
|
||||
{
|
||||
$id = $request->admin_uid;
|
||||
$Admin = Admin::where(['id' => $id])->withoutField('password')->find();
|
||||
if (!$Admin) {
|
||||
return $this->fail('管理员不存在');
|
||||
}
|
||||
return $this->resData(Admin::getTokenInfo($Admin));
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\ComponentBuilder;
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\build\builder\TableBuilder;
|
||||
use app\expose\enum\Action;
|
||||
use app\expose\enum\ResponseEvent;
|
||||
use app\expose\enum\State;
|
||||
use app\expose\helper\Menus;
|
||||
use app\model\Admin;
|
||||
use app\model\AdminRole;
|
||||
use support\Request;
|
||||
|
||||
class AdminRoleController extends Basic
|
||||
{
|
||||
public function indexGetTable(Request $request)
|
||||
{
|
||||
$builder = new TableBuilder([
|
||||
'rowKey' => 'id',
|
||||
'api' => 'AdminRole/index',
|
||||
'lazy' => true,
|
||||
'treeProps' => [
|
||||
'children' => 'children',
|
||||
'hasChildren' => 'hasChildren'
|
||||
]
|
||||
]);
|
||||
$builder->addAction('操作', [
|
||||
'width' => '200px',
|
||||
'fixed' => 'right'
|
||||
]);
|
||||
$builder->addTableAction('编辑', [
|
||||
'path' => 'AdminRole/update',
|
||||
'where' => [
|
||||
['is_system', '!=', 1]
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'title' => '编辑《{name}》角色'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addTableAction('删除', [
|
||||
'model' => Action::COMFIRM['value'],
|
||||
'path' => 'AdminRole/delete',
|
||||
'where' => [
|
||||
['is_system', '!=', 1]
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'error',
|
||||
'message' => '确定要删除《{name}》角色吗?',
|
||||
'confirmButtonClass' => 'el-button--danger'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'danger',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addHeader();
|
||||
$builder->addHeaderAction('创建角色', [
|
||||
'path' => 'AdminRole/create',
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
'title' => '创建角色'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->add('id', 'ID', [
|
||||
'props' => [
|
||||
'width' => '80px'
|
||||
]
|
||||
]);
|
||||
$builder->add('name', '角色名称');
|
||||
$builder->add('admin_num', '人数', [
|
||||
'props' => [
|
||||
'width' => '120px'
|
||||
]
|
||||
]);
|
||||
$builder->add('rule_num', '拥有权限数', [
|
||||
'props' => [
|
||||
'width' => '120px'
|
||||
]
|
||||
]);
|
||||
$builder->add('state', '状态', [
|
||||
'component' => [
|
||||
'name' => 'switch',
|
||||
'api' => 'AdminRole/indexUpdateState',
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '100px'
|
||||
]
|
||||
]);
|
||||
$builder->add('create_time', '时间', [
|
||||
'props' => [
|
||||
'width' => '200px'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'table-times',
|
||||
'props' => [
|
||||
'group' => [
|
||||
[
|
||||
'field' => 'create_time',
|
||||
'label' => '创建'
|
||||
],
|
||||
[
|
||||
'field' => 'update_time',
|
||||
'label' => '更新'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder = $builder->builder();
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
$limit = $request->get('limit', 10);
|
||||
$where = [];
|
||||
$id = $request->get('id');
|
||||
if ($id) {
|
||||
$where[] = ['pid', '=', $id];
|
||||
} else {
|
||||
$where[] = ['pid', 'NULL', ''];
|
||||
}
|
||||
$list = AdminRole::where($where)
|
||||
->order('id asc')->paginate($limit)->each(function ($item) {
|
||||
$item->admin_num = Admin::where(['role_id' => $item->id])->count();
|
||||
$item->rule_num = 0;
|
||||
if ($item->is_system) {
|
||||
$item->rule_num = '所有';
|
||||
} elseif ($item->rule) {
|
||||
$item->rule_num = count($item->rule);
|
||||
}
|
||||
$item->hasChildren = AdminRole::where(['pid' => $item->id])->count() > 0;
|
||||
});
|
||||
return $this->resData($list);
|
||||
}
|
||||
public function indexUpdateState(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$field = $request->post('field');
|
||||
$value = $request->post('value');
|
||||
$AdminRole = AdminRole::where(['id' => $id])->find();
|
||||
if (!$AdminRole) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
if ($AdminRole->is_system) {
|
||||
return $this->fail('系统角色不允许操作');
|
||||
}
|
||||
$AdminRole->{$field} = $value;
|
||||
if ($AdminRole->save()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$AdminRole = AdminRole::where(['id' => $id])->find();
|
||||
if (!$AdminRole) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
if ($AdminRole->is_system) {
|
||||
return $this->fail('系统角色不允许操作');
|
||||
}
|
||||
if (AdminRole::where(['pid' => $id])->count() > 0) {
|
||||
return $this->fail('请先删除子角色');
|
||||
}
|
||||
if (Admin::where(['role_id' => $id])->count() > 0) {
|
||||
return $this->fail('请先将拥有该角色的用户移除');
|
||||
}
|
||||
if ($AdminRole->delete()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
public function create(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$AdminRole = new AdminRole;
|
||||
if (!empty($D['pid'])) {
|
||||
$AdminRole->pid = $D['pid'];
|
||||
}
|
||||
$AdminRole->name = $D['name'];
|
||||
$AdminRole->rule = $D['rule'];
|
||||
if ($AdminRole->save()) {
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$builder = $this->getFormBuilder();
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function update(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$AdminRole = AdminRole::where(['id' => $D['id']])->find();
|
||||
if (!$AdminRole) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
$AdminRole->name = $D['name'];
|
||||
$AdminRole->rule = $D['rule'];
|
||||
if ($AdminRole->save()) {
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$id = $request->get('id');
|
||||
$AdminRole = AdminRole::where(['id' => $id])->find();
|
||||
if (!$AdminRole) {
|
||||
return $this->fail('角色不存在');
|
||||
}
|
||||
$builder = $this->getFormBuilder(true);
|
||||
if (!$AdminRole->rule) {
|
||||
$AdminRole->rule = [];
|
||||
}
|
||||
$builder->setData($AdminRole->toArray());
|
||||
return $this->resData($builder);
|
||||
}
|
||||
private function getFormBuilder($update = false)
|
||||
{
|
||||
$builder = new FormBuilder;
|
||||
$Component = new ComponentBuilder;
|
||||
if (!$update) {
|
||||
$builder->add('pid', '角色等级', 'cascader', null, [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '建议使用公司组织架构为角色等级'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'options' => AdminRole::options(null, ['is_system' => 0]),
|
||||
'clearable' => true,
|
||||
'filterable' => true,
|
||||
'props' => [
|
||||
'checkStrictly' => true,
|
||||
'emitPath' => false
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
$builder->add('name', '角色名称', 'input', '', [
|
||||
'required' => true,
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '建议使用公司组织架构为角色名称'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'maxlength' => 30,
|
||||
'show-word-limit' => true
|
||||
]
|
||||
]);
|
||||
$Install = new \app\admin\api\Install;
|
||||
$menus = new Menus($Install);
|
||||
$builder->add('rule', '规则', 'admin-rule', [], [
|
||||
'required' => true,
|
||||
'options' => $menus,
|
||||
'props' => [
|
||||
'rightDefaultChecked' => [
|
||||
'Index',
|
||||
'Index/index',
|
||||
'Index/control',
|
||||
'Admin/updateSelf',
|
||||
'Admin/getSelfInfo',
|
||||
'Public',
|
||||
'Public/outLogin'
|
||||
]
|
||||
]
|
||||
]);
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\helper\Captcha;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 图像验证码
|
||||
* Class CaptchaController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class CaptchaController extends Basic
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
protected $notNeedLogin = ['captcha', 'captchaCode'];
|
||||
protected $notNeedAuth = ['captcha', 'captchaCode'];
|
||||
|
||||
/**
|
||||
* 获取图像验证码
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function captcha(Request $request)
|
||||
{
|
||||
return Captcha::captcha();
|
||||
}
|
||||
|
||||
public function captchaCode()
|
||||
{
|
||||
return $this->success(null, Captcha::captchaCode());
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\DataboardBuilder;
|
||||
use support\Request;
|
||||
|
||||
class IndexController extends Basic
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
protected $notNeedLogin = ['index'];
|
||||
protected $notNeedAuth = ['index'];
|
||||
public function index(Request $request)
|
||||
{
|
||||
$path = $request->path();
|
||||
# 如果不是以/结尾的,就重定向到以/结尾的URL
|
||||
if (substr($path, -1) != '/') {
|
||||
return redirect($path . '/');
|
||||
}
|
||||
return view(public_path('index.html'));
|
||||
}
|
||||
public function control(Request $request)
|
||||
{
|
||||
$builder = new DataboardBuilder([
|
||||
'gutter' => 6
|
||||
]);
|
||||
$pluginConfig = glob(base_path("plugin/*/api/{$request->app}/IndexController.php"));
|
||||
foreach ($pluginConfig as $path) {
|
||||
$plugin_name = basename(dirname(dirname(dirname($path))));
|
||||
$class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\IndexController";
|
||||
if (!class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
$plugin = new $class;
|
||||
if (method_exists($plugin, 'control')) {
|
||||
$plugin->control($builder);
|
||||
}
|
||||
}
|
||||
foreach ($pluginConfig as $path) {
|
||||
$plugin_name = basename(dirname(dirname(dirname($path))));
|
||||
$class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\IndexController";
|
||||
if (!class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
$plugin = new $class;
|
||||
if (method_exists($plugin, 'echarts')) {
|
||||
$plugin->echarts($builder);
|
||||
}
|
||||
}
|
||||
return $this->resData($builder);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\model\Admin;
|
||||
use app\model\AdminRole;
|
||||
use app\expose\utils\Password;
|
||||
use app\expose\enum\ResponseCode;
|
||||
use app\expose\helper\Captcha;
|
||||
use app\expose\helper\Config;
|
||||
use Exception;
|
||||
use support\Request;
|
||||
|
||||
class LoginController extends Basic
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
protected $notNeedLogin = ['login'];
|
||||
protected $notNeedAuth = ['login'];
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$D = $request->post();
|
||||
$captcha_state = Config::get('captcha', 'state');
|
||||
if ($captcha_state) {
|
||||
if (!Captcha::check($D['captcha'], $D['token'])) {
|
||||
throw new Exception("验证码不正确", ResponseCode::CAPTCHA);
|
||||
}
|
||||
}
|
||||
$Admin = Admin::where(['username' => $D['username']])->find();
|
||||
if (!$Admin) {
|
||||
throw new Exception('密码错误');
|
||||
}
|
||||
if (!Password::verify($D['password'], $Admin->password)) {
|
||||
throw new Exception('密码错误');
|
||||
}
|
||||
if (!$Admin->state) {
|
||||
throw new Exception('管理员账号异常');
|
||||
}
|
||||
$AdminRole = AdminRole::where(['id' => $Admin->role_id])->find();
|
||||
if (!$AdminRole) {
|
||||
throw new Exception('管理员角色异常');
|
||||
}
|
||||
if (!$AdminRole->state) {
|
||||
throw new Exception('管理员角色异常');
|
||||
}
|
||||
if (!$AdminRole->is_system) {
|
||||
$now = date('H:i:s');
|
||||
if (!($now >= $Admin->allow_time_start && $now <= $Admin->allow_time_end)) {
|
||||
throw new Exception("当前不在工作时间");
|
||||
}
|
||||
if (!in_array(date('w'), $Admin->allow_week)) {
|
||||
throw new Exception("今日因该是休息日哦");
|
||||
}
|
||||
}
|
||||
$Admin->login_time = date('Y-m-d H:i:s');
|
||||
$Admin->login_ip = $request->getRealIp(true);
|
||||
if ($Admin->save()) {
|
||||
return $this->success('登录成功', Admin::getTokenInfo($Admin));
|
||||
} else {
|
||||
throw new Exception("登录失败");
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\TableBuilder;
|
||||
use app\expose\enum\PaymentChannels;
|
||||
use app\expose\enum\Platform;
|
||||
use app\expose\enum\State;
|
||||
use app\model\PaymentConfig;
|
||||
use app\model\PaymentTemplate;
|
||||
use support\Request;
|
||||
|
||||
class PaymentController extends Basic
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new PaymentConfig;
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tabs = [];
|
||||
$PaymentConfig = PaymentConfig::where(['channels_uid' => null])->select();
|
||||
$Platform = Platform::getOptions();
|
||||
foreach ($Platform as $key => $item) {
|
||||
$channels = [];
|
||||
switch ($item['value']) {
|
||||
case Platform::PC['value']:
|
||||
$channels = [
|
||||
PaymentChannels::WXPAY,
|
||||
PaymentChannels::ALIPAY,
|
||||
PaymentChannels::BALANCE,
|
||||
PaymentChannels::INTEGRAL
|
||||
];
|
||||
break;
|
||||
case Platform::H5['value']:
|
||||
$channels = [
|
||||
PaymentChannels::WXPAY,
|
||||
PaymentChannels::ALIPAY,
|
||||
PaymentChannels::BALANCE,
|
||||
PaymentChannels::INTEGRAL
|
||||
];
|
||||
break;
|
||||
case Platform::WECHAT_MINIAPP['value']:
|
||||
$channels = [
|
||||
PaymentChannels::WXPAY,
|
||||
PaymentChannels::BALANCE,
|
||||
PaymentChannels::INTEGRAL
|
||||
];
|
||||
break;
|
||||
case Platform::WECHAT_OFFICIAL_ACCOUNT['value']:
|
||||
$channels = [
|
||||
PaymentChannels::WXPAY,
|
||||
PaymentChannels::BALANCE,
|
||||
PaymentChannels::INTEGRAL
|
||||
];
|
||||
break;
|
||||
case Platform::APP['value']:
|
||||
$channels = [
|
||||
PaymentChannels::WXPAY,
|
||||
PaymentChannels::ALIPAY,
|
||||
PaymentChannels::BALANCE,
|
||||
PaymentChannels::INTEGRAL
|
||||
];
|
||||
break;
|
||||
}
|
||||
$tabs[] = $this->getFormBuilder($PaymentConfig, $item, $channels);
|
||||
}
|
||||
return $this->resData($tabs);
|
||||
}
|
||||
private function getFormBuilder($PaymentConfig, $Platform, $channels)
|
||||
{
|
||||
$builder = new TableBuilder([
|
||||
'border' => false,
|
||||
'stripe' => false
|
||||
]);
|
||||
$builder->add('channels', '支付方式', [
|
||||
'component' => [
|
||||
'name' => 'table-userinfo',
|
||||
'props' => [
|
||||
'nickname' => 'channels_text',
|
||||
'avatar' => [
|
||||
'field' => 'channels_icon',
|
||||
'size' => 40
|
||||
]
|
||||
]
|
||||
],
|
||||
'props' => []
|
||||
]);
|
||||
$builder->add('template_id', '支付模板', [
|
||||
'component' => [
|
||||
'name' => 'select',
|
||||
'api' => 'Payment/indexUpdateField',
|
||||
'options' => PaymentTemplate::options(['channels_uid' => null]),
|
||||
'props' => [
|
||||
'clearable' => true
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '300px'
|
||||
]
|
||||
]);
|
||||
$builder->add('state', '是否启用', [
|
||||
'component' => [
|
||||
'name' => 'switch',
|
||||
'api' => 'Payment/indexUpdateState',
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '160px'
|
||||
]
|
||||
]);
|
||||
$builder->add('is_default', '是否为默认支付', [
|
||||
'component' => [
|
||||
'name' => 'switch',
|
||||
'api' => 'Payment/indexUpdateState',
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '160px'
|
||||
]
|
||||
]);
|
||||
$tableData = [];
|
||||
$exist = [];
|
||||
foreach ($PaymentConfig as $item) {
|
||||
if ($item->platform != $Platform['value']) continue;
|
||||
$temp = $item->toArray();
|
||||
$PaymentChannels = PaymentChannels::get($item->channels);
|
||||
if ($PaymentChannels) {
|
||||
$temp['channels_text'] = $PaymentChannels['label'];
|
||||
$temp['channels_icon'] = $PaymentChannels['icon'];
|
||||
}
|
||||
$exist[] = $item->channels;
|
||||
$tableData[] = $temp;
|
||||
}
|
||||
foreach ($channels as $key => $channel) {
|
||||
if (!in_array($channel['value'], $exist)) {
|
||||
$model = new PaymentConfig;
|
||||
$model->platform = $Platform['value'];
|
||||
$model->channels = $channel['value'];
|
||||
$model->state = State::NO['value'];
|
||||
$model->save();
|
||||
$tableData[] = [
|
||||
'id' => $model->id,
|
||||
'channels' => $channel['value'],
|
||||
'channels_text' => $channel['label'],
|
||||
'channels_icon' => $channel['icon'],
|
||||
'template_id' => null,
|
||||
'state' => State::NO['value'],
|
||||
'is_default' => State::NO['value']
|
||||
];
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'title' => $Platform['label'],
|
||||
'name' => $Platform['value'],
|
||||
'tips' => '在' . $Platform['label'] . '端付款时使用',
|
||||
'builder' => $builder,
|
||||
'data' => $tableData
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
public function indexUpdateState(Request $request)
|
||||
{
|
||||
$id = $request->post('id');
|
||||
$field = $request->post('field');
|
||||
$value = $request->post('value');
|
||||
$PaymentConfig = PaymentConfig::where(['id' => $id])->find();
|
||||
if (!$PaymentConfig) {
|
||||
return $this->fail('数据不存在');
|
||||
}
|
||||
if ($field == 'is_default' && $value == State::YES['value']) {
|
||||
$PaymentConfig->where(['platform' => $PaymentConfig->platform])->update(['is_default' => State::NO['value']]);
|
||||
}
|
||||
$PaymentConfig->{$field} = $value;
|
||||
if ($PaymentConfig->save()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->fail('操作失败');
|
||||
}
|
||||
}
|
||||
@@ -1,639 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\ComponentBuilder;
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\build\builder\TableBuilder;
|
||||
use app\expose\enum\Action;
|
||||
use app\expose\enum\AlipayRsaModel;
|
||||
use app\expose\enum\PaymentChannels;
|
||||
use app\expose\enum\State;
|
||||
use app\expose\enum\WxpayMchType;
|
||||
use app\expose\enum\WxpayVersion;
|
||||
use app\model\PaymentTemplate;
|
||||
use support\Request;
|
||||
|
||||
class PaymentTemplateController extends Basic
|
||||
{
|
||||
public function indexGetTable(Request $request)
|
||||
{
|
||||
$builder = new TableBuilder;
|
||||
$builder->addAction('操作', [
|
||||
'width' => '200px',
|
||||
'fixed' => 'right'
|
||||
]);
|
||||
$builder->addTableAction('编辑', [
|
||||
'path' => 'PaymentTemplate/update',
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'title' => '编辑《{title}》支付模板'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addTableAction('删除', [
|
||||
'model' => Action::COMFIRM['value'],
|
||||
'path' => 'PaymentTemplate/delete',
|
||||
'props' => [
|
||||
'type' => 'error',
|
||||
'message' => '确定要删除《{title}》支付模板吗?',
|
||||
'confirmButtonClass' => 'el-button--danger'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'danger',
|
||||
'size' => 'small'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->addHeader();
|
||||
$builder->addHeaderAction('新增模板', [
|
||||
'path' => 'PaymentTemplate/create',
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
'title' => '新增支付模板'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'button',
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
'icon' => 'Plus'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$builder->add('id', 'ID', [
|
||||
'props' => [
|
||||
'width' => '80px'
|
||||
]
|
||||
]);
|
||||
$builder->add('title', '模板名称');
|
||||
$builder->add('channels', '支付方式', [
|
||||
'component' => [
|
||||
'name' => 'tag',
|
||||
'options' => PaymentChannels::getOptions()
|
||||
],
|
||||
'props' => [
|
||||
'width' => '160px'
|
||||
]
|
||||
]);
|
||||
$builder->add('state', '状态', [
|
||||
'component' => [
|
||||
'name' => 'switch',
|
||||
'api' => 'PaymentTemplate/indexUpdateState',
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
],
|
||||
'props' => [
|
||||
'width' => '100px'
|
||||
]
|
||||
]);
|
||||
$builder->add('create_time', '时间', [
|
||||
'props' => [
|
||||
'width' => '200px'
|
||||
],
|
||||
'component' => [
|
||||
'name' => 'table-times',
|
||||
'props' => [
|
||||
'group' => [
|
||||
[
|
||||
'field' => 'create_time',
|
||||
'label' => '创建'
|
||||
],
|
||||
[
|
||||
'field' => 'update_time',
|
||||
'label' => '更新'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
$limit = $request->get('limit', 10);
|
||||
$where = [];
|
||||
$where[] = ['channels_uid', '=', null];
|
||||
$list = PaymentTemplate::where($where)->withoutField('value')
|
||||
->order('id desc')->paginate($limit)->each(function ($item) {
|
||||
});
|
||||
return $this->resData($list);
|
||||
}
|
||||
public function create(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$model = new PaymentTemplate;
|
||||
$model->title = $D['title'];
|
||||
unset($D['title']);
|
||||
$model->channels = $D['channels'];
|
||||
unset($D['channels']);
|
||||
$model->state = State::YES['value'];
|
||||
$model->value = $this->getValue($model->channels, $D);
|
||||
if ($model->save()) {
|
||||
return $this->success('新增成功');
|
||||
}
|
||||
return $this->fail('新增失败');
|
||||
}
|
||||
$builder = $this->getFormBuilder();
|
||||
return $this->resData($builder);
|
||||
}
|
||||
public function update(Request $request)
|
||||
{
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$model = PaymentTemplate::where(['id' => $D['id']])->find();
|
||||
if (!$model) {
|
||||
return $this->fail('数据不存在');
|
||||
}
|
||||
unset($D['id']);
|
||||
$model->title = $D['title'];
|
||||
unset($D['title']);
|
||||
$model->channels = $D['channels'];
|
||||
unset($D['channels']);
|
||||
$model->state = State::YES['value'];
|
||||
$model->value = $this->getValue($model->channels, $D);
|
||||
if ($model->save()) {
|
||||
return $this->success('更新成功');
|
||||
}
|
||||
return $this->fail('更新失败');
|
||||
}
|
||||
$id = $request->get('id');
|
||||
$model = PaymentTemplate::where(['id' => $id])->find();
|
||||
if (!$model) {
|
||||
return $this->fail('数据不存在');
|
||||
}
|
||||
$builder = $this->getFormBuilder(true);
|
||||
$data = [
|
||||
'id' => $model->id,
|
||||
'title' => $model->title,
|
||||
'channels' => $model->channels
|
||||
];
|
||||
foreach ($model->value as $key => $value) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
$builder->setData($data);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
private function getValue($channels, $D)
|
||||
{
|
||||
$value = [];
|
||||
switch ($channels) {
|
||||
case PaymentChannels::WXPAY['value']:
|
||||
$value = [
|
||||
'mch_type' => $D['mch_type'],
|
||||
'appid' => $D['appid'],
|
||||
'mch_id' => $D['mch_id'],
|
||||
'mch_key' => $D['mch_key'],
|
||||
'ssl_cert' => $D['ssl_cert'],
|
||||
'ssl_key' => $D['ssl_key'],
|
||||
'notify_url' => $D['notify_url'],
|
||||
];
|
||||
break;
|
||||
case PaymentChannels::ALIPAY['value']:
|
||||
$value = [
|
||||
'appid' => $D['appid'],
|
||||
'ras_model' => $D['ras_model'],
|
||||
'app_public_cert' => $D['app_public_cert'],
|
||||
'alipay_public_cert' => $D['alipay_public_cert'],
|
||||
'alipay_root_cert' => $D['alipay_root_cert'],
|
||||
'alipay_public_key' => $D['alipay_public_key'],
|
||||
'private_key' => $D['private_key'],
|
||||
'notify_url' => $D['notify_url'],
|
||||
];
|
||||
break;
|
||||
case PaymentChannels::INTEGRAL['value']:
|
||||
$value = [
|
||||
'display_name' => $D['display_name'],
|
||||
'proportion' => $D['proportion'],
|
||||
'is_integer' => $D['is_integer'],
|
||||
'integer' => $D['integer']
|
||||
];
|
||||
break;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
private function getFormBuilder($update = false)
|
||||
{
|
||||
$builder = new FormBuilder(null, '', [
|
||||
'labelWidth' => '300px',
|
||||
'labelPosition' => 'right'
|
||||
]);
|
||||
$Component = new ComponentBuilder;
|
||||
$builder->add('title', '模板名称', 'input', null, [
|
||||
'required' => true,
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '仅用于后台管理使用,对前台用户不可见;例如:H5-支付宝支付;微信小程序-微信支付'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入模板名称'
|
||||
]
|
||||
]);
|
||||
$builder->add('channels', '支付方式', 'radio', PaymentChannels::WXPAY['value'], [
|
||||
'required' => true,
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '保存以后支付方式将不可修改,请谨慎操作,'], ['type' => 'info', 'size' => 'small'])
|
||||
->add('link', ['default' => '微信商户平台'], ['type' => 'success', 'href' => 'https://pay.weixin.qq.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small'])
|
||||
->add('text', ['default' => ','], ['type' => 'info', 'size' => 'small'])
|
||||
->add('link', ['default' => '支付宝开放平台'], ['type' => 'primary', 'href' => 'https://alipay.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'options' => PaymentChannels::getOptions(),
|
||||
'props' => [
|
||||
'disabled' => $update
|
||||
],
|
||||
'subProps' => [
|
||||
'border' => true
|
||||
]
|
||||
]);
|
||||
|
||||
# 微信支付开始
|
||||
/* $builder->add('api_version', '微信支付接口版本', 'radio', WxpayVersion::V3['value'], [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']]
|
||||
],
|
||||
'options' => WxpayVersion::getOptions(),
|
||||
'subProps' => [
|
||||
'border' => true
|
||||
]
|
||||
]); */
|
||||
$builder->add('mch_type', '微信商户号类型', 'radio', WxpayMchType::NORMAL['value'], [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']]
|
||||
],
|
||||
'options' => WxpayMchType::getOptions(),
|
||||
'subProps' => [
|
||||
'border' => true
|
||||
]
|
||||
]);
|
||||
$builder->add('appid', '应用ID (AppID)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::NORMAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入应用ID (AppID)'
|
||||
]
|
||||
]);
|
||||
$builder->add('mch_id', '微信商户号 (MchId)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::NORMAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入微信商户号 (MchId)'
|
||||
]
|
||||
]);
|
||||
$builder->add('mch_key', '支付密钥 (APIKEY)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::NORMAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置APIv3密钥'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入支付密钥 (APIKEY)'
|
||||
]
|
||||
]);
|
||||
$builder->add('ssl_cert', '证书文件 (CERT)', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::NORMAL['value']]
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('ssl_key', '证书文件 (KEY)', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::NORMAL['value']]
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
# 微信支付结束
|
||||
|
||||
# 微信支付服务商模式开始
|
||||
$builder->add('service_appid', '服务商应用ID (AppID)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '请填写微信支付服务商的AppID'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入服务商应用ID (AppID)'
|
||||
]
|
||||
]);
|
||||
$builder->add('service_mch_id', '服务商户号 (MchId)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信支付服务商的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入服务商户号 (MchId)'
|
||||
]
|
||||
]);
|
||||
$builder->add('service_mch_key', '服务商密钥 (APIKEY)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置API密钥'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入服务商密钥 (APIKEY)'
|
||||
]
|
||||
]);
|
||||
$builder->add('appid', '子商户应用ID (AppID)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入子商户应用ID (AppID)'
|
||||
]
|
||||
]);
|
||||
$builder->add('mch_id', '子商户号 (MchId)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入子商户号 (MchId)'
|
||||
]
|
||||
]);
|
||||
$builder->add('service_ssl_cert', '服务商证书文件 (CERT)', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('service_ssl_key', '服务商证书文件 (KEY)', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::WXPAY['value']],
|
||||
['mch_type', '=', WxpayMchType::SERVICE['value']]
|
||||
],
|
||||
'props' => [
|
||||
'accept' => 'application/octet-stream',
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
# 微信支付服务商模式结束
|
||||
|
||||
# 支付宝支付开始
|
||||
$builder->add('appid', '支付宝应用 (AppID)', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '支付宝分配给开发者的应用ID;例如:2021072300007148'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入支付宝应用 (AppID)'
|
||||
]
|
||||
]);
|
||||
$builder->add('ras_model', '加签模式', 'radio', AlipayRsaModel::PUBLIC_CERT['value'], [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '如需使用资金支出类的接口,则必须使用公钥证书模式'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'options' => AlipayRsaModel::getOptions(),
|
||||
'subProps' => [
|
||||
'border' => true
|
||||
]
|
||||
]);
|
||||
$builder->add('app_public_cert', '应用公钥证书', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']],
|
||||
['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '请上传 "appCertPublicKey_xxxxxxxx.crt" 文件'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('alipay_public_cert', '支付宝公钥证书', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']],
|
||||
['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '请上传 "alipayCertPublicKey_RSA2.crt" 文件'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('alipay_root_cert', '支付宝根证书', 'bundle', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']],
|
||||
['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '请上传 "alipayRootCert.crt" 文件'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'view' => 'file',
|
||||
'multiple' => 1
|
||||
]
|
||||
]);
|
||||
$builder->add('alipay_public_key', '支付宝公钥 (alipayPublicKey)', 'input', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']],
|
||||
['ras_model', '=', AlipayRsaModel::PUBLIC_KEY['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '可在 "支付宝开放平台" - "应用信息" - "接口加签方式" - "支付宝公钥" 中复制'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'textarea',
|
||||
'autosize' => [
|
||||
'minRows' => 4,
|
||||
'maxRows' => 6
|
||||
],
|
||||
'placeholder' => '请输入支付宝公钥 (alipayPublicKey)'
|
||||
]
|
||||
]);
|
||||
$builder->add('private_key', '应用私钥 (privateKey)', 'input', '', [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::ALIPAY['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '查看 "应用私钥RSA2048-敏感数据,请妥善保管.txt" 文件,将全部内容复制到此处'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'textarea',
|
||||
'autosize' => [
|
||||
'minRows' => 4,
|
||||
'maxRows' => 6
|
||||
],
|
||||
'placeholder' => '请输入应用私钥 (privateKey)'
|
||||
]
|
||||
]);
|
||||
$builder->add('display_name', '显示名称', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::INTEGRAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '在客户端显示的名称'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '请输入显示名称'
|
||||
]
|
||||
]);
|
||||
$builder->add('proportion', '充值比例', 'input-number', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::INTEGRAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '1元=多少积分'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'controls' => false,
|
||||
'min' => 1,
|
||||
'max' => 1000000
|
||||
]
|
||||
]);
|
||||
$builder->add('is_integer', '整数使用', 'switch', State::NO['value'], [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::INTEGRAL['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '开启后只能使用整数的倍数积分'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'active-value' => State::YES['value'],
|
||||
'inactive-value' => State::NO['value']
|
||||
]
|
||||
]);
|
||||
$builder->add('integer', '整数', 'input-number', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', '=', PaymentChannels::INTEGRAL['value']],
|
||||
['is_integer', '=', State::YES['value']]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '整数的倍数使用'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'controls' => false,
|
||||
'min' => 1,
|
||||
'max' => 1000000
|
||||
]
|
||||
]);
|
||||
$builder->add('notify_url', '支付通知', 'input', null, [
|
||||
'required' => true,
|
||||
'where' => [
|
||||
['channels', 'in', [PaymentChannels::WXPAY['value'],PaymentChannels::ALIPAY['value']]]
|
||||
],
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '只需要填写域名,不需要填写路径,如:https://www.baidu.com'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'text',
|
||||
'placeholder' => '支付通知接口域名'
|
||||
]
|
||||
]);
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\trait\Config;
|
||||
use support\Request;
|
||||
|
||||
class PlatformController extends Basic
|
||||
{
|
||||
use Config;
|
||||
public function wechat_miniproject(Request $request)
|
||||
{
|
||||
return $this->builder();
|
||||
}
|
||||
public function wechat_official_account(Request $request)
|
||||
{
|
||||
return $this->builder();
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\config\Action;
|
||||
use app\expose\build\config\Web;
|
||||
use app\expose\enum\Action as EnumAction;
|
||||
use app\expose\enum\Filesystem;
|
||||
use app\expose\enum\ResponseEvent;
|
||||
use app\expose\helper\Config;
|
||||
use app\expose\helper\Menus;
|
||||
use app\expose\utils\DatabaseSchemaSync;
|
||||
use loong\oauth\facade\Auth;
|
||||
use support\Request;
|
||||
use support\think\Db;
|
||||
|
||||
class PublicController extends Basic
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
protected $notNeedLogin = ['config', 'menus', 'unlock'];
|
||||
protected $notNeedAuth = ['config', 'menus', 'unlock'];
|
||||
public function config(Request $request)
|
||||
{
|
||||
$lang = $request->lang;
|
||||
$domain = $request->app;
|
||||
$config = Config::get('basic');
|
||||
$config = new Web($config);
|
||||
$captcha_state = Config::get('captcha', 'state');
|
||||
$config->useLogin([
|
||||
'title' => trans('Login Title', [], $domain, $lang),
|
||||
'url' => 'Login/login',
|
||||
'user_agreement' => '',
|
||||
'captcha' => $captcha_state
|
||||
]);
|
||||
$config->useApis([
|
||||
'userinfo' => 'Admin/getSelfInfo',
|
||||
'lock' => 'Public/lock',
|
||||
'unlock' => 'Public/unlock',
|
||||
'menus' => 'Public/menus',
|
||||
'outLogin' => 'Public/outLogin'
|
||||
]);
|
||||
$toolbar = new Action();
|
||||
$toolbar->add(EnumAction::LOCK['value'], [
|
||||
'icon' => 'Lock',
|
||||
'tips' => trans('toolbar Lock', [], $domain, $lang),
|
||||
]);
|
||||
$toolbar->add(EnumAction::SEARCH['value'], [
|
||||
'icon' => 'Search',
|
||||
'tips' => trans('toolbar Search', [], $domain, $lang),
|
||||
]);
|
||||
$toolbar->add(EnumAction::NOTIFICATION['value'], [
|
||||
'icon' => 'Notification',
|
||||
'tips' => trans('toolbar Notification', [], $domain, $lang),
|
||||
]);
|
||||
$toolbar->add(EnumAction::FULL_SCREEN['value'], [
|
||||
'icon' => 'FullScreen',
|
||||
'tips' => trans('toolbar FullScreen', [], $domain, $lang),
|
||||
]);
|
||||
// 读取update/VERSION文件和跟目录下的VERSION文件对比
|
||||
$updateVersionContent = file_get_contents(base_path('update/VERSION'));
|
||||
$updateVersion = explode("\n", $updateVersionContent);
|
||||
$updateVersion = (int)trim($updateVersion[0]);
|
||||
$versionContent = file_get_contents(base_path('VERSION'));
|
||||
$version = explode("\n", $versionContent);
|
||||
$version = (int)trim($version[0]);
|
||||
if ($updateVersion < $version) {
|
||||
$toolbar->add(EnumAction::COMFIRM['value'], [
|
||||
'path' => 'Public/syncSql',
|
||||
'icon' => 'Coin',
|
||||
'tips' => trans('同步SQL', [], $domain, $lang),
|
||||
'props' => [
|
||||
'message' => '检测到有SQL未同步,确定要同步SQL吗?',
|
||||
'confirmButtonClass' => 'el-button--danger'
|
||||
]
|
||||
]);
|
||||
}
|
||||
$toolbar->add(EnumAction::LINK['value'], [
|
||||
'icon' => 'House',
|
||||
'tips' => trans('toolbar House', [], $domain, $lang),
|
||||
'props' => [
|
||||
'url' => '/',
|
||||
'target' => '_blank'
|
||||
]
|
||||
]);
|
||||
$toolbar->add(EnumAction::LINK['value'], [
|
||||
'icon' => 'ElementPlus',
|
||||
'tips' => trans('toolbar Control', [], $domain, $lang),
|
||||
'props' => [
|
||||
'url' => '/control/',
|
||||
'target' => '_blank'
|
||||
]
|
||||
]);
|
||||
$config->useToolbar($toolbar->toArray());
|
||||
$userDropdownMenu = new Action();
|
||||
$userDropdownMenu->add(EnumAction::DIALOG['value'], [
|
||||
'path' => 'Admin/updateSelf',
|
||||
'label' => trans('userDropdownMenu updateSelf', [], $domain, $lang),
|
||||
'icon' => 'User',
|
||||
'props' => [
|
||||
'title' => trans('userDropdownMenu updateSelf', [], $domain, $lang)
|
||||
]
|
||||
]);
|
||||
$config->useUserDropdownMenu($userDropdownMenu->toArray());
|
||||
$config->storage = Filesystem::getOptions();
|
||||
$pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php"));
|
||||
foreach ($pluginConfig as $path) {
|
||||
$plugin_name = basename(dirname(dirname(dirname($path))));
|
||||
$class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController";
|
||||
if (!class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
$plugin = new $class;
|
||||
if (method_exists($plugin, 'config')) {
|
||||
$plugin->config($config);
|
||||
}
|
||||
}
|
||||
$updateVersionContent = file_get_contents(base_path('update/VERSION'));
|
||||
$updateVersionArr = explode("\n", $updateVersionContent);
|
||||
$config->version_name = $updateVersionArr[1];
|
||||
$config->version = $updateVersionArr[0];
|
||||
return $this->resData($config);
|
||||
}
|
||||
public function menus(Request $request)
|
||||
{
|
||||
$Install = new \app\admin\api\Install;
|
||||
$menus = new Menus($Install);
|
||||
return $this->resData($menus);
|
||||
}
|
||||
public function outLogin(Request $request)
|
||||
{
|
||||
$token = $request->header('Authorization');
|
||||
if ($token) {
|
||||
try {
|
||||
Auth::setPrefix('ADMIN')->delete($token);
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
}
|
||||
return $this->success(trans('Logout Success', [], $request->app, $request->lang));
|
||||
}
|
||||
public function lock(Request $request)
|
||||
{
|
||||
try {
|
||||
$password = $request->post('password');
|
||||
if (!$password) {
|
||||
return $this->fail(trans('PIN Code Cannot Be Empty', [], $request->app, $request->lang));
|
||||
}
|
||||
if (mb_strlen($password) != 6) {
|
||||
return $this->fail(trans('Please Enter 6 Digits PIN Code', [], $request->app, $request->lang));
|
||||
}
|
||||
$token = $request->header('Authorization');
|
||||
Auth::setPrefix('CONTROL')->lock($token, $password);
|
||||
return $this->event(ResponseEvent::UPDATE_USERINFO, trans('Lock Success', [], $request->app, $request->lang));
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
}
|
||||
public function unlock(Request $request)
|
||||
{
|
||||
$password = $request->post('password');
|
||||
try {
|
||||
$token = $request->header('Authorization');
|
||||
Auth::setPrefix('CONTROL')->unlock($token, $password);
|
||||
return $this->success(trans('Unlock Success', [], $request->app, $request->lang));
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
}
|
||||
public function syncSql(Request $request)
|
||||
{
|
||||
$updateVersionContent = file_get_contents(base_path('update/VERSION'));
|
||||
$updateVersionArr = explode("\n", $updateVersionContent);
|
||||
$updateVersion = (int)trim($updateVersionArr[0]);
|
||||
$versionContent = file_get_contents(base_path('VERSION'));
|
||||
$versionArr = explode("\n", $versionContent);
|
||||
$version = (int)trim($versionArr[0]);
|
||||
if ($updateVersion >= $version) {
|
||||
return $this->fail('已是最新版本');
|
||||
}
|
||||
$sqlSum = 0;
|
||||
$sqlSuccessSum = 0;
|
||||
$sqlErrorSum = 0;
|
||||
for ($i = $updateVersion + 1; $i <= $version; $i++) {
|
||||
$sqlFile = glob(base_path("update/{$i}/*.sql"));
|
||||
if (empty($sqlFile)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($sqlFile as $file) {
|
||||
$sqlSum++;
|
||||
Db::startTrans();
|
||||
try {
|
||||
$sql = file_get_contents($file);
|
||||
$prefix = config('thinkorm.connections.mysql.prefix');
|
||||
$sql = str_replace('`php_', "`$prefix", $sql);
|
||||
$sql = str_replace('INTO php_', "INTO $prefix", $sql);
|
||||
Db::execute($sql);
|
||||
Db::commit();
|
||||
$sqlSuccessSum++;
|
||||
} catch (\Throwable $th) {
|
||||
Db::rollback();
|
||||
$sqlErrorSum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
file_put_contents(base_path('update/VERSION'), $versionContent);
|
||||
return $this->success('同步SQL成功,共执行' . $sqlSum . '条SQL,成功' . $sqlSuccessSum . '条,失败' . $sqlErrorSum . '条,已更新到:' . $versionArr[1]);
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\build\builder\ComponentBuilder;
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\enum\Filesystem;
|
||||
use app\expose\enum\SubmitEvent;
|
||||
use app\expose\trait\Config;
|
||||
use support\Request;
|
||||
|
||||
class SystemController extends Basic
|
||||
{
|
||||
use Config;
|
||||
public function basic(Request $request)
|
||||
{
|
||||
return $this->builder();
|
||||
}
|
||||
public function payment(Request $request)
|
||||
{
|
||||
return $this->builder();
|
||||
}
|
||||
public function sms(Request $request)
|
||||
{
|
||||
return $this->tabsBuilder();
|
||||
}
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$request = request();
|
||||
if ($request->method() === 'POST') {
|
||||
$D = $request->post();
|
||||
$config = [];
|
||||
$config['default'] = $D['default'];
|
||||
$config['max_size'] = (int)$D['max_size'];
|
||||
$config['storage']['public']['url'] = $D['public']['url'];
|
||||
$config['storage']['local']['url'] = $D['local']['url'];
|
||||
foreach ($D['ftp'] as $key => $value) {
|
||||
$config['storage']['ftp'][$key] = $value;
|
||||
}
|
||||
foreach ($D['s3'] as $key => $value) {
|
||||
if (in_array($key, ['key', 'secret'])) {
|
||||
$config['storage']['s3']['credentials'][$key] = $value;
|
||||
} else {
|
||||
$config['storage']['s3'][$key] = $value;
|
||||
}
|
||||
}
|
||||
foreach ($D['minio'] as $key => $value) {
|
||||
if (in_array($key, ['key', 'secret'])) {
|
||||
$config['storage']['minio']['credentials'][$key] = $value;
|
||||
} else {
|
||||
$config['storage']['minio'][$key] = $value;
|
||||
}
|
||||
}
|
||||
foreach ($D['oss'] as $key => $value) {
|
||||
$config['storage']['oss'][$key] = $value;
|
||||
}
|
||||
foreach ($D['qiniu'] as $key => $value) {
|
||||
$config['storage']['qiniu'][$key] = $value;
|
||||
}
|
||||
foreach ($D['cos'] as $key => $value) {
|
||||
$config['storage']['cos'][$key] = $value;
|
||||
}
|
||||
$content = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$user = config_path("plugin/shopwwi/filesystem/app.json");
|
||||
$res = file_put_contents($user, $content);
|
||||
if ($res) {
|
||||
return $this->success('保存成功,重启服务生效');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
$config = config('plugin.shopwwi.filesystem.app');
|
||||
$builder = new FormBuilder(null, null, [
|
||||
'translations'=>true,
|
||||
'submitEvent' => SubmitEvent::SILENT
|
||||
]);
|
||||
$defaultOptions = Filesystem::getOptions();
|
||||
$builder->add('default', '默认存储渠道商', 'select', $config['default'], [
|
||||
'options' => $defaultOptions
|
||||
]);
|
||||
$builder->add('max_size', '单个文件大小(字节)', 'input-number', $config['max_size'], [
|
||||
'props' => [
|
||||
'min' => 0,
|
||||
'controls' => false,
|
||||
'style' => [
|
||||
'width' => '200px'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$Component = new ComponentBuilder;
|
||||
$subBuilder = new FormBuilder('public', '本地存储(外部)');
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['public']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('local', '本地存储(内部)');
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['local']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('ftp', 'FTP存储');
|
||||
$subBuilder->add('host', 'HOST', 'input', $config['storage']['ftp']['host']);
|
||||
$subBuilder->add('username', 'UserName', 'input', $config['storage']['ftp']['username']);
|
||||
$subBuilder->add('password', 'Password', 'input', $config['storage']['ftp']['password']);
|
||||
$subBuilder->add('port', 'Port', 'input', $config['storage']['ftp']['port']);
|
||||
$subBuilder->add('root', '目录', 'input', $config['storage']['ftp']['root']);
|
||||
$subBuilder->add('passive', '被动模式', 'switch', $config['storage']['ftp']['passive']);
|
||||
$subBuilder->add('ssl', 'SSL', 'switch', $config['storage']['ftp']['ssl']);
|
||||
$subBuilder->add('timeout', '超时', 'input-number', $config['storage']['ftp']['timeout']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['ftp']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('s3', 'S3存储');
|
||||
$subBuilder->add('key', 'KEY', 'input', $config['storage']['s3']['credentials']['key']);
|
||||
$subBuilder->add('secret', 'Secret', 'input', $config['storage']['s3']['credentials']['secret']);
|
||||
$subBuilder->add('region', 'Region', 'input', $config['storage']['s3']['region']);
|
||||
$subBuilder->add('version', 'Version', 'input', $config['storage']['s3']['version']);
|
||||
$subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['s3']['bucket_endpoint']);
|
||||
$subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['s3']['use_path_style_endpoint']);
|
||||
$subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['s3']['endpoint']);
|
||||
$subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['s3']['bucket_name']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['s3']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('minio', 'Minio存储');
|
||||
$subBuilder->add('key', 'KEY', 'input', $config['storage']['minio']['credentials']['key']);
|
||||
$subBuilder->add('secret', 'Secret', 'input', $config['storage']['minio']['credentials']['secret']);
|
||||
$subBuilder->add('region', 'Region', 'input', $config['storage']['minio']['region']);
|
||||
$subBuilder->add('version', 'Version', 'input', $config['storage']['minio']['version']);
|
||||
$subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['minio']['bucket_endpoint']);
|
||||
$subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['minio']['use_path_style_endpoint']);
|
||||
$subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['minio']['endpoint']);
|
||||
$subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['minio']['bucket_name']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['minio']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('oss', '阿里云存储');
|
||||
$subBuilder->add('accessId', 'AccessId', 'input', $config['storage']['oss']['accessId']);
|
||||
$subBuilder->add('accessSecret', 'AccessSecret', 'input', $config['storage']['oss']['accessSecret']);
|
||||
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['oss']['bucket']);
|
||||
$subBuilder->add('endpoint', 'Bucket域名', 'input', $config['storage']['oss']['endpoint']);
|
||||
$subBuilder->add('isCName', '私有空间', 'switch', $config['storage']['oss']['isCName']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['oss']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('qiniu', '七牛存储');
|
||||
$subBuilder->add('accessKey', 'AccessKey', 'input', $config['storage']['qiniu']['accessKey']);
|
||||
$subBuilder->add('secretKey', 'SecretKey', 'input', $config['storage']['qiniu']['secretKey']);
|
||||
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['qiniu']['bucket']);
|
||||
$subBuilder->add('domain', 'Domain', 'input', $config['storage']['qiniu']['domain']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['qiniu']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
|
||||
$subBuilder = new FormBuilder('cos', '腾讯云存储');
|
||||
$subBuilder->add('app_id', 'Appid', 'input', $config['storage']['cos']['app_id']);
|
||||
$subBuilder->add('secret_id', 'SecretId', 'input', $config['storage']['cos']['secret_id']);
|
||||
$subBuilder->add('secret_key', 'SecretKey', 'input', $config['storage']['cos']['secret_key']);
|
||||
$subBuilder->add('region', 'Region', 'input', $config['storage']['cos']['region']);
|
||||
$subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['cos']['bucket']);
|
||||
$subBuilder->add('read_from_cdn', '从CDN读取', 'switch', $config['storage']['cos']['read_from_cdn']);
|
||||
$subBuilder->add('signed_url', '私有空间', 'switch', $config['storage']['cos']['signed_url']);
|
||||
$subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['cos']['url'], [
|
||||
'prompt' => [
|
||||
$Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small'])
|
||||
->builder()
|
||||
],
|
||||
'props' => [
|
||||
'placeholder' => 'http://static.example.com'
|
||||
]
|
||||
]);
|
||||
$builder->addGroupForm($subBuilder);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\Basic;
|
||||
use app\expose\trait\Uploads;
|
||||
|
||||
class UploadsController extends Basic
|
||||
{
|
||||
use Uploads;
|
||||
public function __construct()
|
||||
{
|
||||
$request = request();
|
||||
$this->admin_uid = $request->admin_uid;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\expose\middleware\AdminAuth;
|
||||
|
||||
class Auth extends AdminAuth
|
||||
{
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\expose\helper\Config;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http\Chunk;
|
||||
use Workerman\Protocols\Http\ServerSentEvents;
|
||||
use Workerman\Timer;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$config = new Config('basic');
|
||||
$templateFile = public_path('template/index.html');
|
||||
if (!file_exists($templateFile)) {
|
||||
return '请检查 /public/template/index.html 是否存在。';
|
||||
}
|
||||
return view($templateFile, $config->toArray());
|
||||
}
|
||||
public function fastmovie(Request $request)
|
||||
{
|
||||
$config = new Config('basic');
|
||||
$templateFile = public_path('template/fastmovie.html');
|
||||
if (!file_exists($templateFile)) {
|
||||
return '请检查 /public/template/fastmovie.html 是否存在。';
|
||||
}
|
||||
return view($templateFile, $config->toArray());
|
||||
}
|
||||
public function test(Request $request)
|
||||
{
|
||||
//当前连接
|
||||
$connect = $request->connection;
|
||||
//设置连接头
|
||||
$connect->send(new Response(200, [
|
||||
'Content-Type' => 'text/event-stream',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
'Connection' => 'keep-alive',
|
||||
], "\r\n"));
|
||||
$content = file_get_contents(runtime_path('story_stream_project_27_20251111_110715.txt'));
|
||||
$streamData = explode("\r\n", $content);
|
||||
foreach ($streamData as $line) {
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
$connect->send("id: " . uniqid() . "\n", true);
|
||||
$connect->send($line . "\n\n", true);
|
||||
}
|
||||
$connect->send("data: [DONE]\n\n", true);
|
||||
$connect->close();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\expose\enum\EventName;
|
||||
use app\expose\enum\PaymentChannels;
|
||||
use app\expose\helper\Uploads;
|
||||
use app\expose\trait\Json;
|
||||
use app\model\PaymentNotifyWechat;
|
||||
use app\model\PaymentTemplate;
|
||||
use Exception;
|
||||
use support\Log;
|
||||
use support\Request;
|
||||
use think\facade\Db;
|
||||
use Webman\Event\Event;
|
||||
use Yansongda\Pay\Pay;
|
||||
|
||||
class NotifyController
|
||||
{
|
||||
use Json;
|
||||
public function wechat(Request $request, $plugin, $template_id)
|
||||
{
|
||||
$data = $request->post();
|
||||
Log::error('微信支付回调1111', $data);
|
||||
try {
|
||||
$D = $request->post();
|
||||
if (PaymentNotifyWechat::where(['notify_id' => $D['id']])->count()) {
|
||||
return $this->code('SUCCESS', '成功');
|
||||
}
|
||||
$PaymentTemplate = PaymentTemplate::where(['id' => $template_id])->find();
|
||||
if (!$PaymentTemplate) {
|
||||
throw new Exception('支付模板不存在');
|
||||
}
|
||||
$Payment = $PaymentTemplate->value;
|
||||
$config = [
|
||||
'wechat' => [
|
||||
'default' => [
|
||||
// 必填-商户号
|
||||
'mch_id' => $Payment['mch_id'],
|
||||
// 选填-v2商户私钥
|
||||
'mch_secret_key_v2' => '',
|
||||
// 必填-v3商户秘钥
|
||||
'mch_secret_key' => $Payment['mch_key'],
|
||||
// 必填-商户私钥 字符串或路径
|
||||
'mch_secret_cert' => base_path(Uploads::path($Payment['ssl_key'])),
|
||||
// 必填-商户公钥证书路径
|
||||
'mch_public_cert_path' => base_path(Uploads::path($Payment['ssl_cert'])),
|
||||
// 必填
|
||||
'notify_url' => $Payment['notify_url'],
|
||||
// 选填-公众号 的 app_id
|
||||
'mp_app_id' => $Payment['appid'],
|
||||
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
|
||||
'mode' => Pay::MODE_NORMAL,
|
||||
]
|
||||
],
|
||||
'logger' => [
|
||||
'enable' => true,
|
||||
'file' => runtime_path('logs/wechat-notify-' . date('Y-m-d') . '.log'),
|
||||
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
|
||||
'type' => 'single', // optional, 可选 daily.
|
||||
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
|
||||
],
|
||||
'http' => [
|
||||
'timeout' => 5.0,
|
||||
'connect_timeout' => 5.0,
|
||||
],
|
||||
];
|
||||
$wxpay = Pay::wechat($config);
|
||||
$data = $wxpay->callback($D);
|
||||
$obj = null;
|
||||
Log::error('微信支付回调222', (array)$data);
|
||||
Db::startTrans();
|
||||
try {
|
||||
$PaymentNotifyWechat = new PaymentNotifyWechat;
|
||||
$PaymentNotifyWechat->notify_id = $D['id'];
|
||||
$PaymentNotifyWechat->resource_type = $D['resource_type'];
|
||||
$PaymentNotifyWechat->event_type = $D['event_type'];
|
||||
$PaymentNotifyWechat->summary = $D['summary'];
|
||||
$PaymentNotifyWechat->resource_original_type = $D['resource']['original_type'];
|
||||
$PaymentNotifyWechat->resource_algorithm = $D['resource']['algorithm'];
|
||||
$PaymentNotifyWechat->resource_ciphertext = $D['resource']['ciphertext'];
|
||||
$PaymentNotifyWechat->resource_associated_data = $D['resource']['associated_data'];
|
||||
$PaymentNotifyWechat->resource_nonce = $D['resource']['nonce'];
|
||||
$PaymentNotifyWechat->plugin = $plugin;
|
||||
$PaymentNotifyWechat->template_id = $template_id;
|
||||
$PaymentNotifyWechat->save();
|
||||
$class = "plugin\\{$plugin}\\api\\notify\\Wechat";
|
||||
if (class_exists($class)) {
|
||||
$obj = new $class;
|
||||
$obj->{$data->resource['original_type']}($data->resource['ciphertext']);
|
||||
} else {
|
||||
Event::emit(EventName::ORDERS_PAY['value'], [
|
||||
'data' => $data->resource['ciphertext'],
|
||||
'payment_channel' => PaymentChannels::WXPAY['value'],
|
||||
'plugin' => $plugin,
|
||||
'template_id' => $template_id
|
||||
]);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Throwable $th) {
|
||||
Db::rollback();
|
||||
throw $th;
|
||||
}
|
||||
if ($obj && method_exists($obj, 'finish')) {
|
||||
$obj->finish();
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Log::error('微信支付回调错误:' . $th->getMessage(), $th->getTrace());
|
||||
return $this->json(['code' => 'FAIL', 'message' => $th->getMessage()], JSON_UNESCAPED_UNICODE, 400);
|
||||
}
|
||||
return $this->code('SUCCESS', '成功');
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\expose\helper\Config;
|
||||
use app\expose\utils\wechat\OfficialAccount;
|
||||
use support\Log;
|
||||
use support\Request;
|
||||
|
||||
class WechatOfficialAccountController
|
||||
{
|
||||
public function message(Request $request)
|
||||
{
|
||||
$OfficialAccount = new OfficialAccount();
|
||||
$config = Config::get('wechat_official_account');
|
||||
if (!$config['state']) {
|
||||
return 'wechat official account is not enabled';
|
||||
}
|
||||
try {
|
||||
$OfficialAccount->checkSignature($request, $config);
|
||||
if ($request->method() === 'GET') {
|
||||
return $request->get('echostr');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
if ($request->method() === 'GET') {
|
||||
return $th->getMessage();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
return $OfficialAccount->handle($request,$config);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\exception;
|
||||
|
||||
use app\expose\trait\Json;
|
||||
use Throwable;
|
||||
use Webman\Exception\ExceptionHandler;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use support\Log;
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
use Json;
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
public function render(Request $request, Throwable $exception): Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return $this->exception($exception);
|
||||
}
|
||||
if (config('app.debug')) {
|
||||
Log::error($exception->getTraceAsString());
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
return new Response(500, [], $exception->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\api;
|
||||
|
||||
use support\Log;
|
||||
|
||||
trait Install
|
||||
{
|
||||
protected $path;
|
||||
/**
|
||||
* 多语言必须在构造函数中设置plugin值为插件名
|
||||
*/
|
||||
protected $plugin;
|
||||
/**
|
||||
* 安装
|
||||
*
|
||||
* @param $version
|
||||
* @return void
|
||||
*/
|
||||
public static function install($version) {}
|
||||
|
||||
/**
|
||||
* 卸载
|
||||
*
|
||||
* @param $version
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstall($version) {}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param $from_version
|
||||
* @param $to_version
|
||||
* @param $context
|
||||
* @return void
|
||||
*/
|
||||
public static function update($from_version, $to_version, $context = null) {}
|
||||
|
||||
/**
|
||||
* 更新前数据收集等
|
||||
*
|
||||
* @param $from_version
|
||||
* @param $to_version
|
||||
* @return array|array[]
|
||||
*/
|
||||
public static function beforeUpdate($from_version, $to_version)
|
||||
{
|
||||
// 在更新之前获得老菜单,通过context传递给 update
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getMenus()
|
||||
{
|
||||
clearstatcache();
|
||||
# 当前类的运行目录
|
||||
if (is_file($menu_file = $this->path . '/../admin.json')) {
|
||||
$content = file_get_contents($menu_file);
|
||||
if (!$content) {
|
||||
return false;
|
||||
}
|
||||
$data = json_decode($content, true);
|
||||
return $data;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function getPlugin()
|
||||
{
|
||||
return $this->plugin;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class ActionBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($label = '', $props = [])
|
||||
{
|
||||
$this->data = [
|
||||
'label' => $label,
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 向操作按钮中添加字段
|
||||
*
|
||||
* @param string $label 操作按钮标题
|
||||
* @param mixed $extra 额外参数
|
||||
* @param string $extra.path 路径, 例如: '/app/pluginExample/admin/Form/index'
|
||||
* @param string $extra.model 操作模式, 详见 \app\expose\enum\Action::class,例如:Action::COMFIRM['value']
|
||||
* @param mixed $extra.where 显示条件, 例如: [['is_system', '!=', 1]]
|
||||
* @param mixed $extra.params 额外传参, 例如: [['id' => 1]]
|
||||
* @param string|array $extra.field 指定提交字段, 例如: *或['id'=>'uid'],id:数据集中字段名,uid:提交数据中字段名
|
||||
* @param mixed $extra.props 操作属性, 例如: ['type' => 'primary', 'title' => '编辑《{nickname}》']
|
||||
* @param mixed $extra.component 操作展示组件
|
||||
* @param string $extra.component.name 组件名, 例如: 'button'
|
||||
* @param mixed $extra.component.props 组件属性, 例如: ['type' => 'primary', 'size' => 'small']
|
||||
* @return ActionBuilder
|
||||
*/
|
||||
public function add(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => array_merge([
|
||||
'loading' => false,
|
||||
], $extra)
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return ActionBuilder
|
||||
*/
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据操作
|
||||
*
|
||||
* @param string $action 操作,append:追加合并数据
|
||||
* @return ActionBuilder
|
||||
*/
|
||||
public function setDataAction(string $action)
|
||||
{
|
||||
$this->data['callback'] = $action;
|
||||
return $this;
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class AppsBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($props = [])
|
||||
{
|
||||
$this->data['props'] = array_merge([], $props);
|
||||
$this->data['columns'] = [];
|
||||
}
|
||||
public function add(string $prop, string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['columns'][] = [
|
||||
'prop' => $prop,
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['action'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addTabs(string $label, string $name, mixed $extra)
|
||||
{
|
||||
$this->data['tabs'][] = [
|
||||
'label' => $label,
|
||||
'name' => $name,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addScreen()
|
||||
{
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class ComponentBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function add(string $component, mixed $children = null, array $props = [])
|
||||
{
|
||||
$this->data[] = [
|
||||
'component' => $component,
|
||||
'children' => $children,
|
||||
'props' => $props
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
$result = $this->data;
|
||||
$this->data = [];
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class DataboardBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($props = [])
|
||||
{
|
||||
$this->data['props'] = array_merge([
|
||||
'gutter' => 0
|
||||
], $props);
|
||||
$this->data['rows'] = [];
|
||||
}
|
||||
public function add(Basic $component, array $props = [], array $row = [])
|
||||
{
|
||||
$this->data['rows'][] = [
|
||||
'props' => $props,
|
||||
'row' => $row,
|
||||
'component' => $component
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
/**
|
||||
* 审核表单构建器
|
||||
*
|
||||
* @package app\expose\build\builder
|
||||
* @property string $formName 审核表单名
|
||||
* @property string $formTitle 审核表单标题
|
||||
* @property array $props Form Attributes
|
||||
* @method ExamineBuilder add(string $field, string $title, string $component, string $value = null, mixed $extra = []) 向审核表单中添加字段
|
||||
* @method ExamineBuilder remove(string $field) 从审核表单中移除字段
|
||||
* @method ExamineBuilder setOldData(array $data) 设置审核原始对比数据
|
||||
* @method ExamineBuilder setNewData(array $data) 设置审核最新对比数据
|
||||
* @method FormBuilder addForm(FormBuilder $builder) 添加表单
|
||||
*/
|
||||
class ExamineBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
protected $translations=false;
|
||||
public function __construct( $props = [])
|
||||
{
|
||||
if(isset($props['translations'])){
|
||||
$this->translations=$props['translations'];
|
||||
unset($props['translations']);
|
||||
}
|
||||
$this->data['props'] = $props;
|
||||
$this->data['rule'] = [];
|
||||
$this->data['form'] = [];
|
||||
}
|
||||
public function setTranslations(bool $state=true){
|
||||
$this->translations=$state;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 向审核表单中添加字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param string $title 字段标题
|
||||
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
|
||||
* @param mixed $extra 额外参数
|
||||
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
|
||||
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
|
||||
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
|
||||
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
|
||||
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
|
||||
* @return ExamineBuilder
|
||||
*/
|
||||
public function add(string $field, string $title, string $component, mixed $extra = []): ExamineBuilder
|
||||
{
|
||||
$title = $this->trans($title);
|
||||
$this->data['rule'][] = [
|
||||
'field' => $field,
|
||||
'title' => $title,
|
||||
'component' => $component,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 从审核表单中移除字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @return ExamineBuilder
|
||||
*/
|
||||
public function remove(string $field): ExamineBuilder
|
||||
{
|
||||
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
|
||||
return $item['field'] != $field;
|
||||
});
|
||||
$this->data['rule'] = array_values($rule);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置审核原始对比数据
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return ExamineBuilder
|
||||
*/
|
||||
public function setOldData(array $data): ExamineBuilder
|
||||
{
|
||||
$this->data['old']=$data;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置审核最新对比数据
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return ExamineBuilder
|
||||
*/
|
||||
public function setNewData(array $data): ExamineBuilder
|
||||
{
|
||||
$this->data['new']=$data;
|
||||
return $this;
|
||||
}
|
||||
public function addForm(FormBuilder $builder)
|
||||
{
|
||||
$this->data['form']=$builder;
|
||||
}
|
||||
public function trans(string|null $val,array $parameters =[])
|
||||
{
|
||||
if(!$val||!$this->translations){
|
||||
return $val;
|
||||
}
|
||||
$request=request();
|
||||
$lang = '';
|
||||
$domain=null;
|
||||
if ($request && $request->lang) {
|
||||
$lang = $request->lang;
|
||||
$domain=$request->app;
|
||||
}
|
||||
return trans($val,$parameters,$domain,$lang);
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
/**
|
||||
* 表单构建器
|
||||
*
|
||||
* @package app\expose\build\builder
|
||||
* @property string $formName 表单名
|
||||
* @property string $formTitle 表单标题
|
||||
* @property array $props Form Attributes
|
||||
* @method FormBuilder add(string $field, string $title, string $component, string $value = null, mixed $extra = [],?ActionBuilder $action = null) 向表单中添加字段
|
||||
* @method FormBuilder remove(string $field) 从表单中移除字段
|
||||
* @method FormBuilder addValue(string $field, mixed $value) 向表单中添加数据
|
||||
* @method FormBuilder setData(array $data) 设置表单数据
|
||||
* @method string getFormName() 获取表单名
|
||||
* @method string getFormTitle() 获取表单标题
|
||||
* @method FormBuilder addGroupForm(FormBuilder $builder) 向表单中添加分组
|
||||
* @method FormBuilder setUrl(string $url) 设置表单提交地址
|
||||
*/
|
||||
class FormBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
protected $url = '';
|
||||
protected $translations=false;
|
||||
public function __construct($formName = null, $formTitle = null, $props = [])
|
||||
{
|
||||
if(isset($props['translations'])){
|
||||
$this->translations=$props['translations'];
|
||||
unset($props['translations']);
|
||||
}
|
||||
$this->data['props'] = $props;
|
||||
$this->data['name'] = $formName ?? 'basic';
|
||||
$this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title');
|
||||
$this->data['rule'] = [];
|
||||
$this->data['form'] = [];
|
||||
$this->data['group'] = [];
|
||||
$this->data['url'] = $this->url;
|
||||
}
|
||||
public function setTranslations(bool $state=true){
|
||||
$this->translations=$state;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 向表单中添加字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param string $title 字段标题
|
||||
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
|
||||
* @param mixed|null $value 默认值
|
||||
* @param mixed $extra 额外参数
|
||||
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
|
||||
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
|
||||
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
|
||||
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
|
||||
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
|
||||
* @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function add(string $field, string $title, string $component, mixed $value = null, mixed $extra = [],?ActionBuilder $action = null): FormBuilder
|
||||
{
|
||||
$title = $this->trans($title);
|
||||
$this->data['rule'][] = [
|
||||
'field' => $field,
|
||||
'title' => $title,
|
||||
'component' => $component,
|
||||
'extra' => $extra,
|
||||
'action' => $action ? $action->builder() : null
|
||||
];
|
||||
$this->addValue($field, $value);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 从表单中移除字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function remove(string $field): FormBuilder
|
||||
{
|
||||
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
|
||||
return $item['field'] != $field;
|
||||
});
|
||||
$this->data['rule'] = array_values($rule);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 向表单中添加数据
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param mixed $value 默认值
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function addValue(string $field, mixed $value): FormBuilder
|
||||
{
|
||||
$this->data['form'][$field] = $value;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置表单数据
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function setData(array $data): FormBuilder
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->addValue($key, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 获取表单名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName(): string
|
||||
{
|
||||
return $this->data['name'];
|
||||
}
|
||||
/**
|
||||
* 获取表单标题
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormTitle(): string
|
||||
{
|
||||
return $this->data['title'];
|
||||
}
|
||||
/**
|
||||
* 向表单中添加分组
|
||||
*
|
||||
* @param FormBuilder $builder 分组表单
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function addGroupForm(FormBuilder $builder): FormBuilder
|
||||
{
|
||||
$formName = $builder->getFormName();
|
||||
$temp = $builder->toArray();
|
||||
$this->data['group'][] = $temp;
|
||||
$this->addValue($formName, $temp['form']);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置表单提交地址
|
||||
*
|
||||
* @param string $url 地址
|
||||
* @return FormBuilder
|
||||
*/
|
||||
public function setUrl(string $url): FormBuilder
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->data['url'] = $url;
|
||||
return $this;
|
||||
}
|
||||
public function trans(string|null $val,array $parameters =[])
|
||||
{
|
||||
if(!$val||!$this->translations){
|
||||
return $val;
|
||||
}
|
||||
$request=request();
|
||||
$lang = '';
|
||||
$domain=null;
|
||||
if ($request && $request->lang) {
|
||||
$lang = $request->lang;
|
||||
$domain=$request->app;
|
||||
}
|
||||
return trans($val,$parameters,$domain,$lang);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class ImagesBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($props = [])
|
||||
{
|
||||
$this->data['props'] = array_merge([
|
||||
'image'=>'url',
|
||||
'title'=>'title',
|
||||
'info'=>'info'
|
||||
], $props);
|
||||
}
|
||||
public function addSelection(string $prop = '', string $label = '', mixed $extra = [])
|
||||
{
|
||||
if (!isset($extra['props'])) {
|
||||
$extra['props'] = [
|
||||
'width' => '50px',
|
||||
'type' => 'selection'
|
||||
];
|
||||
}
|
||||
$extra['props']['type'] = 'selection';
|
||||
$this->data['selection'] = true;
|
||||
return $this;
|
||||
}
|
||||
public function addAction(string|ActionBuilder $label, mixed $extra = [])
|
||||
{
|
||||
if ($label instanceof ActionBuilder) {
|
||||
$this->data['action'] = $label->builder();
|
||||
return $this;
|
||||
}
|
||||
if (!isset($this->data['action'])) {
|
||||
$this->data['action'] = [];
|
||||
}
|
||||
$this->data['action'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addFooter(mixed $props = [])
|
||||
{
|
||||
if (!isset($this->data['selection'])) {
|
||||
$this->addSelection();
|
||||
}
|
||||
$this->data['footer'] = [
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addFooterAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['footer']['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addHeader(array|ActionBuilder $props = [])
|
||||
{
|
||||
if ($props instanceof ActionBuilder) {
|
||||
$this->data['header'] = $props->builder();
|
||||
return $this;
|
||||
}
|
||||
$this->data['header'] = [
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addHeaderAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['header']['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addScreen(FormBuilder $builder)
|
||||
{
|
||||
$this->data['screen'] = $builder;
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
/**
|
||||
* 信息展示构建器
|
||||
*
|
||||
* @package app\expose\build\builder
|
||||
* @property string $formName 信息展示名
|
||||
* @property string $formTitle 信息展示标题
|
||||
* @property array $props Form Attributes
|
||||
* @method InfoBuilder add(string $field, string $title, string $component, mixed $extra = [],?ActionBuilder $action = null) 向信息展示中添加字段
|
||||
* @method InfoBuilder remove(string $field) 从信息展示中移除字段
|
||||
* @method InfoBuilder addValue(string $field, mixed $value) 向信息展示中添加数据
|
||||
* @method InfoBuilder setData(array $data) 设置信息展示数据
|
||||
* @method InfoBuilder addGroupInfo(InfoBuilder $builder) 向信息展示中添加分组
|
||||
*/
|
||||
class InfoBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
protected $translations = false;
|
||||
public function __construct($formName = null, $formTitle = null, $props = [])
|
||||
{
|
||||
if (isset($props['translations'])) {
|
||||
$this->translations = $props['translations'];
|
||||
unset($props['translations']);
|
||||
}
|
||||
$this->data['props'] = $props;
|
||||
$this->data['name'] = $formName ?? 'basic';
|
||||
$this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title');
|
||||
$this->data['rule'] = [];
|
||||
$this->data['group'] = [];
|
||||
$this->data['data'] = [];
|
||||
}
|
||||
public function setTranslations(bool $state = true)
|
||||
{
|
||||
$this->translations = $state;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 向信息展示中添加字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param string $title 字段标题
|
||||
* @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库
|
||||
* @param mixed $extra 额外参数
|
||||
* @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]]
|
||||
* @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容']
|
||||
* @param mixed $extra.subProps 子组件选项属性,和$extra.props类似
|
||||
* @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ]
|
||||
* @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder
|
||||
* @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder
|
||||
* @return InfoBuilder
|
||||
*/
|
||||
public function add(string $field, string $title, string $component, mixed $extra = [], ?ActionBuilder $action = null): InfoBuilder
|
||||
{
|
||||
$title = $this->trans($title);
|
||||
$this->data['rule'][] = [
|
||||
'field' => $field,
|
||||
'title' => $title,
|
||||
'component' => $component,
|
||||
'extra' => $extra,
|
||||
'action' => $action ? $action->builder() : null
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 从信息展示中移除字段
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @return InfoBuilder
|
||||
*/
|
||||
public function remove(string $field): InfoBuilder
|
||||
{
|
||||
$rule = array_filter($this->data['rule'], function ($item) use ($field) {
|
||||
return $item['field'] != $field;
|
||||
});
|
||||
$this->data['rule'] = array_values($rule);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 向表单中添加数据
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param mixed $value 默认值
|
||||
* @return InfoBuilder
|
||||
*/
|
||||
public function addValue(string $field, mixed $value): InfoBuilder
|
||||
{
|
||||
$this->data['data'][$field] = $value;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置表单数据
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return InfoBuilder
|
||||
*/
|
||||
public function setData(array $data): InfoBuilder
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->addValue($key, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 获取表单名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInfoName(): string
|
||||
{
|
||||
return $this->data['name'];
|
||||
}
|
||||
/**
|
||||
* 获取表单标题
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInfoTitle(): string
|
||||
{
|
||||
return $this->data['title'];
|
||||
}
|
||||
/**
|
||||
* 向表单中添加分组
|
||||
*
|
||||
* @param InfoBuilder $builder 分组表单
|
||||
* @return InfoBuilder
|
||||
*/
|
||||
public function addGroupInfo(InfoBuilder $builder): InfoBuilder
|
||||
{
|
||||
$infoName = $builder->getInfoName();
|
||||
$temp = $builder->toArray();
|
||||
$this->data['group'][] = $temp;
|
||||
$this->addValue($infoName, $temp['data']);
|
||||
return $this;
|
||||
}
|
||||
public function trans(string|null $val, array $parameters = [])
|
||||
{
|
||||
if (!$val || !$this->translations) {
|
||||
return $val;
|
||||
}
|
||||
$request = request();
|
||||
$lang = '';
|
||||
$domain = null;
|
||||
if ($request && $request->lang) {
|
||||
$lang = $request->lang;
|
||||
$domain = $request->app;
|
||||
}
|
||||
return trans($val, $parameters, $domain, $lang);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class RechargeBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($props = [])
|
||||
{
|
||||
$this->data['props'] = array_merge([], $props);
|
||||
$this->data['list'] = [];
|
||||
$this->data['prompt']=[];
|
||||
}
|
||||
public function addPayment(mixed $data)
|
||||
{
|
||||
$this->data['payment']=$data;
|
||||
return $this;
|
||||
}
|
||||
public function addList(mixed $data)
|
||||
{
|
||||
$this->data['list']=$data;
|
||||
return $this;
|
||||
}
|
||||
public function addQrcodePrompt(ComponentBuilder $builder)
|
||||
{
|
||||
$this->data['prompt']['qrcode'][] = $builder->builder();
|
||||
return $this;
|
||||
}
|
||||
public function addAgreementPrompt(ComponentBuilder $builder)
|
||||
{
|
||||
$this->data['prompt']['agreement'][] = $builder->builder();
|
||||
return $this;
|
||||
}
|
||||
public function addFooterPrompt(ComponentBuilder $builder)
|
||||
{
|
||||
$this->data['prompt']['footer'][] = $builder->builder();
|
||||
return $this;
|
||||
}
|
||||
public function addSwiperPrompt(ComponentBuilder $builder)
|
||||
{
|
||||
$this->data['prompt']['swiper'][] = $builder->builder();
|
||||
return $this;
|
||||
}
|
||||
public function setCreateOrdersApi(string $api)
|
||||
{
|
||||
$this->data['create_orders']=$api;
|
||||
return $this;
|
||||
}
|
||||
public function setGetOrdersStateApi(string $api)
|
||||
{
|
||||
$this->data['get_orders_state']=$api;
|
||||
return $this;
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
class SoltBuilder
|
||||
{
|
||||
public $name = 'default';
|
||||
public function __construct($name = 'default')
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public static function solt(string $solt = 'default')
|
||||
{
|
||||
$self = new self($solt);
|
||||
return $self;
|
||||
}
|
||||
public function text(string $text)
|
||||
{
|
||||
return [$this->name => $text];
|
||||
}
|
||||
public function add(mixed $component, mixed $props = [], mixed $children = null)
|
||||
{
|
||||
return [
|
||||
$this->name => [
|
||||
'component' => $component,
|
||||
'props' => $props,
|
||||
'children' => $children
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class TableBuilder extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($props = [])
|
||||
{
|
||||
$this->data['props'] = array_merge([
|
||||
'border' => true,
|
||||
'stripe' => true,
|
||||
'size' => 'large',
|
||||
'header-row-style' => [
|
||||
'--el-table-header-bg-color' => 'var(--el-bg-color-page)'
|
||||
]
|
||||
], $props);
|
||||
$this->data['columns'] = [];
|
||||
}
|
||||
public function add(string $prop, string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['columns'][] = [
|
||||
'prop' => $prop,
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addSelection(string $prop = '', string $label = '', mixed $extra = [])
|
||||
{
|
||||
if (!isset($extra['props'])) {
|
||||
$extra['props'] = [
|
||||
'width' => '50px',
|
||||
'type' => 'selection'
|
||||
];
|
||||
}
|
||||
$extra['props']['type'] = 'selection';
|
||||
$this->add($prop, $label, $extra);
|
||||
$this->data['selection'] = true;
|
||||
return $this;
|
||||
}
|
||||
public function addAction(string|ActionBuilder $label, mixed $props = [])
|
||||
{
|
||||
if ($label instanceof ActionBuilder) {
|
||||
$this->data['action'] = $label->builder();
|
||||
return $this;
|
||||
}
|
||||
$this->data['action'] = [
|
||||
'label' => $label,
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addTableAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['action']['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addFooter(mixed $props = [])
|
||||
{
|
||||
if (!isset($this->data['selection'])) {
|
||||
$this->addSelection();
|
||||
}
|
||||
$this->data['footer'] = [
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addFooterAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['footer']['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addHeader(array|ActionBuilder $props = [])
|
||||
{
|
||||
if ($props instanceof ActionBuilder) {
|
||||
$this->data['header'] = $props->builder();
|
||||
return $this;
|
||||
}
|
||||
$this->data['header'] = [
|
||||
'extra' => [
|
||||
'group' => [],
|
||||
'props' => $props,
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addHeaderAction(string $label, mixed $extra = [])
|
||||
{
|
||||
$this->data['header']['extra']['group'][] = [
|
||||
'label' => $label,
|
||||
'extra' => $extra
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
public function addScreen(FormBuilder $builder)
|
||||
{
|
||||
$this->data['screen'] = $builder;
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Basic extends DataModel
|
||||
{
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\component;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\Str;
|
||||
|
||||
/**
|
||||
* 申请组件
|
||||
*
|
||||
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
|
||||
*
|
||||
* @method Apply setLabel(string $label) 设置标签
|
||||
* @method Apply setTips(string $tips) 设置提示
|
||||
* @method Apply setApply(bool $apply) 设置是否可以申请
|
||||
* @method Apply setPrice(int $price) 设置价格
|
||||
* @method Apply setAgreement(array $agreement) 设置协议
|
||||
* @method Apply setClass(string $class) 设置class属性
|
||||
* @method Apply setInterval(int $interval) 自动更新数据的时间间隔
|
||||
*/
|
||||
class Apply extends Basic implements DataboardBuilderInterface
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->data['name'] = 'apply';
|
||||
$this->data['props'] = [];
|
||||
}
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$name = lcfirst(str_replace('set', '', $name));
|
||||
$name = Str::snake($name, '_');
|
||||
$this->data['props'][$name] = $arguments[0];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return Apply
|
||||
*/
|
||||
public function setData(mixed $data): Apply
|
||||
{
|
||||
$this->data['props']['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
public function setApi(string $api): Apply
|
||||
{
|
||||
$this->data['props']['api'] = $api;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\component;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\Str;
|
||||
|
||||
/**
|
||||
* 申请组件
|
||||
*
|
||||
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
|
||||
*
|
||||
* @method Domain setLabel(string $label) 设置标签
|
||||
* @method Domain setTips(string $tips) 设置提示
|
||||
* @method Domain setClass(string $class) 设置class属性
|
||||
* @method Domain setInterval(int $interval) 自动更新数据的时间间隔
|
||||
*/
|
||||
class Domain extends Basic implements DataboardBuilderInterface
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->data['name'] = 'domain';
|
||||
$this->data['props'] = [];
|
||||
}
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$name = lcfirst(str_replace('set', '', $name));
|
||||
$name = Str::snake($name, '_');
|
||||
$this->data['props'][$name] = $arguments[0];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return Domain
|
||||
*/
|
||||
public function setData(mixed $data): Domain
|
||||
{
|
||||
$this->data['props']['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
public function setApi(string $api): Domain
|
||||
{
|
||||
$this->data['props']['api'] = $api;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\component;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\Str;
|
||||
|
||||
class Echarts extends Basic implements DataboardBuilderInterface
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->data['name'] = 'echarts';
|
||||
$this->data['props'] = [];
|
||||
}
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$name = lcfirst(str_replace('set', '', $name));
|
||||
$name = Str::snake($name, '_');
|
||||
$this->data['props'][$name] = $arguments[0];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return echarts
|
||||
*/
|
||||
public function setData(mixed $data): echarts
|
||||
{
|
||||
$this->data['props']['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
public function setApi(string $api): echarts
|
||||
{
|
||||
$this->data['props']['api'] = $api;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\component;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\Str;
|
||||
class Placeholder extends Basic implements DataboardBuilderInterface
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->data['name'] = 'div';
|
||||
$this->data['props'] = [];
|
||||
}
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$name = lcfirst(str_replace('set', '', $name));
|
||||
$name = Str::snake($name, '_');
|
||||
$this->data['props'][$name] = $arguments[0];
|
||||
return $this;
|
||||
}
|
||||
public function setData(mixed $data): Placeholder
|
||||
{
|
||||
$this->data['props']['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
public function setApi(string $api): Placeholder
|
||||
{
|
||||
$this->data['props']['api'] = $api;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\component;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\interface\DataboardBuilderInterface;
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
use app\expose\utils\Str;
|
||||
|
||||
/**
|
||||
* 统计组件
|
||||
*
|
||||
* 使用set+属性名的方式设置属性,如setLabel、setTips、setUnit等
|
||||
*
|
||||
* @method statistic setLabel(string $label) 设置标签
|
||||
* @method statistic setTips(string $tips) 设置提示
|
||||
* @method statistic setUnit(string $unit) 设置单位
|
||||
* @method statistic setClass(string $class) 设置class属性
|
||||
* @method statistic setInterval(int $interval) 自动更新数据的时间间隔
|
||||
*/
|
||||
class Statistic extends Basic implements DataboardBuilderInterface
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->data['name'] = 'statistic';
|
||||
$this->data['props'] = [
|
||||
'yesterday_label'=>'昨日',
|
||||
];
|
||||
}
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$name = lcfirst(str_replace('set', '', $name));
|
||||
$name = Str::snake($name, '_');
|
||||
$this->data['props'][$name] = $arguments[0];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 设置数据
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param number $data.today 今日数据
|
||||
* @param number $data.yesterday 昨日数据
|
||||
* @param number $data.growth_rate 增长率
|
||||
* @return statistic
|
||||
*/
|
||||
public function setData(mixed $data): statistic
|
||||
{
|
||||
$this->data['props']['data'] = $data;
|
||||
return $this;
|
||||
}
|
||||
public function setApi(string $api): statistic
|
||||
{
|
||||
$this->data['props']['api'] = $api;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\builder\databoardBuilder\interface;
|
||||
|
||||
use app\expose\build\builder\databoardBuilder\Basic;
|
||||
|
||||
interface DataboardBuilderInterface
|
||||
{
|
||||
public function setData(mixed $data): Basic;
|
||||
public function setApi(string $api): Basic;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\config;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Action extends DataModel
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = array_merge($this->data, $data);
|
||||
}
|
||||
/**
|
||||
* 添加一个操作
|
||||
*
|
||||
* @param string $model 查看 \app\expose\enum\Action::class, 例如 \app\expose\enum\Action::LOCK['value']
|
||||
* @param array $props ['icon' => 'Lock','label' => '锁屏']
|
||||
* @return Action
|
||||
*/
|
||||
public function add($model, $props = []): Action
|
||||
{
|
||||
$this->data[] = [
|
||||
'model' => $model,
|
||||
'props' => $props
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\config;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Apis extends DataModel
|
||||
{
|
||||
protected $data = [
|
||||
'userinfo' => 'User/getUserInfo',
|
||||
];
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = array_merge($this->data, $data);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\config;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Login extends DataModel
|
||||
{
|
||||
protected $data = [
|
||||
# 是否启用登录
|
||||
'enable' => true,
|
||||
# 登录标题
|
||||
'title' => '账号密码登录',
|
||||
# 登录请求地址
|
||||
'url' => 'Login/login',
|
||||
# 检测登录状态地址
|
||||
'check' => 'Login/check',
|
||||
# 登录页背景,auto:自动,off:关闭,url:自定义,url[]:多张图片
|
||||
'bg_image' => 'auto',
|
||||
# 登录页展示图片
|
||||
'image' => '',
|
||||
# 开启图形验证码,在短信登录时默认开启
|
||||
'captcha' => true,
|
||||
# 跳转链接文案
|
||||
'link_text' => '',
|
||||
# 用户协议链接
|
||||
'user_agreement' => '/article/user_agreement.html',
|
||||
# 用户协议自定义配置
|
||||
/* 'user_agreement_config'=>[
|
||||
'title'=>'《用户协议》',
|
||||
'label'=>'点击"登录"即代表你已阅读并同意'
|
||||
] */
|
||||
];
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = array_merge($this->data, $data);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\build\config;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Web extends DataModel
|
||||
{
|
||||
protected $data = [
|
||||
# 应用名称
|
||||
'web_name' => 'Loong',
|
||||
# LOGO展示方式
|
||||
'logo_use' => 'svg',
|
||||
# 应用浅色LOGO
|
||||
'web_logo_light' => '',
|
||||
# 应用深色LOGO
|
||||
'web_logo_dark' => '',
|
||||
# ICP备案
|
||||
'web_icp' => '',
|
||||
# 公安备案号
|
||||
'web_mps' => '',
|
||||
# 公安备案文案
|
||||
'web_mps_text' => '',
|
||||
# 网站标题
|
||||
'web_title' => '',
|
||||
# 版本名称
|
||||
'version_name' => 'v1.0.0',
|
||||
# 版本号
|
||||
'version' => 10000,
|
||||
# 版权信息
|
||||
'copyright' => 'copyright © 2020 RenLoong All Rights Reserved',
|
||||
# 微信客服链接
|
||||
'wechat_url' => '',
|
||||
# 客服二维码
|
||||
'wechat_qrcode_url' => '',
|
||||
# 页面布局,control: 控制台布局,admin: 后台布局
|
||||
'layout' => 'control',
|
||||
];
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$request = request();
|
||||
$this->data['layout'] = $request->app;
|
||||
$this->data = array_merge($this->data, $data);
|
||||
}
|
||||
public function useLogin(array $data = [])
|
||||
{
|
||||
$this->data['login'] = new Login($data);
|
||||
return $this;
|
||||
}
|
||||
public function useVcode(array $data = [])
|
||||
{
|
||||
$this->data['vcode'] = new Login($data);
|
||||
return $this;
|
||||
}
|
||||
public function useRegister(array $data = [])
|
||||
{
|
||||
$this->data['register'] = new Login($data);
|
||||
return $this;
|
||||
}
|
||||
public function useQrcodeLogin(array $data = [])
|
||||
{
|
||||
$this->data['qrcode_login'] = new Login($data);
|
||||
return $this;
|
||||
}
|
||||
public function useApis(array $data = [])
|
||||
{
|
||||
$apis = new Apis($data);
|
||||
if (!isset($this->data['apis'])) {
|
||||
$this->data['apis'] = [];
|
||||
}
|
||||
$this->data['apis'] = array_merge($this->data['apis'], $apis->toArray());
|
||||
return $this;
|
||||
}
|
||||
public function useToolbar(array $data = [])
|
||||
{
|
||||
if (!isset($this->data['toolbar'])) {
|
||||
$this->data['toolbar'] = [];
|
||||
}
|
||||
$this->data['toolbar'] = array_merge($this->data['toolbar'], $data);
|
||||
return $this;
|
||||
}
|
||||
public function useUserDropdownMenu(array $data = [])
|
||||
{
|
||||
if (!isset($this->data['user_dropdown_menu'])) {
|
||||
$this->data['user_dropdown_menu'] = [];
|
||||
}
|
||||
$this->data['user_dropdown_menu'] = array_merge($this->data['user_dropdown_menu'], $data);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Action extends Enum
|
||||
{
|
||||
const DEFAULT = [
|
||||
'label' => '页面跳转',
|
||||
'value' => ''
|
||||
];
|
||||
const REDIRECT = [
|
||||
'label' => '重定向',
|
||||
'value' => 'redirect'
|
||||
];
|
||||
const LINK= [
|
||||
'label' => '链接跳转',
|
||||
'value' => 'link'
|
||||
];
|
||||
const COMFIRM = [
|
||||
'label' => '确认操作',
|
||||
'value' => 'comfirm'
|
||||
];
|
||||
const DIALOG = [
|
||||
'label' => '弹窗操作',
|
||||
'value' => 'dialog'
|
||||
];
|
||||
const REQUEST = [
|
||||
'label' => '请求操作',
|
||||
'value' => 'request'
|
||||
];
|
||||
const LOCK = [
|
||||
'label' => '锁屏',
|
||||
'value' => 'Lock'
|
||||
];
|
||||
const SEARCH = [
|
||||
'label' => '搜索',
|
||||
'value' => 'Search'
|
||||
];
|
||||
const NOTIFICATION = [
|
||||
'label' => '通知',
|
||||
'value' => 'Notification'
|
||||
];
|
||||
const FULL_SCREEN = [
|
||||
'label' => '全屏',
|
||||
'value' => 'FullScreen'
|
||||
];
|
||||
const OUT_LOGIN = [
|
||||
'label' => '退出登录',
|
||||
'value' => 'OutLogin'
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class AlipayRsaModel extends Enum
|
||||
{
|
||||
const PUBLIC_CERT = [
|
||||
'label' => '公钥证书',
|
||||
'value' => 'public_cert',
|
||||
];
|
||||
const PUBLIC_KEY = [
|
||||
'label' => '公钥',
|
||||
'value' => 'public_key',
|
||||
];
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class EventName extends Enum
|
||||
{
|
||||
const USER_REGISTER = [
|
||||
'label' => '注册',
|
||||
'value' => 'USER.REGISTER',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
];
|
||||
const USER_LOGIN = [
|
||||
'label' => '登录',
|
||||
'value' => 'USER.LOGIN',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const USER_LOGOUT = [
|
||||
'label' => '退出',
|
||||
'value' => 'USER.LOGOUT',
|
||||
'props' => [
|
||||
'type' => 'warning'
|
||||
]
|
||||
];
|
||||
const USER_UPDATE = [
|
||||
'label' => '更新用户信息',
|
||||
'value' => 'USER.UPDATE',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const ORDERS_CREATE = [
|
||||
'label' => '创建订单',
|
||||
'value' => 'ORDERS.CREATE',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const ORDERS_PAY = [
|
||||
'label' => '支付订单',
|
||||
'value' => 'ORDERS.PAY',
|
||||
'props' => [
|
||||
'type' => 'danger'
|
||||
]
|
||||
];
|
||||
const ORDERS_PAY_SUCCESS = [
|
||||
'label' => '支付成功',
|
||||
'value' => 'ORDERS.PAY_SUCCESS',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const ORDERS_CANCEL = [
|
||||
'label' => '取消订单',
|
||||
'value' => 'ORDERS.CANCEL',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const ORDERS_REFUND = [
|
||||
'label' => '退款订单',
|
||||
'value' => 'ORDERS.REFUND',
|
||||
'props' => [
|
||||
'type' => 'warning'
|
||||
]
|
||||
];
|
||||
const ORDERS_FINISH = [
|
||||
'label' => '完成订单',
|
||||
'value' => 'ORDERS.FINISH',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const WECHAT_OFFICIAL_ACCOUNT_SCAN = [
|
||||
'label' => '微信公众号扫码',
|
||||
'value' => 'WECHAT_OFFICIAL_ACCOUNT.SCAN',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
];
|
||||
const WECHAT_OFFICIAL_ACCOUNT_SUBSCRLBE=[
|
||||
'label' => '微信公众号关注',
|
||||
'value' => 'WECHAT_OFFICIAL_ACCOUNT.SUBSCRLBE',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
];
|
||||
const WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE=[
|
||||
'label' => '微信公众号取关',
|
||||
'value' => 'WECHAT_OFFICIAL_ACCOUNT.UNSUBSCRLBE',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
];
|
||||
const NOTIFICATION_LIMIT= [
|
||||
'label' => '有限通知',
|
||||
'value' => 'NOTIFICATION.LIMIT',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const NOTIFICATION_UNLIMIT= [
|
||||
'label' => '无限通知',
|
||||
'value' => 'NOTIFICATION.UNLIMIT',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Examine extends Enum
|
||||
{
|
||||
const WAIT = [
|
||||
'label' => '待审核',
|
||||
'value' => 0,
|
||||
'props'=>[
|
||||
'type'=>'primary'
|
||||
]
|
||||
];
|
||||
const PASS = [
|
||||
'label' => '通过',
|
||||
'value' => 1,
|
||||
'props'=>[
|
||||
'type'=>'success'
|
||||
]
|
||||
];
|
||||
const REJECT = [
|
||||
'label' => '驳回',
|
||||
'value' => 2,
|
||||
'props'=>[
|
||||
'type'=>'danger'
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Filesystem extends Enum
|
||||
{
|
||||
const PUBLIC = [
|
||||
'label' => '本地存储(外部)',
|
||||
'value' => 'public'
|
||||
];
|
||||
const LOCAL = [
|
||||
'label' => '本地存储(内部)',
|
||||
'value' => 'local'
|
||||
];
|
||||
const OSS = [
|
||||
'label' => '阿里云存储',
|
||||
'value' => 'oss'
|
||||
];
|
||||
const COS = [
|
||||
'label' => '腾讯云存储',
|
||||
'value' => 'cos'
|
||||
];
|
||||
const QINIU = [
|
||||
'label' => '七牛存储',
|
||||
'value' => 'qiniu'
|
||||
];
|
||||
const FTP = [
|
||||
'label' => 'FTP存储',
|
||||
'value' => 'ftp'
|
||||
];
|
||||
const S3 = [
|
||||
'label' => 'S3存储',
|
||||
'value' => 's3'
|
||||
];
|
||||
const MINIO = [
|
||||
'label' => 'Minio存储',
|
||||
'value' => 'minio'
|
||||
];
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Methods extends Enum
|
||||
{
|
||||
const GET = [
|
||||
'label' => 'GET',
|
||||
'value' => 'GET'
|
||||
];
|
||||
const POST = [
|
||||
'label' => 'POST',
|
||||
'value' => 'POST'
|
||||
];
|
||||
const PUT = [
|
||||
'label' => 'PUT',
|
||||
'value' => 'PUT'
|
||||
];
|
||||
const DELETE = [
|
||||
'label' => 'DELETE',
|
||||
'value' => 'DELETE'
|
||||
];
|
||||
const OPTIONS = [
|
||||
'label' => 'OPTIONS',
|
||||
'value' => 'OPTIONS'
|
||||
];
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class PaymentChannels extends Enum
|
||||
{
|
||||
const WXPAY = [
|
||||
'label' => '微信支付',
|
||||
'value' => 'wxpay',
|
||||
'icon' => '/static/image/wxpay.png',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const ALIPAY = [
|
||||
'label' => '支付宝支付',
|
||||
'value' => 'alipay',
|
||||
'icon' => '/static/image/alipay.png',
|
||||
'props' => [
|
||||
'type' => 'primary'
|
||||
]
|
||||
];
|
||||
const BALANCE = [
|
||||
'label' => '余额支付',
|
||||
'value' => 'balance',
|
||||
'icon' => '/static/image/balance.png',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const INTEGRAL = [
|
||||
'label' => '积分支付',
|
||||
'value' => 'integral',
|
||||
'icon' => '/static/image/integral.png',
|
||||
'props' => [
|
||||
'type' => 'warning'
|
||||
]
|
||||
];
|
||||
const CASH = [
|
||||
'label' => '现金支付',
|
||||
'value' => 'cash',
|
||||
'icon' => '/static/image/cash.png',
|
||||
'props' => [
|
||||
'type' => 'danger'
|
||||
]
|
||||
];
|
||||
const COUPON = [
|
||||
'label' => '优惠券支付',
|
||||
'value' => 'coupon',
|
||||
'icon' => '/static/image/coupon.png',
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const FREE = [
|
||||
'label' => '免费',
|
||||
'value' => 'free',
|
||||
'icon' => '/static/image/free.png',
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const ADMIN = [
|
||||
'label' => '管理员操作',
|
||||
'value' => 'admin',
|
||||
'icon' => '/static/image/admin.png',
|
||||
'props' => [
|
||||
'type' => 'warning'
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Permission extends Enum
|
||||
{
|
||||
const CREATE = [
|
||||
'label' => '创建',
|
||||
'value' => 'Create'
|
||||
];
|
||||
const DELETE = [
|
||||
'label' => '删除',
|
||||
'value' => 'Delete'
|
||||
];
|
||||
const UPDATE = [
|
||||
'label' => '更新',
|
||||
'value' => 'Update'
|
||||
];
|
||||
const UPDATE_STATE = [
|
||||
'label' => '更新状态',
|
||||
'value' => 'UpdateState'
|
||||
];
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Platform extends Enum
|
||||
{
|
||||
const PC = [
|
||||
'label' => 'PC',
|
||||
'value' => 'pc'
|
||||
];
|
||||
const H5 = [
|
||||
'label' => 'H5',
|
||||
'value' => 'h5'
|
||||
];
|
||||
const WECHAT_MINIAPP = [
|
||||
'label' => '微信小程序',
|
||||
'value' => 'wechat_miniapp'
|
||||
];
|
||||
const WECHAT_OFFICIAL_ACCOUNT = [
|
||||
'label' => '微信公众号',
|
||||
'value' => 'wechat_official_account'
|
||||
];
|
||||
const APP = [
|
||||
'label' => 'APP',
|
||||
'value' => 'app'
|
||||
];
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
/**
|
||||
* JSON响应Code静态常量
|
||||
* Class ResponseCode
|
||||
* @package app\expose\enum
|
||||
*/
|
||||
class ResponseCode extends Enum
|
||||
{
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
const SUCCESS = 200;
|
||||
/**
|
||||
* 等待
|
||||
*/
|
||||
const WAIT = 202;
|
||||
/**
|
||||
* 有事件通知
|
||||
*/
|
||||
const SUCCESS_EVENT_PUSH = 201;
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
const FAIL = 404;
|
||||
/**
|
||||
* 需要登录
|
||||
*/
|
||||
const NEED_LOGIN = 12000;
|
||||
/**
|
||||
* 需要两步验证
|
||||
*/
|
||||
const NEED_TWOFA = 12001;
|
||||
/**
|
||||
* 验证码错误
|
||||
*/
|
||||
const CAPTCHA = 300;
|
||||
/**
|
||||
* 站点信息错误
|
||||
*/
|
||||
const SITE_ERROR = 400;
|
||||
/**
|
||||
* 需要支付
|
||||
*/
|
||||
const NEED_PAY = 9100;
|
||||
/**
|
||||
* 支付成功
|
||||
*/
|
||||
const PAY_SUCCESS = 9000;
|
||||
/**
|
||||
* 支付失败
|
||||
*/
|
||||
const PAY_FAIL = 9010;
|
||||
/**
|
||||
* 重定向
|
||||
*/
|
||||
const REDIRECT = 302;
|
||||
/**
|
||||
* 确认重定向
|
||||
*/
|
||||
const REDIRECT_CONFIRM = 303;
|
||||
/**
|
||||
* 打开新窗口
|
||||
*/
|
||||
const OPEN_NEW_WINDOW = 304;
|
||||
/**
|
||||
* 确认打开新窗口
|
||||
*/
|
||||
const OPEN_NEW_WINDOW_CONFIRM = 305;
|
||||
/**
|
||||
* 无权限
|
||||
*/
|
||||
const NO_PERMISSION = 403;
|
||||
/**
|
||||
* 需要绑定电话
|
||||
*/
|
||||
const NEED_BIND_MOBILE = 12002;
|
||||
/**
|
||||
* 锁定
|
||||
*/
|
||||
const LOCK = 423;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class ResponseEvent extends Enum
|
||||
{
|
||||
/**
|
||||
* 通知客户端更新网站配置
|
||||
*/
|
||||
const UPDATE_WEBCONFIG = "UPDATE::WEBCONFIG";
|
||||
/**
|
||||
* 通知客户端更新用户信息
|
||||
*/
|
||||
const UPDATE_USERINFO = "UPDATE::USERINFO";
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class SmsChannels extends Enum
|
||||
{
|
||||
const ALIYUN = [
|
||||
'label' => '阿里云短信',
|
||||
'value' => 'aliyun'
|
||||
];
|
||||
const TENCENT = [
|
||||
'label' => '腾讯云短信',
|
||||
'value' => 'tencent'
|
||||
];
|
||||
const SMSBAO = [
|
||||
'label' => '短信宝',
|
||||
'value' => 'smsbao'
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class State extends Enum
|
||||
{
|
||||
const YES = [
|
||||
'label' => '正常',
|
||||
'value' => 1
|
||||
];
|
||||
const NO = [
|
||||
'label' => '禁用',
|
||||
'value' => 0
|
||||
];
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
/**
|
||||
* 表单提交事件
|
||||
* 默认提交表单后重置表单
|
||||
* @package app\expose\enum
|
||||
*/
|
||||
class SubmitEvent extends Enum
|
||||
{
|
||||
/**
|
||||
* 提交表单后静默处理
|
||||
*/
|
||||
const SILENT = "SILENT";
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class Week extends Enum
|
||||
{
|
||||
const SUNDAY = [
|
||||
'label' => '周日',
|
||||
'value' => 0,
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
const MONDAY = [
|
||||
'label' => '周一',
|
||||
'value' => 1,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const TUESDAY = [
|
||||
'label' => '周二',
|
||||
'value' => 2,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const WEDNESDAY = [
|
||||
'label' => '周三',
|
||||
'value' => 3,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const THURSDAY = [
|
||||
'label' => '周四',
|
||||
'value' => 4,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const FRIDAY = [
|
||||
'label' => '周五',
|
||||
'value' => 5,
|
||||
'props' => [
|
||||
'type' => 'success'
|
||||
]
|
||||
];
|
||||
const SATURDAY = [
|
||||
'label' => '周六',
|
||||
'value' => 6,
|
||||
'props' => [
|
||||
'type' => 'info'
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class WxpayMchType extends Enum
|
||||
{
|
||||
const NORMAL = [
|
||||
'label' => '普通商户',
|
||||
'value' => 'normal'
|
||||
];
|
||||
const SERVICE = [
|
||||
'label' => '子商户 (服务商模式)',
|
||||
'value' => 'service'
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum;
|
||||
|
||||
use app\expose\enum\builder\Enum;
|
||||
|
||||
class WxpayVersion extends Enum
|
||||
{
|
||||
const V3 = [
|
||||
'label' => 'V3',
|
||||
'value' => 'v3'
|
||||
];
|
||||
const V2 = [
|
||||
'label' => 'V2',
|
||||
'value' => 'v2'
|
||||
];
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\enum\builder;
|
||||
|
||||
class Enum
|
||||
{
|
||||
/**
|
||||
* 获取枚举选项
|
||||
* @param callable|null $filter 过滤函数
|
||||
* @return array 选项数组
|
||||
*/
|
||||
public static function getOptions(?callable $filter = null)
|
||||
{
|
||||
$class = new \ReflectionClass(static::class);
|
||||
$options = [];
|
||||
foreach ($class->getConstants() as $key => $value) {
|
||||
if ($filter) {
|
||||
if ($filter($value)) {
|
||||
if (is_array($value)) {
|
||||
$options[] = $value;
|
||||
} else {
|
||||
$options[] = [
|
||||
'label' => $key,
|
||||
'value' => $value
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($value)) {
|
||||
$options[] = $value;
|
||||
} else {
|
||||
$options[] = [
|
||||
'label' => $key,
|
||||
'value' => $value
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
/**
|
||||
* 获取枚举文本
|
||||
* @param mixed $value 值
|
||||
* @return string 文本
|
||||
*/
|
||||
public static function getText($value)
|
||||
{
|
||||
$options = static::getOptions();
|
||||
foreach ($options as $option) {
|
||||
if ($option['value'] == $value) {
|
||||
return $option['label'];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* 获取枚举选项
|
||||
* @param mixed $value 值
|
||||
* @return array|null 选项
|
||||
*/
|
||||
public static function get($value, $key = null)
|
||||
{
|
||||
$options = static::getOptions();
|
||||
foreach ($options as $option) {
|
||||
if ($option['value'] == $value) {
|
||||
return array_key_exists($key, $option) ? $option[$key] : $option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 获取枚举值
|
||||
* @param callable|null $filter 过滤函数
|
||||
* @return array 值数组
|
||||
*/
|
||||
public static function getValues(?callable $filter = null)
|
||||
{
|
||||
$options = static::getOptions($filter);
|
||||
$values = [];
|
||||
foreach ($options as $option) {
|
||||
$values[] = $option['value'];
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
/**
|
||||
* 判断是否存在
|
||||
* @param mixed $value 值
|
||||
* @return bool 是否存在
|
||||
*/
|
||||
public static function has($value)
|
||||
{
|
||||
$options = static::getOptions();
|
||||
foreach ($options as $option) {
|
||||
if ($option['value'] == $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\exception;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
protected $data = [];
|
||||
|
||||
public function __construct($message = '', $code = 0, $data = [])
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use Exception;
|
||||
use support\Response;
|
||||
use Webman\Captcha\CaptchaBuilder;
|
||||
|
||||
/**
|
||||
* 图像验证码助手类
|
||||
* Class Captcha
|
||||
* @package app\helper
|
||||
*
|
||||
* @method static Response captcha()
|
||||
* @method static array captchaCode()
|
||||
* @method static bool check(string $captcha)
|
||||
*
|
||||
*/
|
||||
class Captcha
|
||||
{
|
||||
/**
|
||||
* 检验图像验证码
|
||||
* @param string $captcha
|
||||
* @return boolean
|
||||
*/
|
||||
public static function check(string $captcha, ?string $token = null): bool
|
||||
{
|
||||
$request = request();
|
||||
if ($token) {
|
||||
$request->sessionId($token);
|
||||
}
|
||||
$captchaData = $request->session()->get('captcha');
|
||||
if (!$captchaData) {
|
||||
throw new Exception('图像验证码不存在');
|
||||
}
|
||||
if ($captchaData['expire'] < time()) {
|
||||
throw new Exception('图像验证码已过期');
|
||||
}
|
||||
// 对比session中的captcha值
|
||||
if (strtolower($captcha) !== $captchaData['captcha']) {
|
||||
throw new Exception('图像验证码不正确');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static function create()
|
||||
{
|
||||
$request = request();
|
||||
$bg = $request->get('bg');
|
||||
$length = $request->get('length', 4);
|
||||
if ($length < 4) {
|
||||
$length = 4;
|
||||
}
|
||||
if ($length > 6) {
|
||||
$length = 6;
|
||||
}
|
||||
$defaultBg = '255,255,255';
|
||||
if ($bg) {
|
||||
$defaultBg = $bg;
|
||||
}
|
||||
$width = (int)$request->get('w', 150);
|
||||
$height = (int)$request->get('h', 40);
|
||||
// 初始化验证码类
|
||||
$builder = new CaptchaBuilder((int)$length);
|
||||
$bgArr = explode(',', $defaultBg);
|
||||
$builder->setBackgroundColor($bgArr[0], $bgArr[1], $bgArr[2]);
|
||||
$builder->setDistortion(false);
|
||||
$builder->setInterpolation(false);
|
||||
$builder->setTextColor(255 - $bgArr[0], 255 - $bgArr[1], 255 - $bgArr[2]);
|
||||
// 生成验证码
|
||||
$builder->build($width, $height);
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图像验证码
|
||||
* @return Response
|
||||
*/
|
||||
public static function captcha(): Response
|
||||
{
|
||||
$request = request();
|
||||
$builder = self::create();
|
||||
$request->session()->set('captcha', [
|
||||
'captcha' => strtolower($builder->getPhrase()),
|
||||
'expire' => time() + 60 * 5
|
||||
]);
|
||||
$img_content = $builder->inline();
|
||||
return response($img_content, 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 图像验证码
|
||||
*/
|
||||
public static function captchaCode(): array
|
||||
{
|
||||
$request = request();
|
||||
$builder = self::create();
|
||||
$request->session()->set('captcha', [
|
||||
'captcha' => strtolower($builder->getPhrase()),
|
||||
'expire' => time() + 60 * 5
|
||||
]);
|
||||
$img_content = $builder->inline();
|
||||
return ['base64' => $img_content, 'token' => $request->sessionId()];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\enum\SubmitEvent;
|
||||
use app\model\Config as ModelConfig;
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Config extends DataModel
|
||||
{
|
||||
protected $configData = [];
|
||||
protected $groupData = [];
|
||||
protected $data = [];
|
||||
protected $group = '';
|
||||
protected $channels_uid = null;
|
||||
/**
|
||||
* 获取配置
|
||||
*
|
||||
* @param string $group 配置分组,settings目录下的文件名
|
||||
* @param string|null $plugin 如若要获取全局配置,请传入''
|
||||
*/
|
||||
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
|
||||
{
|
||||
$request = request();
|
||||
if ($plugin === null) {
|
||||
$plugin = $request->plugin;
|
||||
}
|
||||
if ($request && $request->channels_uid && $channels_uid === null) {
|
||||
$this->channels_uid = $request->channels_uid;
|
||||
} elseif ($channels_uid) {
|
||||
$this->channels_uid = $channels_uid;
|
||||
}
|
||||
$this->group = $plugin ? $plugin . '.' . $group : $group;
|
||||
$this->configData = config('settings');
|
||||
$this->groupData = $this->configData[$this->group] ?? [];
|
||||
$this->builder();
|
||||
}
|
||||
/**
|
||||
* 获取当前应用指定配置,如需获取全局配置请new Config('group','');
|
||||
*
|
||||
* @param string $group 配置分组,settings目录下的文件名
|
||||
* @param string|null $field 获取自定字段配置
|
||||
* @param mixed $default 默认数据
|
||||
* @return array|mixed
|
||||
*/
|
||||
public static function get(string $group, string|null $field = null, mixed $default = null)
|
||||
{
|
||||
$self = new self($group);
|
||||
if ($field) {
|
||||
return isset($self->{$field}) ? $self->{$field} : $default;
|
||||
}
|
||||
return $self->toArray();
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
$d = [];
|
||||
$request = request();
|
||||
$domain = '';
|
||||
$host = '';
|
||||
if ($request) {
|
||||
$domain = $request->header('x-forwarded-proto') . '://' . $request->host();
|
||||
$host = $request->host();
|
||||
}
|
||||
$replaceData = [
|
||||
'{DOMAIN}' => $domain,
|
||||
'{HOST}' => $host,
|
||||
'{CHANNELS_UID}' => $this->channels_uid
|
||||
];
|
||||
if (!empty($this->groupData)) {
|
||||
foreach ($this->groupData as $key => $item) {
|
||||
$has = false;
|
||||
if (is_string($item['value']) && strpos($item['value'], '{DOMAIN}') !== false && $domain) {
|
||||
$has = true;
|
||||
} elseif (is_string($item['value']) && strpos($item['value'], '{HOST}') !== false && $host) {
|
||||
$has = true;
|
||||
} elseif (is_string($item['value']) && strpos($item['value'], '{CHANNELS_UID}') !== false && $this->channels_uid) {
|
||||
$has = true;
|
||||
}
|
||||
if ($has) {
|
||||
$d[$item['field']] = str_replace(array_keys($replaceData), array_values($replaceData), $item['value']);
|
||||
} else {
|
||||
$d[$item['field']] = $item['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
|
||||
if ($ConfigModel) {
|
||||
foreach ($ConfigModel->value as $field => $value) {
|
||||
$has = false;
|
||||
if (is_string($value) && strpos($value, '{DOMAIN}') !== false && $domain) {
|
||||
$has = true;
|
||||
} elseif (is_string($value) && strpos($value, '{HOST}') !== false && $host) {
|
||||
$has = true;
|
||||
} elseif (is_string($value) && strpos($value, '{CHANNELS_UID}') !== false && $this->channels_uid) {
|
||||
$has = true;
|
||||
}
|
||||
if ($has) {
|
||||
$d[$field] = str_replace(array_keys($replaceData), array_values($replaceData), $value);
|
||||
} else {
|
||||
$d[$field] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->data = $d;
|
||||
}
|
||||
public function getGroupData()
|
||||
{
|
||||
return $this->groupData;
|
||||
}
|
||||
public function getGrop()
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
public static function set($group, $field, $value)
|
||||
{
|
||||
$self = new self($group);
|
||||
$self->{$field} = $value;
|
||||
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
|
||||
if (!$ConfigModel) {
|
||||
$ConfigModel = new ModelConfig;
|
||||
$ConfigModel->group = $group;
|
||||
$ConfigModel->channels_uid = $self->channels_uid;
|
||||
}
|
||||
$ConfigModel->value = $self->toArray();
|
||||
$ConfigModel->save();
|
||||
}
|
||||
public static function formBuilder($group, $plugin = null, $channels_uid = null)
|
||||
{
|
||||
$self = new self($group, $plugin, $channels_uid);
|
||||
$builder = new FormBuilder(null, null, [
|
||||
'submitEvent' => SubmitEvent::SILENT
|
||||
]);
|
||||
$builder->setTranslations();
|
||||
$groupData = $self->getGroupData();
|
||||
foreach ($groupData as $key => $item) {
|
||||
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
|
||||
}
|
||||
$builder->setData($self->toArray());
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\build\builder\FormBuilder;
|
||||
use app\expose\enum\SubmitEvent;
|
||||
use app\model\Config as ModelConfig;
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class ConfigGroup extends DataModel
|
||||
{
|
||||
protected $configData = [];
|
||||
protected $groupData = [];
|
||||
protected $data = [];
|
||||
protected $group = '';
|
||||
protected $channels_uid = null;
|
||||
public function __construct(string $group, string|null $plugin = null, int|null $channels_uid = null)
|
||||
{
|
||||
$request = request();
|
||||
if ($plugin === null) {
|
||||
$plugin = $request->plugin;
|
||||
}
|
||||
if ($request && $request->channels_uid && $channels_uid === null) {
|
||||
$this->channels_uid = $request->channels_uid;
|
||||
} elseif ($channels_uid) {
|
||||
$this->channels_uid = $channels_uid;
|
||||
}
|
||||
$this->group = $plugin ? $plugin . '.' . $group : $group;
|
||||
$this->configData = config('settings-tabs');
|
||||
$this->groupData = $this->configData[$this->group] ?? [];
|
||||
$this->builder();
|
||||
}
|
||||
public function builder()
|
||||
{
|
||||
$d = [];
|
||||
if (!empty($this->groupData)) {
|
||||
foreach ($this->groupData as $key => $item) {
|
||||
if ($key != 'group') {
|
||||
$d[$item['field']] = $item['value'];
|
||||
} else {
|
||||
foreach ($item as $group) {
|
||||
$d[$group['name']] = [];
|
||||
foreach ($group['children'] as $child) {
|
||||
$d[$group['name']][$child['field']] = $child['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find();
|
||||
if ($ConfigModel) {
|
||||
foreach ($ConfigModel->value as $field => $value) {
|
||||
$d[$field] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->data = $d;
|
||||
}
|
||||
public function getGroupData()
|
||||
{
|
||||
return $this->groupData;
|
||||
}
|
||||
public function getGrop()
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
public static function get($group, $field = null, $default = null)
|
||||
{
|
||||
$self = new self($group);
|
||||
if ($field) {
|
||||
return isset($self->{$field}) ? $self->{$field} : $default;
|
||||
}
|
||||
return $self->toArray();
|
||||
}
|
||||
public static function set($group, $field, $value)
|
||||
{
|
||||
$self = new self($group);
|
||||
$self->{$field} = $value;
|
||||
$ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find();
|
||||
if (!$ConfigModel) {
|
||||
$ConfigModel = new ModelConfig;
|
||||
$ConfigModel->group = $group;
|
||||
$ConfigModel->channels_uid = $self->channels_uid;
|
||||
}
|
||||
$ConfigModel->value = $self->toArray();
|
||||
$ConfigModel->save();
|
||||
}
|
||||
public static function formBuilder($group, $plugin = null, $channels_uid = null)
|
||||
{
|
||||
$self = new self($group, $plugin, $channels_uid);
|
||||
$builder = new FormBuilder(null, null, [
|
||||
'translations' => true,
|
||||
'submitEvent' => SubmitEvent::SILENT
|
||||
]);
|
||||
$groupData = $self->getGroupData();
|
||||
foreach ($groupData as $key => $item) {
|
||||
if ($key != 'group') {
|
||||
$builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']);
|
||||
}
|
||||
}
|
||||
foreach ($groupData['group'] as $key => $item) {
|
||||
$subBuilder = new FormBuilder($item['name'], $item['title']);
|
||||
foreach ($item['children'] as $child) {
|
||||
$subBuilder->add($child['field'], $child['title'], $child['component'], $child['value'], $child['extra']);
|
||||
}
|
||||
$builder->addGroupForm($subBuilder);
|
||||
}
|
||||
$builder->setData($self->toArray());
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Menus extends DataModel
|
||||
{
|
||||
public $data = [];
|
||||
public $itemData = [
|
||||
# 菜单标题
|
||||
"title" => "Title",
|
||||
# 菜单图标,element-plus中的Icon名称,如:ElementPlus
|
||||
"icon" => "",
|
||||
# 路由路径
|
||||
"path" => "",
|
||||
# 使用组件,table:表格列表,form:表单,images:图片列表
|
||||
"component" => "default",
|
||||
# 请求方式,GET,POST,PUT,DELETE
|
||||
"methods" => ["GET"],
|
||||
# 是否显示,0:隐藏,1:显示
|
||||
"show" => 1,
|
||||
# 排序,正序
|
||||
"sort" => 9999,
|
||||
# 携带地址栏参数
|
||||
"query" => [],
|
||||
# 携带post参数
|
||||
"params" => [],
|
||||
# 路由meta
|
||||
"meta" => [
|
||||
"login" => true,
|
||||
],
|
||||
# 子级和父级参数一致
|
||||
"children" => []
|
||||
];
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param object $Install 需实现 getMenus 方法的类返回菜单数据
|
||||
*/
|
||||
public function __construct($Install)
|
||||
{
|
||||
$request = request();
|
||||
$lang = null;
|
||||
if ($request && $request->lang) {
|
||||
$lang = $request->lang;
|
||||
}
|
||||
$data = $Install->getMenus();
|
||||
if ($lang) {
|
||||
$this->translateChildren($data, $request->app,$lang);
|
||||
}
|
||||
foreach (glob(base_path('plugin/*')) as $path) {
|
||||
$plugin_name = basename($path);
|
||||
$class = 'plugin\\' . $plugin_name . '\\api\\Install';
|
||||
$plugin = new $class;
|
||||
$menus = $plugin->getMenus();
|
||||
if ($menus) {
|
||||
if ($lang&&$plugin->getPlugin()) {
|
||||
$request->plugin = $plugin_name;
|
||||
$this->translateChildren($menus, $request->app,$lang);
|
||||
}
|
||||
$data[] = $menus;
|
||||
}
|
||||
}
|
||||
$this->builder($data);
|
||||
}
|
||||
public function translateChildren(&$data, $domain,$lang)
|
||||
{
|
||||
if (is_array($data) && isset($data[0])) {
|
||||
foreach ($data as $key => &$item) {
|
||||
if (isset($item['title'])) {
|
||||
$data[$key]['title'] = trans($item['title'], [], $domain, $lang);
|
||||
}
|
||||
if (!empty($item['children'])) {
|
||||
$this->translateChildren($item['children'],$domain, $lang);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isset($data['title'])) {
|
||||
$data['title'] = trans($data['title'], [], $domain, $lang);
|
||||
}
|
||||
if (!empty($data['children'])) {
|
||||
$this->translateChildren($data['children'],$domain, $lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 递归合并
|
||||
*
|
||||
* @param array $data
|
||||
* @return $data
|
||||
*/
|
||||
public function mergeMenus(array $data): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($data as $key => $item) {
|
||||
$temp = array_merge($this->itemData, $item);
|
||||
if (!empty($temp['children'])) {
|
||||
$temp['children'] = $this->mergeMenus($temp['children']);
|
||||
}
|
||||
$result[] = $temp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* 构建菜单
|
||||
*
|
||||
* @param array $data
|
||||
* @return Menus
|
||||
*/
|
||||
public function builder(array $data)
|
||||
{
|
||||
$this->data = $this->mergeMenus($data);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\enum\PaymentChannels;
|
||||
use app\expose\enum\Platform;
|
||||
use app\expose\enum\State;
|
||||
use app\expose\helper\Uploads as HelperUploads;
|
||||
use app\model\PaymentConfig;
|
||||
use app\model\PaymentTemplate;
|
||||
use Exception;
|
||||
use plugin\control\expose\helper\Uploads;
|
||||
use Yansongda\Pay\Pay;
|
||||
use support\Log;
|
||||
|
||||
class Payment
|
||||
{
|
||||
public static function get($platform, $channels, int|null $channels_uid = null)
|
||||
{
|
||||
$where = [];
|
||||
$where[] = ['platform', '=', $platform];
|
||||
$where[] = ['channels', '=', $channels];
|
||||
if ($channels_uid) {
|
||||
$where[] = ['channels_uid', '=', $channels_uid];
|
||||
}
|
||||
$PaymentConfig = PaymentConfig::where($where)->find();
|
||||
if (!$PaymentConfig) {
|
||||
throw new Exception('支付配置不存在');
|
||||
}
|
||||
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id, 'channels_uid' => $channels_uid])->find();
|
||||
if (!$PaymentTemplate) {
|
||||
throw new Exception('支付模板不存在');
|
||||
}
|
||||
return $PaymentTemplate->value;
|
||||
}
|
||||
public static function platform(int|null $channels_uid = null)
|
||||
{
|
||||
$platform = request()->platform;
|
||||
$where = [];
|
||||
$where[] = ['platform', '=', $platform];
|
||||
$where[] = ['state', '=', State::YES['value']];
|
||||
if ($channels_uid) {
|
||||
$where[] = ['channels_uid', '=', $channels_uid];
|
||||
}
|
||||
$PaymentConfig = PaymentConfig::where($where)->select();
|
||||
$data = [];
|
||||
foreach ($PaymentConfig as $item) {
|
||||
$enum = PaymentChannels::get($item->channels);
|
||||
$temp = [
|
||||
'id' => $item->id,
|
||||
'label' => $enum['label'],
|
||||
'enum' => $enum,
|
||||
];
|
||||
if ($item->is_default == State::YES['value']) {
|
||||
$temp['default'] = 1;
|
||||
}
|
||||
$data[] = $temp;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
public static function getIntegralName()
|
||||
{
|
||||
$integral = '积分';
|
||||
try {
|
||||
$platform = request()->platform;
|
||||
if (!$platform) {
|
||||
$platform = Platform::PC['value'];
|
||||
}
|
||||
$Payment = self::get($platform, PaymentChannels::INTEGRAL['value']);
|
||||
if (isset($Payment['display_name'])) {
|
||||
$integral = $Payment['display_name'];
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
return $integral;
|
||||
}
|
||||
public static function createPayment($model, $id)
|
||||
{
|
||||
$PaymentConfig = PaymentConfig::where(['id' => $id])->find();
|
||||
if (!$PaymentConfig) {
|
||||
throw new Exception('支付配置不存在');
|
||||
}
|
||||
switch ($PaymentConfig->channels) {
|
||||
case PaymentChannels::WXPAY['value']:
|
||||
return self::wxPay($model, $PaymentConfig);
|
||||
case PaymentChannels::ALIPAY['value']:
|
||||
break;
|
||||
case PaymentChannels::INTEGRAL['value']:
|
||||
break;
|
||||
case PaymentChannels::BALANCE['value']:
|
||||
break;
|
||||
}
|
||||
throw new \Exception('未知支付方式');
|
||||
}
|
||||
public static function wxPay($model, $PaymentConfig)
|
||||
{
|
||||
switch ($PaymentConfig->platform) {
|
||||
case Platform::PC['value']:
|
||||
return self::wxPayPc($model, $PaymentConfig);
|
||||
case Platform::H5['value']:
|
||||
return self::wxPayH5($model, $PaymentConfig);
|
||||
case Platform::WECHAT_OFFICIAL_ACCOUNT['value']:
|
||||
return self::wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig);
|
||||
case Platform::APP['value']:
|
||||
return self::wxPayApp($model, $PaymentConfig);
|
||||
case Platform::WECHAT_MINIAPP['value']:
|
||||
return self::wxPayMiniapp($model, $PaymentConfig);
|
||||
}
|
||||
}
|
||||
public static function wxPayPc($model, $PaymentConfig)
|
||||
{
|
||||
$PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id])->find();
|
||||
if (!$PaymentTemplate) {
|
||||
throw new Exception('支付模板不存在');
|
||||
}
|
||||
$Payment = $PaymentTemplate->value;
|
||||
$notify_url = $Payment['notify_url'];
|
||||
# 判断是否为“/”结尾
|
||||
if (substr($notify_url, -1) == '/') {
|
||||
$notify_url = substr($notify_url, 0, -1);
|
||||
}
|
||||
$notify_url .= '/notify/wechat/' . $model->plugin . '/' . $PaymentTemplate->id;
|
||||
$mch_secret_cert = null;
|
||||
if ($PaymentTemplate->channels_uid) {
|
||||
$mch_secret_cert = Uploads::downloadTemp($Payment['ssl_key'], 'mch_secret_cert_' . $PaymentTemplate->id);
|
||||
} else {
|
||||
$mch_secret_cert = base_path(HelperUploads::path($Payment['ssl_key']));
|
||||
}
|
||||
$mch_public_cert_path = null;
|
||||
if ($PaymentTemplate->channels_uid) {
|
||||
$mch_public_cert_path = Uploads::downloadTemp($Payment['ssl_cert'], 'mch_public_cert_path_' . $PaymentTemplate->id);
|
||||
} else {
|
||||
$mch_public_cert_path = base_path(HelperUploads::path($Payment['ssl_cert']));
|
||||
}
|
||||
$config = [
|
||||
'wechat' => [
|
||||
'default' => [
|
||||
// 必填-商户号
|
||||
'mch_id' => $Payment['mch_id'],
|
||||
// 选填-v2商户私钥
|
||||
'mch_secret_key_v2' => '',
|
||||
// 必填-v3商户秘钥
|
||||
'mch_secret_key' => $Payment['mch_key'],
|
||||
// 必填-商户私钥 字符串或路径
|
||||
'mch_secret_cert' => $mch_secret_cert,
|
||||
// 必填-商户公钥证书路径
|
||||
'mch_public_cert_path' => $mch_public_cert_path,
|
||||
// 必填
|
||||
'notify_url' => $notify_url,
|
||||
// 选填-公众号 的 app_id
|
||||
'mp_app_id' => $Payment['appid'],
|
||||
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
|
||||
'mode' => Pay::MODE_NORMAL,
|
||||
]
|
||||
],
|
||||
'logger' => [
|
||||
'enable' => true,
|
||||
'file' => runtime_path('logs/wechat-pay-' . date('Y-m-d') . '.log'),
|
||||
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
|
||||
'type' => 'single', // optional, 可选 daily.
|
||||
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
|
||||
],
|
||||
'http' => [
|
||||
'timeout' => 5.0,
|
||||
'connect_timeout' => 5.0,
|
||||
],
|
||||
];
|
||||
$order = [
|
||||
'out_trade_no' => $model->trade,
|
||||
'amount' => [
|
||||
'total' => getenv('DEV') === 'true' ? 1 : $model->price * 100,
|
||||
],
|
||||
'description' => $model->title,
|
||||
'time_expire' => date('c', strtotime($model->expire_time)),
|
||||
];
|
||||
Log::info('wxPayPc', ['order' => $order, 'config' => $config]);
|
||||
$Pay = Pay::wechat($config)->scan($order);
|
||||
return [
|
||||
'plugin' => $model->plugin,
|
||||
'trade' => $model->trade,
|
||||
'qrcode' => $Pay->code_url,
|
||||
];
|
||||
}
|
||||
public static function wxPayH5($model, $PaymentConfig) {}
|
||||
public static function wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig) {}
|
||||
public static function wxPayApp($model, $PaymentConfig) {}
|
||||
public static function wxPayMiniapp($model, $PaymentConfig) {}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\enum\Filesystem;
|
||||
use app\model\Uploads as ModelUploads;
|
||||
use app\model\UploadsClassify;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||
use Shopwwi\WebmanFilesystem\FilesystemFactory;
|
||||
use Webman\Http\UploadFile;
|
||||
|
||||
class Uploads
|
||||
{
|
||||
/**
|
||||
* 获取文件URL
|
||||
* @param string|array|null $path 文件路径
|
||||
* @return string|array
|
||||
*/
|
||||
public static function url(string|array|null $path)
|
||||
{
|
||||
if (empty($path)) {
|
||||
return $path;
|
||||
}
|
||||
if (is_array($path)) {
|
||||
$data = [];
|
||||
foreach ($path as $value) {
|
||||
$data[] = self::url($value);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
# 判断path是否以[?]开头,如果是则获取对应的key
|
||||
if (strpos($path, '[') === 0) {
|
||||
$key = substr($path, 1, strpos($path, ']') - 1);
|
||||
$model = new \stdClass;
|
||||
$model->channels = $key;
|
||||
$model->path = substr($path, strpos($path, ']') + 1);
|
||||
}else{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
return Storage::adapter($model->channels)->url($model->path);
|
||||
}
|
||||
/**
|
||||
* 获取文件在本地存储的完整路径
|
||||
* @param string|array|null $path 文件路径
|
||||
* @return string|array
|
||||
*/
|
||||
public static function local(string|array|null $path)
|
||||
{
|
||||
if (empty($path)) {
|
||||
return $path;
|
||||
}
|
||||
if (is_array($path)) {
|
||||
$data = [];
|
||||
foreach ($path as $value) {
|
||||
$data[] = self::local($value);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
if (strpos($path, '[') === 0) {
|
||||
$key = substr($path, 1, strpos($path, ']') - 1);
|
||||
$model = new \stdClass;
|
||||
$model->channels = $key;
|
||||
$model->path = substr($path, strpos($path, ']') + 1);
|
||||
}else{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
$has = $filesystem->has($model->path);
|
||||
if ($has) {
|
||||
if (in_array($model->channels, [Filesystem::LOCAL['value'], Filesystem::PUBLIC['value']])) {
|
||||
$config = config('plugin.shopwwi.filesystem.app.storage.' . $model->channels);
|
||||
return $config['root'] . $model->path;
|
||||
} else {
|
||||
return self::downloadTemp(Storage::adapter($model->channels)->url($model->path));
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
/**
|
||||
* 获取文件路径
|
||||
* @param string|array|null $url URL地址
|
||||
* @return string|array
|
||||
*/
|
||||
public static function path(string|array|null $url)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return '';
|
||||
}
|
||||
if (is_array($url)) {
|
||||
$data = [];
|
||||
if (count($url) === 1) {
|
||||
return self::path(current($url));
|
||||
}
|
||||
$config = config('plugin.shopwwi.filesystem.app.storage');
|
||||
$urls = [];
|
||||
foreach ($config as $key => $value) {
|
||||
if (empty($value['url'])) {
|
||||
continue;
|
||||
}
|
||||
$urls[$key] = $value['url'];
|
||||
}
|
||||
foreach ($url as $value) {
|
||||
if (filter_var($value, FILTER_SANITIZE_URL) === false) {
|
||||
throw new Exception('URL地址不合法');
|
||||
}
|
||||
$parseUrl = parse_url($value);
|
||||
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
|
||||
$key = array_search($domain, $urls);
|
||||
if ($key) {
|
||||
$data[] = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
|
||||
} else {
|
||||
$data[] = ltrim($parseUrl['path'], '/');
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
} else {
|
||||
if (filter_var($url, FILTER_SANITIZE_URL) === false) {
|
||||
throw new Exception('URL地址不合法');
|
||||
}
|
||||
$parseUrl = parse_url($url);
|
||||
$config = config('plugin.shopwwi.filesystem.app.storage');
|
||||
$urls = [];
|
||||
foreach ($config as $key => $value) {
|
||||
if (empty($value['url'])) {
|
||||
continue;
|
||||
}
|
||||
$urls[$key] = $value['url'];
|
||||
}
|
||||
$domain = $parseUrl['scheme'] . '://' . $parseUrl['host'];
|
||||
$key = array_search($domain, $urls);
|
||||
if ($key) {
|
||||
$data = '[' . $key . ']' . ltrim($parseUrl['path'], '/');
|
||||
} else {
|
||||
$data = ltrim($parseUrl['path'], '/');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存文件
|
||||
* @param string $path 文件路径
|
||||
* @param string $channels 文件存储通道
|
||||
* @param string $dir_name 文件存储目录
|
||||
* @param string $title 文件分类标题
|
||||
* @return array
|
||||
*/
|
||||
public static function save(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save', $title = '本地保存')
|
||||
{
|
||||
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels])->find();
|
||||
if (!$UploadsClassify) {
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->title = $title;
|
||||
$UploadsClassify->dir_name = $dir_name;
|
||||
$UploadsClassify->channels = $channels;
|
||||
$UploadsClassify->sort = 0;
|
||||
$UploadsClassify->is_system = 1;
|
||||
$UploadsClassify->save();
|
||||
}
|
||||
$date_path = date('Ymd');
|
||||
$originName = basename($path);
|
||||
//单文件上传
|
||||
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
|
||||
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
|
||||
$Uploads = new ModelUploads;
|
||||
$Uploads->classify_id = $UploadsClassify->id;
|
||||
$Uploads->filename = $result->origin_name;
|
||||
$Uploads->path = $result->file_name;
|
||||
$Uploads->ext = $result->extension;
|
||||
$Uploads->mime = $result->mime_type;
|
||||
$Uploads->size = $result->size;
|
||||
$Uploads->channels = $channels;
|
||||
$Uploads->save();
|
||||
return [
|
||||
'id' => $Uploads->id,
|
||||
'url' => $result->file_url,
|
||||
'path' => $result->file_name,
|
||||
'mime' => $result->mime_type,
|
||||
'dir_name' => $dir_name
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 远程下载文件
|
||||
* @param string $url 文件URL
|
||||
* @param string $channels 文件存储通道
|
||||
* @return string
|
||||
*/
|
||||
public static function download(string $url, $channels = Filesystem::PUBLIC['value'])
|
||||
{
|
||||
$dir_name = 'uploads/remote';
|
||||
$UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'is_system' => 1])->find();
|
||||
if (!$UploadsClassify) {
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->title = '远程下载';
|
||||
$UploadsClassify->dir_name = $dir_name;
|
||||
$UploadsClassify->channels = $channels;
|
||||
$UploadsClassify->sort = 0;
|
||||
$UploadsClassify->is_system = 1;
|
||||
$UploadsClassify->save();
|
||||
}
|
||||
$date_path = date('Ymd');
|
||||
$client = new Client();
|
||||
$response = $client->get($url);
|
||||
$body = $response->getBody();
|
||||
$file = $body->getContents();
|
||||
$urlPath = parse_url($url, PHP_URL_PATH);
|
||||
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
|
||||
$fileName = uniqid() . '.' . $ext;
|
||||
$temp = tempnam(sys_get_temp_dir(), '') . $fileName;
|
||||
file_put_contents($temp, $file);
|
||||
$file = new UploadFile($temp, $temp, $response->getHeaderLine('Content-Type'), 0);
|
||||
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
|
||||
\unlink($temp);
|
||||
$Uploads = new ModelUploads;
|
||||
$Uploads->classify_id = $UploadsClassify->id;
|
||||
$Uploads->filename = $result->origin_name;
|
||||
$Uploads->path = $result->file_name;
|
||||
$Uploads->ext = $result->extension;
|
||||
$Uploads->mime = $result->mime_type;
|
||||
$Uploads->size = $result->size;
|
||||
$Uploads->channels = $channels;
|
||||
$Uploads->save();
|
||||
return $result->file_name;
|
||||
}
|
||||
/**
|
||||
* 下载文件到临时文件
|
||||
* @param string $url 文件URL
|
||||
* @return string 临时文件路径
|
||||
*/
|
||||
public static function downloadTemp(string $url)
|
||||
{
|
||||
$client = new Client();
|
||||
$response = $client->get($url);
|
||||
$body = $response->getBody();
|
||||
$file = $body->getContents();
|
||||
$urlPath = parse_url($url, PHP_URL_PATH);
|
||||
$ext = pathinfo($urlPath, PATHINFO_EXTENSION);
|
||||
$fileName = uniqid() . '.' . $ext;
|
||||
$temp = runtime_path('temp/' . $fileName);
|
||||
file_put_contents($temp, $file);
|
||||
return $temp;
|
||||
}
|
||||
/**
|
||||
* 上传文件
|
||||
* @param string $path 文件路径
|
||||
* @param string $channels 文件存储通道
|
||||
* @param string $dir_name 文件存储目录
|
||||
* @return array
|
||||
*/
|
||||
public static function upload(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save')
|
||||
{
|
||||
$date_path = date('Ymd');
|
||||
$originName = basename($path);
|
||||
//单文件上传
|
||||
$file = new UploadFile($path, $originName, mime_content_type($path), filesize($path));
|
||||
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
|
||||
$Uploads = new ModelUploads;
|
||||
$Uploads->filename = $result->origin_name;
|
||||
$Uploads->path = $result->file_name;
|
||||
$Uploads->ext = $result->extension;
|
||||
$Uploads->mime = $result->mime_type;
|
||||
$Uploads->size = $result->size;
|
||||
$Uploads->channels = $channels;
|
||||
$Uploads->save();
|
||||
return [
|
||||
'id' => $Uploads->id,
|
||||
'url' => $result->file_url,
|
||||
'path' => $result->file_name,
|
||||
'mime' => $result->mime_type,
|
||||
'dir_name' => $dir_name
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 删除文件
|
||||
* @param string $path 文件路径
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete(string $path)
|
||||
{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return true;
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
$filesystem->delete($model->path);
|
||||
$model->delete();
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 重命名文件
|
||||
* @param string $path 文件路径
|
||||
* @param string $newName 新文件名
|
||||
* @return bool
|
||||
*/
|
||||
public static function rename(string $path, string $newName)
|
||||
{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return false;
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
$filesystem->move($model->path, $newName);
|
||||
$model->path = $newName;
|
||||
$model->save();
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 判断文件是否存在
|
||||
* @param string $path 文件路径
|
||||
* @return bool
|
||||
*/
|
||||
public static function has(string $path)
|
||||
{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return false;
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
return $filesystem->has($model->path);
|
||||
}
|
||||
/**
|
||||
* 复制文件
|
||||
* @param string $path 文件路径
|
||||
* @param string $newName 新文件名
|
||||
* @return bool
|
||||
*/
|
||||
public static function copy(string $path, string $newName)
|
||||
{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return false;
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
$filesystem->copy($model->path, $newName);
|
||||
$model = new ModelUploads;
|
||||
$model->classify_id = $model->classify_id;
|
||||
$model->filename = $model->filename;
|
||||
$model->path = $newName;
|
||||
$model->ext = $model->ext;
|
||||
$model->mime = $model->mime;
|
||||
$model->size = $model->size;
|
||||
$model->path = $newName;
|
||||
$model->save();
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 获取文件列表
|
||||
* @param string $path 文件路径
|
||||
* @param bool $recursive 是否递归
|
||||
* @return array
|
||||
*/
|
||||
public static function listContents(string $path, $recursive = false)
|
||||
{
|
||||
$model = ModelUploads::where(['path' => $path])->find();
|
||||
if (!$model) {
|
||||
return false;
|
||||
}
|
||||
$filesystem = FilesystemFactory::get($model->channels);
|
||||
return $filesystem->listContents($path, $recursive);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use app\expose\enum\State;
|
||||
use app\expose\template\email\Vcode as EmailVcode;
|
||||
use app\expose\template\sms\Vcode as SmsVcode;
|
||||
use app\expose\utils\Email;
|
||||
use app\expose\utils\Sms;
|
||||
use app\expose\utils\Str;
|
||||
use support\Log;
|
||||
|
||||
class Vcode
|
||||
{
|
||||
public static function check($username, $vcode, $scene, $token = null)
|
||||
{
|
||||
$request = request();
|
||||
if (!empty($token)) {
|
||||
$request->sessionId($token);
|
||||
}
|
||||
$vcodeData = $request->session()->get('vcode:' . $scene . ':' . $username);
|
||||
if (empty($vcodeData)) {
|
||||
throw new \Exception('验证码不存在');
|
||||
}
|
||||
if ($vcodeData['vcode'] != $vcode) {
|
||||
throw new \Exception('验证码错误');
|
||||
}
|
||||
if ($vcodeData['expire'] < time()) {
|
||||
throw new \Exception('验证码已过期');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static function send($username, $scene, $token = null)
|
||||
{
|
||||
$request = request();
|
||||
if (!empty($token)) {
|
||||
$request->sessionId($token);
|
||||
}
|
||||
$vcode = Str::random(6, 1);
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
||||
$Email = new Email;
|
||||
$Email->toemail = $username;
|
||||
$Email->setTemplate(EmailVcode::class);
|
||||
$Email->setData(['vcode' => $vcode]);
|
||||
$Email->send();
|
||||
} else {
|
||||
$request->session()->set('vcode:' . $scene . ':' . $username, [
|
||||
'vcode' => $vcode,
|
||||
'expire' => time() + 60 * 5
|
||||
]);
|
||||
$config = new ConfigGroup('sms', '');
|
||||
foreach ($config['channels'] as $channel) {
|
||||
$item = $config[$channel];
|
||||
if ($item['enable'] == State::NO['value']) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (empty($item['vcode_template_' . $scene])) {
|
||||
continue;
|
||||
}
|
||||
$SmsVcode = new SmsVcode();
|
||||
$SmsVcode->channel = $channel;
|
||||
$SmsVcode->config = $item;
|
||||
$SmsVcode->template_code = $item['vcode_template_' . $scene];
|
||||
$Sms = new Sms;
|
||||
$Sms->mobile = $username;
|
||||
$Sms->setTemplate($SmsVcode);
|
||||
$Sms->setData(['code' => $vcode]);
|
||||
$Sms->send();
|
||||
return true;
|
||||
} catch (\Throwable $th) {
|
||||
Log::error("发送短信失败:" . $th->getMessage(), $th->getTrace());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new \Exception('发送失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\helper;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use support\Redis;
|
||||
|
||||
class Wechat
|
||||
{
|
||||
/**
|
||||
* QR_SCENE
|
||||
* 临时的整型参数值
|
||||
* @var string
|
||||
*/
|
||||
const QR_SCENE = 'QR_SCENE';
|
||||
/**
|
||||
* QR_STR_SCENE
|
||||
* 临时的字符串参数值
|
||||
* @var string
|
||||
*/
|
||||
const QR_STR_SCENE = 'QR_STR_SCENE';
|
||||
/**
|
||||
* QR_LIMIT_SCENE
|
||||
* 永久的整型参数值
|
||||
* @var string
|
||||
*/
|
||||
const QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
|
||||
/**
|
||||
* QR_LIMIT_STR_SCENE
|
||||
* 永久的字符串参数值
|
||||
* @var string
|
||||
*/
|
||||
const QR_LIMIT_STR_SCENE = 'QR_LIMIT_STR_SCENE';
|
||||
public static function getAccessToken()
|
||||
{
|
||||
$config = new Config('wechat_official_account', '');
|
||||
$access_token = Redis::get('wechat_official_account_access_token');
|
||||
if ($access_token) {
|
||||
return $access_token;
|
||||
}
|
||||
if (!$config['state']) {
|
||||
throw new \Exception('请先开启公众号');
|
||||
}
|
||||
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$config['app_id']}&secret={$config['app_secret']}";
|
||||
try {
|
||||
$client = new Client();
|
||||
$response = $client->get($url);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
if (isset($data['errcode'])) {
|
||||
throw new \Exception($data['errmsg']);
|
||||
}
|
||||
Redis::set('wechat_official_account_access_token', $data['access_token'], 'EX', $data['expires_in']);
|
||||
return $data['access_token'];
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建公众号二维码
|
||||
*
|
||||
* @param string $action_name QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
|
||||
* @param int $expire 二维码有效时间,以秒为单位。最大不超过2592000(即30天)
|
||||
* @param array $eventData 回调事件数据
|
||||
* @param Class $eventData[0] 回调事件类
|
||||
* @param string $eventData[1] 回调事件方法
|
||||
* @param array $eventData[2] 回调事件参数,会在回调事件方法中$data.params原样传入
|
||||
* @return array $data
|
||||
* @return string $data['url'] 二维码图片地址
|
||||
* @return string $data['id'] 二维码id,在回调事件中$data['EventKey']
|
||||
* @return int $data['expire_seconds'] 二维码有效时间
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createQrCode($action_name, $expire, $eventData)
|
||||
{
|
||||
$request = request();
|
||||
$id = $request->sessionId();
|
||||
if(count($eventData)<2){
|
||||
throw new \Exception('回调事件数据不完整');
|
||||
}
|
||||
$access_token = self::getAccessToken();
|
||||
$url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $access_token;
|
||||
$client = new Client();
|
||||
$data = [
|
||||
'expire_seconds' => $expire,
|
||||
'action_name' => $action_name,
|
||||
'action_info' => [
|
||||
'scene' => []
|
||||
]
|
||||
];
|
||||
if (in_array($action_name, [self::QR_SCENE, self::QR_LIMIT_SCENE])) {
|
||||
$data['action_info']['scene'] = ['scene_id' => $id];
|
||||
} else {
|
||||
$data['action_info']['scene'] = ['scene_str' => $id];
|
||||
}
|
||||
Redis::set($id, json_encode($eventData, JSON_UNESCAPED_UNICODE), 'EX', $expire + 60);
|
||||
$response = $client->post($url, [
|
||||
'json' => $data
|
||||
]);
|
||||
$res = json_decode($response->getBody()->getContents(), true);
|
||||
if (isset($res['errcode'])) {
|
||||
throw new \Exception($res['errmsg']);
|
||||
}
|
||||
$res['id'] = $id;
|
||||
return $res;
|
||||
}
|
||||
/**
|
||||
* 发送模板消息
|
||||
*
|
||||
* @param mixed $params 模板消息参数
|
||||
* @return string
|
||||
*/
|
||||
public static function sendTemplate($params)
|
||||
{
|
||||
$access_token = self::getAccessToken();
|
||||
$url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token;
|
||||
$client = new Client([
|
||||
'content_type' => 'application/json',
|
||||
]);
|
||||
$response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
if (empty($data['msgid'])) {
|
||||
throw new \Exception("[{$data['errcode']}]" . $data['errmsg']);
|
||||
}
|
||||
return $data['msgid'];
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\middleware;
|
||||
|
||||
use app\expose\enum\ResponseCode;
|
||||
use app\expose\enum\State;
|
||||
use app\expose\trait\Json;
|
||||
use app\model\Admin;
|
||||
use Exception;
|
||||
use loong\oauth\exception\LockException;
|
||||
use loong\oauth\exception\TokenExpireException;
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
use loong\oauth\facade\Auth;
|
||||
use support\Log;
|
||||
/**
|
||||
* 后台应用必须继承此中间件,否者无法正常访问
|
||||
*/
|
||||
class AdminAuth implements MiddlewareInterface
|
||||
{
|
||||
use Json;
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
if ($request->method() == 'OPTIONS') {
|
||||
return response('', 204);
|
||||
}
|
||||
# 微信浏览器
|
||||
$this->isWehcatBrowser($request);
|
||||
// 鉴权检测
|
||||
try {
|
||||
$this->Authorization($request);
|
||||
$response = $next($request);
|
||||
} catch (\Throwable $th) {
|
||||
if ($request->expectsJson()) {
|
||||
$response = $this->exception($th);
|
||||
} else {
|
||||
if (config('app.debug')) {
|
||||
Log::error($th->getTraceAsString());
|
||||
throw $th;
|
||||
} else {
|
||||
$response = response($th->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
public function isWehcatBrowser($request)
|
||||
{
|
||||
$request->isWehcatBrowser = strpos(strtolower($request->header('user-agent') ?? ''), 'micromessenger') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务逻辑
|
||||
* @author 贵州猿创科技有限公司
|
||||
* @Email 416716328@qq.com
|
||||
* @DateTime 2023-03-11
|
||||
*/
|
||||
public function Authorization(Request $request)
|
||||
{
|
||||
# 无控制器地址
|
||||
if (!$request->controller) {
|
||||
return true;
|
||||
}
|
||||
# 获取控制器鉴权信息
|
||||
$controller = new \ReflectionClass($request->controller);
|
||||
$properties = $controller->getDefaultProperties();
|
||||
# 无需登录方法
|
||||
$notNeedLogin = $properties['notNeedLogin'] ?? [];
|
||||
# 无需鉴权方法
|
||||
$notNeedAuth = $properties['notNeedAuth'] ?? [];
|
||||
# 是否强制登录
|
||||
$isForceLogin = true;
|
||||
if (in_array($request->action, $notNeedLogin)) {
|
||||
$isForceLogin = false;
|
||||
}
|
||||
try {
|
||||
# 令牌验证
|
||||
$token = $request->header('Authorization');
|
||||
if (!$token) {
|
||||
throw new Exception('请先登录', ResponseCode::NEED_LOGIN);
|
||||
}
|
||||
$user = Auth::setPrefix('ADMIN')->decrypt($token);
|
||||
if (!$user) {
|
||||
throw new TokenExpireException();
|
||||
}
|
||||
$request->admin_uid = $user['uid'];
|
||||
} catch (TokenExpireException $e) {
|
||||
if ($isForceLogin) {
|
||||
throw new Exception('登录已过期,请重新登录', ResponseCode::NEED_LOGIN);
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
if ($isForceLogin) {
|
||||
throw new Exception($e->getMessage(), ResponseCode::LOCK);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
if ($isForceLogin) {
|
||||
throw new \Exception($th->getMessage(), ResponseCode::NEED_LOGIN);
|
||||
}
|
||||
}
|
||||
if ($request->admin_uid) {
|
||||
if ($user['is_system']) {
|
||||
return true;
|
||||
} else {
|
||||
$now = date('H:i:s');
|
||||
if (!($now >= $user['allow_time_start'] && $now <= $user['allow_time_end'])) {
|
||||
throw new Exception("当前不在工作时间");
|
||||
}
|
||||
if (!in_array(date('w'), $user['allow_week'])) {
|
||||
throw new Exception("今日因该是休息日哦");
|
||||
}
|
||||
$Admin = Admin::where('id', $user['uid'])->find();
|
||||
if ($Admin->state != State::YES['value']) {
|
||||
throw new Exception("您的账号已被禁用", ResponseCode::NEED_LOGIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
# 不需要鉴权
|
||||
if (in_array($request->action, $notNeedAuth)) {
|
||||
return true;
|
||||
}
|
||||
if (empty($user)) {
|
||||
throw new Exception('请先登录', ResponseCode::NEED_LOGIN);
|
||||
}
|
||||
if (empty($user['permissions'])) {
|
||||
throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION);
|
||||
}
|
||||
$plugin = $request->plugin;
|
||||
// plugin\api\app\admin\controller\IndexController,只取Index
|
||||
$controllers = explode('\\', $request->controller);
|
||||
$controller = str_replace('Controller', '', end($controllers));
|
||||
$action = str_replace(['GetTable', 'GetGrid'], ['', ''], $request->action);
|
||||
$rule = $controller . '/' . $action;
|
||||
if ($plugin) {
|
||||
$app = $request->app;
|
||||
$rule = "/app/{$plugin}/{$app}/{$rule}";
|
||||
}
|
||||
if (!in_array($rule, $user['permissions'])) {
|
||||
throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\middleware;
|
||||
|
||||
use app\expose\enum\Platform as EnumPlatform;
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
/**
|
||||
* 用到支付必须引用继承这个中间件
|
||||
*/
|
||||
class Platform implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
$platform = $request->header('x-platform');
|
||||
if ($platform &&EnumPlatform::has($platform)) {
|
||||
$request->platform = $platform;
|
||||
}
|
||||
$this->isMobile($request);
|
||||
if ($request->isMobile) {
|
||||
$request->platform = EnumPlatform::H5['value'];
|
||||
}
|
||||
# 微信浏览器
|
||||
$this->isWehcatBrowser($request);
|
||||
# 检测多语言
|
||||
$this->languare($request);
|
||||
$response = $next($request);
|
||||
return $response;
|
||||
}
|
||||
public function isWehcatBrowser($request)
|
||||
{
|
||||
$request->isWehcatBrowser = strpos(strtolower($request->header('user-agent')??''), 'micromessenger') !== false;
|
||||
if($request->isWehcatBrowser&&$request->platform==EnumPlatform::H5['value']){
|
||||
$request->platform = EnumPlatform::WECHAT_OFFICIAL_ACCOUNT['value'];
|
||||
}
|
||||
}
|
||||
public function isMobile($request)
|
||||
{
|
||||
$request->isMobile = strpos(strtolower($request->header('user-agent')??''), 'mobile') !== false;
|
||||
}
|
||||
public function languare($request)
|
||||
{
|
||||
$lang=$request->header('lang');
|
||||
if(!$lang){
|
||||
$AcceptLanguage=$request->header('accept-language');
|
||||
if($AcceptLanguage){
|
||||
$AcceptLanguageArr=explode(',',$AcceptLanguage);
|
||||
if(isset($AcceptLanguageArr[0])){
|
||||
$lang=$AcceptLanguageArr[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
$request->lang='zh-CN';
|
||||
if($lang){
|
||||
$request->lang=$lang;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
use support\View;
|
||||
|
||||
/**
|
||||
* 如需要使用模板引擎,可以继承此中间件
|
||||
*/
|
||||
class Template implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
$webInfo = [
|
||||
'title' => 'Webman',
|
||||
'keywords' => 'Webman,PHP',
|
||||
'description' => 'Webman is a high-performance, component-based PHP framework for web applications.',
|
||||
];
|
||||
View::assign('web', $webInfo);
|
||||
/** @var Response $response */
|
||||
$response = $next($request);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\template\email;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Vcode extends DataModel
|
||||
{
|
||||
public function builder($username)
|
||||
{
|
||||
return "您的验证码是:1234";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\template\sms;
|
||||
|
||||
use app\expose\helper\ConfigGroup;
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class Basic extends DataModel
|
||||
{
|
||||
protected $data;
|
||||
public function __construct($channels = null)
|
||||
{
|
||||
if ($channels) {
|
||||
$config = new ConfigGroup('sms', '');
|
||||
$this->data['channels'] = $channels;
|
||||
$this->data['config'] = $config[$channels];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\template\sms;
|
||||
|
||||
class Vcode extends Basic
|
||||
{
|
||||
public function builder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\trait;
|
||||
|
||||
use app\expose\helper\Config as HelperConfig;
|
||||
use app\model\Config as ModelConfig;
|
||||
use app\expose\enum\ResponseEvent;
|
||||
use app\expose\helper\ConfigGroup;
|
||||
|
||||
/**
|
||||
* 使用该抽象类需要在控制器中引入以下代码
|
||||
*
|
||||
* use app\expose\trait\Config;
|
||||
* use app\expose\trait\Json;
|
||||
*
|
||||
* Class Config
|
||||
* {
|
||||
* use Config;
|
||||
* use Json;
|
||||
* }
|
||||
*/
|
||||
trait Config
|
||||
{
|
||||
public $channels_uid=null;
|
||||
/**
|
||||
* 配置管理
|
||||
* @return mixed
|
||||
*/
|
||||
private function builder($callback = null)
|
||||
{
|
||||
$request = request();
|
||||
if ($request->method() === 'POST') {
|
||||
return $this->update($callback);
|
||||
}
|
||||
$builder = HelperConfig::formBuilder($request->action, null, $this->channels_uid);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
private function tabsBuilder($callback = null)
|
||||
{
|
||||
$request = request();
|
||||
if ($request->method() === 'POST') {
|
||||
return $this->update($callback);
|
||||
}
|
||||
$builder = ConfigGroup::formBuilder($request->action, null, $this->channels_uid);
|
||||
return $this->resData($builder);
|
||||
}
|
||||
private function update($callback = null)
|
||||
{
|
||||
$request = request();
|
||||
$HelperConfig = new HelperConfig($request->action, null, $this->channels_uid);
|
||||
$group = $HelperConfig->getGrop();
|
||||
$D = $request->post();
|
||||
$where = [];
|
||||
$where[] = ['group', '=', $group];
|
||||
if ($this->channels_uid) {
|
||||
$where[] = ['channels_uid', '=', $this->channels_uid];
|
||||
}
|
||||
$ConfigModel = ModelConfig::where($where)->find();
|
||||
if (!$ConfigModel) {
|
||||
$ConfigModel = new ModelConfig;
|
||||
$ConfigModel->group = $group;
|
||||
if ($this->channels_uid) {
|
||||
$ConfigModel->channels_uid = $this->channels_uid;
|
||||
}
|
||||
}
|
||||
$ConfigModel->value = $D;
|
||||
if ($ConfigModel->save()) {
|
||||
if ($callback) {
|
||||
$callback($D);
|
||||
}
|
||||
return $this->event(ResponseEvent::UPDATE_WEBCONFIG, '保存成功');
|
||||
}
|
||||
return $this->fail('保存失败');
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\trait;
|
||||
|
||||
use app\expose\enum\ResponseCode;
|
||||
use app\expose\exception\Exception;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* JSON响应工具
|
||||
*/
|
||||
trait Json
|
||||
{
|
||||
/**
|
||||
* 返回成功JSON
|
||||
*
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function success($msg = 'success', $data = [])
|
||||
{
|
||||
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回失败JSON
|
||||
*
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function fail(string $msg = 'fail', $data = [])
|
||||
{
|
||||
return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回数据JSON
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function resData($data)
|
||||
{
|
||||
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]);
|
||||
}
|
||||
protected static function event($event, $msg = 'success')
|
||||
{
|
||||
return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [
|
||||
'event' => $event
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* 返回自定义JSON
|
||||
*
|
||||
* @param int $code 状态码
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function code($code, $msg = null, $data = [])
|
||||
{
|
||||
return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回异常消息
|
||||
*
|
||||
* @param \Throwable $th 捕获的异常
|
||||
* @return Response
|
||||
*/
|
||||
protected static function exception($th)
|
||||
{
|
||||
$data = [];
|
||||
if (config('app.debug')) {
|
||||
$data = [
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine(),
|
||||
'trace' => $th->getTrace(),
|
||||
];
|
||||
}
|
||||
if ($th instanceof Exception) {
|
||||
$data = array_merge($data, $th->getData());
|
||||
}
|
||||
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 响应失败
|
||||
*
|
||||
* @param \Throwable $th 捕获的异常
|
||||
* @return Response
|
||||
*/
|
||||
protected static function server($th, $http_code = 500)
|
||||
{
|
||||
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code);
|
||||
}
|
||||
/**
|
||||
* 返回JSON
|
||||
*
|
||||
* @param mixed $data JSON数据
|
||||
* @param int|null $options JSON编码
|
||||
* @param int $http_code 服务器响应代码
|
||||
* @return Response
|
||||
*/
|
||||
protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200)
|
||||
{
|
||||
$request = request();
|
||||
if ($request && $request->lang) {
|
||||
$data['msg'] = trans($data['msg'], [], null, $request->lang);
|
||||
}
|
||||
return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options));
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\trait;
|
||||
|
||||
use app\expose\enum\Filesystem;
|
||||
use app\model\Uploads as ModelUploads;
|
||||
use app\model\UploadsClassify;
|
||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||
use Shopwwi\WebmanFilesystem\FilesystemFactory;
|
||||
use support\Log;
|
||||
use support\Request;
|
||||
|
||||
trait Uploads
|
||||
{
|
||||
protected $admin_uid;
|
||||
protected $uid;
|
||||
public function getList(Request $request)
|
||||
{
|
||||
$limit = $request->get('limit', 10);
|
||||
$dir_name = $request->get('dir_name', 'all');
|
||||
$where = [];
|
||||
if ($dir_name != 'all') {
|
||||
$where[] = ['c.dir_name', '=', $dir_name];
|
||||
}
|
||||
$accept = $request->get('accept');
|
||||
if ($accept) {
|
||||
# 判断是否包含*号
|
||||
if (strpos($accept, '*') !== false) {
|
||||
$accept = str_replace('*', '', $accept);
|
||||
$where[] = ['u.mime', 'like', "%{$accept}%"];
|
||||
} elseif(strpos($accept, '.') !== false){
|
||||
$accept = str_replace('.', '', $accept);
|
||||
if(strpos($accept, ',') !== false){
|
||||
$accept = explode(',', $accept);
|
||||
$where[] = ['u.ext', 'in', $accept];
|
||||
} else {
|
||||
$where[] = ['u.ext', '=', $accept];
|
||||
}
|
||||
} else {
|
||||
$where[] = ['u.mime', 'in', explode(',', $accept)];
|
||||
}
|
||||
}
|
||||
if ($this->uid) {
|
||||
$where[] = ['u.uid', '=', $this->uid];
|
||||
}
|
||||
if ($this->admin_uid) {
|
||||
$where[] = ['u.admin_uid', '=', $this->admin_uid];
|
||||
}
|
||||
$ModelUploads = ModelUploads::alias('u')->where($where)
|
||||
->join('uploads_classify c', 'u.classify_id=c.id')
|
||||
->field('u.*,c.title as classify_title')
|
||||
->order('u.id desc')
|
||||
->paginate($limit)->each(function ($item) {
|
||||
$item->url = Storage::adapter($item->channels)->url($item->path);
|
||||
return $item;
|
||||
});
|
||||
return $this->resData($ModelUploads);
|
||||
}
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$dir_name = $request->post('dir_name');
|
||||
$dir_title = $request->post('dir_title');
|
||||
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
|
||||
if (!$UploadsClassify) {
|
||||
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->find();
|
||||
if (!$UploadsClassify) {
|
||||
if (empty($dir_title)) {
|
||||
return $this->fail('分类不存在');
|
||||
}
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->title = $dir_title;
|
||||
$UploadsClassify->dir_name = $dir_name;
|
||||
$UploadsClassify->channels = config('plugin.shortplay.filesystem.app.default');
|
||||
$UploadsClassify->save();
|
||||
}
|
||||
}
|
||||
$channels = $request->post('channels');
|
||||
if (!$channels) {
|
||||
$channels = $UploadsClassify->channels;
|
||||
}
|
||||
$date_path = date('Ymd');
|
||||
//单文件上传
|
||||
$file = $request->file('file');
|
||||
try {
|
||||
$result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file);
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
try {
|
||||
$Uploads = new ModelUploads;
|
||||
$Uploads->uid = $this->uid;
|
||||
$Uploads->admin_uid = $this->admin_uid;
|
||||
$Uploads->classify_id = $UploadsClassify->id;
|
||||
$Uploads->filename = $result->origin_name;
|
||||
$Uploads->path = $result->file_name;
|
||||
$Uploads->ext = $result->extension;
|
||||
$Uploads->mime = $result->mime_type;
|
||||
$Uploads->size = $result->size;
|
||||
$Uploads->channels = $channels;
|
||||
$Uploads->save();
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
return $this->resData([
|
||||
'id' => $Uploads->id,
|
||||
'url' => $result->file_url,
|
||||
'path' => $result->file_name,
|
||||
'mime' => $result->mime_type,
|
||||
'dir_name' => $dir_name
|
||||
]);
|
||||
}
|
||||
public function uploads(Request $request)
|
||||
{
|
||||
$dir_name = $request->post('dir_name');
|
||||
$dir_title = $request->post('dir_title');
|
||||
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
|
||||
if (!$UploadsClassify) {
|
||||
$UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->find();
|
||||
if (!$UploadsClassify) {
|
||||
if (empty($dir_title)) {
|
||||
return $this->fail('分类不存在');
|
||||
}
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->title = $dir_title;
|
||||
$UploadsClassify->dir_name = $dir_name;
|
||||
$UploadsClassify->channels = config('plugin.shortplay.filesystem.app.default');
|
||||
$UploadsClassify->save();
|
||||
}
|
||||
}
|
||||
$channels = $request->post('channels');
|
||||
if (!$channels) {
|
||||
$channels = $UploadsClassify->channels;
|
||||
}
|
||||
$date_path = date('Ymd');
|
||||
$files = $request->file();
|
||||
try {
|
||||
$results = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->uploads($files);
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
$resData = [];
|
||||
try {
|
||||
foreach ($results as $result) {
|
||||
$Uploads = new ModelUploads;
|
||||
$Uploads->uid = $this->uid;
|
||||
$Uploads->admin_uid = $this->admin_uid;
|
||||
$Uploads->classify_id = $UploadsClassify->id;
|
||||
$Uploads->filename = $result->origin_name;
|
||||
$Uploads->path = $result->file_name;
|
||||
$Uploads->ext = $result->extension;
|
||||
$Uploads->mime = $result->mime_type;
|
||||
$Uploads->size = $result->size;
|
||||
$Uploads->channels = $channels;
|
||||
$Uploads->save();
|
||||
$resData[] = [
|
||||
'id' => $Uploads->id,
|
||||
'url' => $result->file_url,
|
||||
'path' => $result->file_name,
|
||||
'mime' => $result->mime_type,
|
||||
'dir_name' => $dir_name
|
||||
];
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
return $this->exception($th);
|
||||
}
|
||||
return $this->resData($resData);
|
||||
}
|
||||
public function deleteUploads(Request $request)
|
||||
{
|
||||
$ids = $request->post('ids');
|
||||
$data = ModelUploads::whereIn('id', $ids)->select();
|
||||
foreach ($data as $item) {
|
||||
if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$filesystem = FilesystemFactory::get($item->channels);
|
||||
$filesystem->delete($item->path);
|
||||
} catch (\Throwable $th) {
|
||||
Log::error('删除文件"'.$item->path.'"失败:'.$th->getMessage(),$th->getTrace());
|
||||
}
|
||||
$item->delete();
|
||||
}
|
||||
return $this->success('删除成功');
|
||||
}
|
||||
public function updateUploads(Request $request)
|
||||
{
|
||||
$ids = $request->post('ids');
|
||||
$dir_name = $request->post('dir_name');
|
||||
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
|
||||
if (!$UploadsClassify) {
|
||||
return $this->fail('分类不存在');
|
||||
}
|
||||
$data = ModelUploads::whereIn('id', $ids)->select();
|
||||
foreach ($data as $item) {
|
||||
if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) {
|
||||
continue;
|
||||
}
|
||||
$item->classify_id = $UploadsClassify->id;
|
||||
$item->save();
|
||||
}
|
||||
return $this->success('修改成功');
|
||||
}
|
||||
public function getUploadClassify(Request $request)
|
||||
{
|
||||
if (!UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->count()) {
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->title = '默认分类';
|
||||
$UploadsClassify->dir_name = 'uploads/default';
|
||||
$UploadsClassify->channels = Filesystem::PUBLIC['value'];
|
||||
$UploadsClassify->sort = 0;
|
||||
$UploadsClassify->is_system = 1;
|
||||
$UploadsClassify->save();
|
||||
}
|
||||
$UploadsClassify = UploadsClassify::order('sort asc,id asc')->select()->toArray();
|
||||
$all = [
|
||||
'title' => '全部',
|
||||
'dir_name' => 'all',
|
||||
'is_system' => 1,
|
||||
];
|
||||
array_unshift($UploadsClassify, $all);
|
||||
return $this->resData($UploadsClassify);
|
||||
}
|
||||
public function saveClassify(Request $request)
|
||||
{
|
||||
$data = $request->post();
|
||||
if (empty($data['title'])) {
|
||||
return $this->fail('标题不能为空');
|
||||
}
|
||||
if (empty($data['channels'])) {
|
||||
return $this->fail('请选择储存渠道');
|
||||
}
|
||||
if (empty($data['dir_name'])) {
|
||||
return $this->fail('目录名不能为空');
|
||||
}
|
||||
$UploadsClassify = UploadsClassify::where('dir_name', $data['dir_name'])->find();
|
||||
if (!$UploadsClassify) {
|
||||
$UploadsClassify = new UploadsClassify;
|
||||
$UploadsClassify->dir_name = 'uploads/' . $data['dir_name'];
|
||||
}
|
||||
$UploadsClassify->title = $data['title'];
|
||||
$UploadsClassify->channels = $data['channels'];
|
||||
if (!empty($data['sort'])) {
|
||||
$UploadsClassify->sort = $data['sort'];
|
||||
}
|
||||
if ($UploadsClassify->save()) {
|
||||
return $this->success('创建成功');
|
||||
} else {
|
||||
return $this->fail('创建失败');
|
||||
}
|
||||
}
|
||||
public function deleteClassify(Request $request)
|
||||
{
|
||||
$dir_name = $request->post('dir_name');
|
||||
if (empty($dir_name)) {
|
||||
return $this->fail('目录名不能为空');
|
||||
}
|
||||
if ($dir_name == 'all') {
|
||||
return $this->fail('全部目录不能删除');
|
||||
}
|
||||
$UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find();
|
||||
if (!$UploadsClassify) {
|
||||
return $this->fail('目录不存在');
|
||||
}
|
||||
if ($UploadsClassify->is_system) {
|
||||
return $this->fail('系统分类,无法删除');
|
||||
}
|
||||
if (ModelUploads::where(['classify_id' => $UploadsClassify->id])->count() > 0) {
|
||||
return $this->fail('目录分类下有文件不能删除');
|
||||
}
|
||||
if ($UploadsClassify->delete()) {
|
||||
return $this->success('删除成功');
|
||||
} else {
|
||||
return $this->fail('删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* 数据序列化模型
|
||||
*
|
||||
* 继承DataModel必须实现属性$data
|
||||
* @package app\utils
|
||||
* @property mixed $data
|
||||
* @method mixed __get(string $name)
|
||||
* @method void __set(string $name, mixed $value)
|
||||
* @method bool __isset(string $name)
|
||||
* @method void __unset(string $name)
|
||||
* @method string __toString()
|
||||
* @method mixed offsetGet(mixed $offset)
|
||||
* @method void offsetSet(mixed $offset, mixed $value)
|
||||
* @method bool offsetExists(mixed $offset)
|
||||
* @method void offsetUnset(mixed $offset)
|
||||
* @method array toArray()
|
||||
* @method string toJson()
|
||||
* @method array jsonSerialize()
|
||||
*/
|
||||
class DataModel implements ArrayAccess, JsonSerializable
|
||||
{
|
||||
protected $data = [];
|
||||
public function __construct($data=[])
|
||||
{
|
||||
$this->data=$data;
|
||||
}
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->data[$name]);
|
||||
}
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->data[$name]);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
public function offsetGet(mixed $offset):mixed
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
}
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->data[] = $value;
|
||||
} else {
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
}
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return $this->__isset($offset);
|
||||
}
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
$this->__unset($offset);
|
||||
}
|
||||
public function toArray()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
|
||||
class Email
|
||||
{
|
||||
public $toemail;
|
||||
protected $template;
|
||||
protected $data;
|
||||
public function setTemplate($template)
|
||||
{
|
||||
$this->template = new $template;
|
||||
return $this;
|
||||
}
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
public function send()
|
||||
{
|
||||
$content = $this->template->builder($this->toemail, $this->data);
|
||||
p($content);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use app\expose\enum\ResponseCode;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* JSON响应工具
|
||||
* @deprecated 请使用app\expose\trait\Json替代
|
||||
*/
|
||||
trait Json
|
||||
{
|
||||
/**
|
||||
* 返回成功JSON
|
||||
*
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function success($msg = 'success', $data = [])
|
||||
{
|
||||
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回失败JSON
|
||||
*
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function fail(string $msg = 'fail', $data = [])
|
||||
{
|
||||
return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回数据JSON
|
||||
*
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function resData($data)
|
||||
{
|
||||
return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]);
|
||||
}
|
||||
protected static function event($event, $msg = 'success')
|
||||
{
|
||||
return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [
|
||||
'event' => $event
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* 返回自定义JSON
|
||||
*
|
||||
* @param int $code 状态码
|
||||
* @param string $msg 消息
|
||||
* @param mixed $data 数据
|
||||
* @return Response
|
||||
*/
|
||||
protected static function code($code, $msg = null, $data = [])
|
||||
{
|
||||
return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 返回异常消息
|
||||
*
|
||||
* @param \Throwable $th 捕获的异常
|
||||
* @return Response
|
||||
*/
|
||||
protected static function exception($th)
|
||||
{
|
||||
$data = [];
|
||||
if (config('app.debug')) {
|
||||
$data = [
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine(),
|
||||
'trace' => $th->getTrace(),
|
||||
];
|
||||
}
|
||||
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]);
|
||||
}
|
||||
/**
|
||||
* 响应失败
|
||||
*
|
||||
* @param \Throwable $th 捕获的异常
|
||||
* @return Response
|
||||
*/
|
||||
protected static function server($th, $http_code = 500)
|
||||
{
|
||||
return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code);
|
||||
}
|
||||
/**
|
||||
* 返回JSON
|
||||
*
|
||||
* @param mixed $data JSON数据
|
||||
* @param int|null $options JSON编码
|
||||
* @param int $http_code 服务器响应代码
|
||||
* @return Response
|
||||
*/
|
||||
protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200)
|
||||
{
|
||||
$request=request();
|
||||
if($request&&$request->lang){
|
||||
$data['msg']=trans($data['msg'],[], null, $request->lang);
|
||||
}
|
||||
return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options));
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
class Password
|
||||
{
|
||||
/**
|
||||
* 获取密码散列值
|
||||
*
|
||||
* @param string $password
|
||||
* @param [type] $algo
|
||||
* @return string
|
||||
*/
|
||||
public static function encrypt(string $password, string $algo = PASSWORD_DEFAULT): string
|
||||
{
|
||||
return password_hash($password, $algo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $hash
|
||||
* @return boolean
|
||||
*/
|
||||
public static function verify(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use \Exception;
|
||||
|
||||
/**
|
||||
* RAS加解密
|
||||
*/
|
||||
class Rsa
|
||||
{
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param string 加密后的字符串
|
||||
* @param string 私钥,可以是文件路径
|
||||
* @return object
|
||||
*/
|
||||
public static function decrypt($token, $rsa_privatekey)
|
||||
{
|
||||
if (file_exists($rsa_privatekey)) {
|
||||
$rsa_privatekey = @file_get_contents($rsa_privatekey);
|
||||
}
|
||||
if (empty($rsa_privatekey)) {
|
||||
throw new Exception('私钥为空');
|
||||
}
|
||||
$split = str_split($token, 172); // 1024 bit 固定172
|
||||
$crypto = '';
|
||||
foreach ($split as $chunk) {
|
||||
$isOkay = openssl_private_decrypt(base64_decode($chunk), $decryptData, $rsa_privatekey); // base64在这里使用,因为172字节是一组,是encode来的
|
||||
if (!$isOkay) {
|
||||
throw new Exception("解密失败");
|
||||
}
|
||||
$crypto .= $decryptData;
|
||||
}
|
||||
if (!$crypto) {
|
||||
throw new Exception("解密失败");
|
||||
}
|
||||
return json_decode(base64_decode($crypto));
|
||||
}
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param mixed 可被json序列化的数据
|
||||
* @param string 公钥,可以是文件路径
|
||||
* @return string
|
||||
*/
|
||||
public static function encrypt(mixed $data, string $rsa_publickey)
|
||||
{
|
||||
if (file_exists($rsa_publickey)) {
|
||||
$rsa_publickey = @file_get_contents($rsa_publickey);
|
||||
}
|
||||
if (empty($rsa_publickey)) {
|
||||
throw new Exception('公钥为空');
|
||||
}
|
||||
$data = base64_encode(json_encode($data));
|
||||
$split = str_split($data, 117); // 1024 bit && OPENSSL_PKCS1_PADDING 不大于117即可
|
||||
$crypto = '';
|
||||
foreach ($split as $chunk) {
|
||||
$isOkay = openssl_public_encrypt($chunk, $encryptData, $rsa_publickey);
|
||||
if (!$isOkay) {
|
||||
throw new Exception("加密失败");
|
||||
}
|
||||
$crypto .= base64_encode($encryptData);
|
||||
}
|
||||
return $crypto;
|
||||
}
|
||||
/**
|
||||
* 创建一对公钥私钥
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function createKey()
|
||||
{
|
||||
$res = openssl_pkey_new([
|
||||
"private_key_bits" => 2048,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA
|
||||
]);
|
||||
openssl_pkey_export($res, $privKey);
|
||||
$pubKey = openssl_pkey_get_details($res);
|
||||
return [
|
||||
'privatekey' => $privKey,
|
||||
'publickey' => $pubKey["key"]
|
||||
];
|
||||
}
|
||||
# 以下是数字加密解密
|
||||
const PASSWORD = 'MAW512P89WIAUW9G7OHQWVGAPX51WPSN';
|
||||
/**
|
||||
* 数字加密函数
|
||||
*
|
||||
* @param int $number
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
public static function encryptNumber(int $number, string $password = '')
|
||||
{
|
||||
// 生成随机的初始向量
|
||||
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
|
||||
$password = $password ?: self::PASSWORD;
|
||||
// 加密数字
|
||||
$encrypted = openssl_encrypt($number, 'aes-256-cbc', $password, 0, $iv);
|
||||
|
||||
// 将初始向量和加密后的数据进行编码并拼接起来
|
||||
$encoded = base64_encode($iv . $encrypted);
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
/**
|
||||
* 数字解密函数
|
||||
*
|
||||
* @param string $encrypted
|
||||
* @param string $password
|
||||
* @return int
|
||||
*/
|
||||
public static function decryptNumber(string $encrypted, string $password = '')
|
||||
{
|
||||
// 解码加密后的数据
|
||||
$decoded = base64_decode($encrypted);
|
||||
|
||||
// 提取初始向量和加密后的数据
|
||||
$ivLength = openssl_cipher_iv_length('aes-256-cbc');
|
||||
$iv = substr($decoded, 0, $ivLength);
|
||||
$encryptedData = substr($decoded, $ivLength);
|
||||
|
||||
$password = $password ?: self::PASSWORD;
|
||||
|
||||
// 解密数据
|
||||
$decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $password, 0, $iv);
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use app\expose\enum\SmsChannels;
|
||||
use Exception;
|
||||
|
||||
use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi;
|
||||
use Darabonba\OpenApi\Models\Config;
|
||||
use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest;
|
||||
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
|
||||
|
||||
class Sms
|
||||
{
|
||||
public $mobile;
|
||||
protected $data;
|
||||
protected $template;
|
||||
public function setTemplate($template)
|
||||
{
|
||||
# 判断$template是否已经new过
|
||||
if (is_object($template)) {
|
||||
$this->template = $template;
|
||||
return $this;
|
||||
}
|
||||
$this->template = new $template;
|
||||
return $this;
|
||||
}
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
public function send()
|
||||
{
|
||||
$this->template->builder($this->mobile, $this->data);
|
||||
switch ($this->template->channel) {
|
||||
case SmsChannels::ALIYUN['value']:
|
||||
$this->sendAliyun();
|
||||
break;
|
||||
case SmsChannels::TENCENT['value']:
|
||||
$this->sendTencent();
|
||||
break;
|
||||
case SmsChannels::SMSBAO['value']:
|
||||
$this->sendSmsbao();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("未知的短信服务商");
|
||||
}
|
||||
}
|
||||
public function sendAliyun()
|
||||
{
|
||||
if (empty($this->template->config)) {
|
||||
throw new Exception("阿里云短信服务商配置不完整");
|
||||
}
|
||||
$config = new Config([
|
||||
"accessKeyId" => $this->template->config['access_key_id'],
|
||||
"accessKeySecret" => $this->template->config['access_secret'],
|
||||
]);
|
||||
$config->endpoint = "dysmsapi.aliyuncs.com";
|
||||
$client = new Dysmsapi($config);
|
||||
$request = new SendSmsRequest([
|
||||
"phoneNumbers" => $this->mobile,
|
||||
"templateCode" => $this->template->template_code,
|
||||
"templateParam" => json_encode($this->data, JSON_UNESCAPED_UNICODE),
|
||||
"signName" => $this->template->config['sign_name']
|
||||
]);
|
||||
$runtime = new RuntimeOptions();
|
||||
$runtime->maxIdleConns = 3;
|
||||
$runtime->connectTimeout = 10000;
|
||||
$runtime->readTimeout = 10000;
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
$res = $client->sendSms($request, $runtime);
|
||||
if ($res->body->code == 'OK') {
|
||||
return true;
|
||||
}
|
||||
if ($res->body->message) {
|
||||
throw new Exception($res->body->message);
|
||||
}
|
||||
throw new Exception("发送失败");
|
||||
}
|
||||
public function sendTencent()
|
||||
{
|
||||
throw new Exception("error");
|
||||
}
|
||||
public function sendSmsbao()
|
||||
{
|
||||
throw new Exception("error");
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
class Str
|
||||
{
|
||||
|
||||
protected static $snakeCache = [];
|
||||
|
||||
protected static $camelCache = [];
|
||||
|
||||
protected static $studlyCache = [];
|
||||
|
||||
/**
|
||||
* 检查字符串中是否包含某些字符串
|
||||
* @param string $haystack
|
||||
* @param string|array $needles
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains(string $haystack, $needles): bool
|
||||
{
|
||||
foreach ((array) $needles as $needle) {
|
||||
if ('' != $needle && mb_strpos($haystack, $needle) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否以某些字符串结尾
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|array $needles
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith(string $haystack, $needles): bool
|
||||
{
|
||||
foreach ((array) $needles as $needle) {
|
||||
if ((string) $needle === static::substr($haystack, -static::length($needle))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否以某些字符串开头
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|array $needles
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith(string $haystack, $needles): bool
|
||||
{
|
||||
foreach ((array) $needles as $needle) {
|
||||
if ('' != $needle && mb_strpos($haystack, $needle) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定长度的随机字母数字组合的字符串
|
||||
*
|
||||
* @param int $length
|
||||
* @param int $type
|
||||
* @param string $addChars
|
||||
* @return string
|
||||
*/
|
||||
public static function random(int $length = 6, int $type = null, string $addChars = ''): string
|
||||
{
|
||||
$str = '';
|
||||
switch ($type) {
|
||||
case 0:
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
|
||||
break;
|
||||
case 1:
|
||||
$chars = str_repeat('0123456789', 3);
|
||||
break;
|
||||
case 2:
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
|
||||
break;
|
||||
case 3:
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
|
||||
break;
|
||||
case 4:
|
||||
$chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars;
|
||||
break;
|
||||
default:
|
||||
$chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
|
||||
break;
|
||||
}
|
||||
if ($length > 10) {
|
||||
$chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5);
|
||||
}
|
||||
if ($type != 4) {
|
||||
$chars = str_shuffle($chars);
|
||||
$str = substr($chars, 0, $length);
|
||||
} else {
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1);
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转小写
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function lower(string $value): string
|
||||
{
|
||||
return mb_strtolower($value, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转大写
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function upper(string $value): string
|
||||
{
|
||||
return mb_strtoupper($value, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串的长度
|
||||
*
|
||||
* @param string $value
|
||||
* @return int
|
||||
*/
|
||||
public static function length(string $value): int
|
||||
{
|
||||
return mb_strlen($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取字符串
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $start
|
||||
* @param int|null $length
|
||||
* @return string
|
||||
*/
|
||||
public static function substr(string $string, int $start, int $length = null): string
|
||||
{
|
||||
return mb_substr($string, $start, $length, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰转下划线
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
public static function snake(string $value, string $delimiter = '_'): string
|
||||
{
|
||||
$key = $value;
|
||||
|
||||
if (isset(static::$snakeCache[$key][$delimiter])) {
|
||||
return static::$snakeCache[$key][$delimiter];
|
||||
}
|
||||
|
||||
if (!ctype_lower($value)) {
|
||||
$value = preg_replace('/\s+/u', '', ucwords($value));
|
||||
|
||||
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
|
||||
}
|
||||
|
||||
return static::$snakeCache[$key][$delimiter] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转驼峰(首字母小写)
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function camel(string $value): string
|
||||
{
|
||||
if (isset(static::$camelCache[$value])) {
|
||||
return static::$camelCache[$value];
|
||||
}
|
||||
|
||||
return static::$camelCache[$value] = lcfirst(static::studly($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转驼峰(首字母大写)
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function studly(string $value): string
|
||||
{
|
||||
$key = $value;
|
||||
|
||||
if (isset(static::$studlyCache[$key])) {
|
||||
return static::$studlyCache[$key];
|
||||
}
|
||||
|
||||
$value = ucwords(str_replace(['-', '_'], ' ', $value));
|
||||
|
||||
return static::$studlyCache[$key] = str_replace(' ', '', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为首字母大写的标题格式
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function title(string $value): string
|
||||
{
|
||||
return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
/**
|
||||
* 手机号,邮箱,脱敏
|
||||
* @param string|null $value
|
||||
* @return string|null
|
||||
*/
|
||||
public static function mask(?string $value): ?string
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||
$arr = explode('@', $value);
|
||||
$name = $arr[0];
|
||||
$domain = $arr[1];
|
||||
|
||||
// 如果长度小于等于4,就直接显示
|
||||
if (strlen($name) <= 4) {
|
||||
$masked = $name;
|
||||
} else {
|
||||
$masked = substr($name, 0, 2) . '****' . substr($name, -2);
|
||||
}
|
||||
|
||||
return $masked . '@' . $domain;
|
||||
} else {
|
||||
return substr($value, 0, 3) . '****' . substr($value, -4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use app\expose\utils\DataModel;
|
||||
|
||||
class TreeModel extends DataModel
|
||||
{
|
||||
private function _organizeRecords($regions,$idKey)
|
||||
{
|
||||
$organizedRegions = [];
|
||||
foreach ($regions as $region) {
|
||||
$organizedRegions[$region[$idKey]] = $region;
|
||||
$organizedRegions[$region[$idKey]]['children'] = [];
|
||||
}
|
||||
return $organizedRegions;
|
||||
}
|
||||
private function _buildTree($organized,$pidKey)
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($organized as $id => $record) {
|
||||
if ($record[$pidKey]) {
|
||||
$organized[$record[$pidKey]]['children'][] = &$organized[$id];
|
||||
} else {
|
||||
$tree[] = &$organized[$id];
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
public function toTree($idKey='id',$pidKey='pid')
|
||||
{
|
||||
$organized=$this->_organizeRecords($this->data,$idKey);
|
||||
$data=$this->_buildTree($organized,$pidKey);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* PHP Class for handling Google Authenticator 2-factor authentication.
|
||||
*
|
||||
* @author Michael Kliewe
|
||||
* @copyright 2012 Michael Kliewe
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*
|
||||
* @link http://www.phpgangsta.de/
|
||||
*/
|
||||
class TwofaAuthenticator
|
||||
{
|
||||
protected $_codeLength = 6;
|
||||
|
||||
/**
|
||||
* Create new secret.
|
||||
* 16 characters, randomly chosen from the allowed base32 characters.
|
||||
*
|
||||
* @param int $secretLength
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createSecret($secretLength = 16)
|
||||
{
|
||||
$validChars = $this->_getBase32LookupTable();
|
||||
|
||||
// Valid secret lengths are 80 to 640 bits
|
||||
if ($secretLength < 16 || $secretLength > 128) {
|
||||
throw new Exception('Bad secret length');
|
||||
}
|
||||
$secret = '';
|
||||
$rnd = false;
|
||||
if (function_exists('random_bytes')) {
|
||||
$rnd = random_bytes($secretLength);
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
$rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
$rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
|
||||
if (!$cryptoStrong) {
|
||||
$rnd = false;
|
||||
}
|
||||
}
|
||||
if ($rnd !== false) {
|
||||
for ($i = 0; $i < $secretLength; ++$i) {
|
||||
$secret .= $validChars[ord($rnd[$i]) & 31];
|
||||
}
|
||||
} else {
|
||||
throw new Exception('No source of secure random');
|
||||
}
|
||||
|
||||
return $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the code, with given secret and point in time.
|
||||
*
|
||||
* @param string $secret
|
||||
* @param int|null $timeSlice
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCode($secret, $timeSlice = null)
|
||||
{
|
||||
if ($timeSlice === null) {
|
||||
$timeSlice = floor(time() / 30);
|
||||
}
|
||||
|
||||
$secretkey = $this->_base32Decode($secret);
|
||||
|
||||
// Pack time into binary string
|
||||
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
|
||||
// Hash it with users secret key
|
||||
$hm = hash_hmac('SHA1', $time, $secretkey, true);
|
||||
// Use last nipple of result as index/offset
|
||||
$offset = ord(substr($hm, -1)) & 0x0F;
|
||||
// grab 4 bytes of the result
|
||||
$hashpart = substr($hm, $offset, 4);
|
||||
|
||||
// Unpak binary value
|
||||
$value = unpack('N', $hashpart);
|
||||
$value = $value[1];
|
||||
// Only 32 bits
|
||||
$value = $value & 0x7FFFFFFF;
|
||||
|
||||
$modulo = pow(10, $this->_codeLength);
|
||||
|
||||
return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QR-Code URL for image, from google charts.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $secret
|
||||
* @param string $title
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQRCode($name, $secret, $title = null)
|
||||
{
|
||||
$urlencoded = 'otpauth://totp/'.$name.'?secret='.$secret;
|
||||
if (isset($title)) {
|
||||
$urlencoded .= '&issuer='.urlencode($title);
|
||||
}
|
||||
return $urlencoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now.
|
||||
*
|
||||
* @param string $secret
|
||||
* @param string $code
|
||||
* @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
|
||||
* @param int|null $currentTimeSlice time slice if we want use other that time()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
|
||||
{
|
||||
if ($currentTimeSlice === null) {
|
||||
$currentTimeSlice = floor(time() / 30);
|
||||
}
|
||||
|
||||
if (strlen($code) != 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
|
||||
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
|
||||
if ($this->timingSafeEquals($calculatedCode, $code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the code length, should be >=6.
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* @return PHPGangsta_GoogleAuthenticator
|
||||
*/
|
||||
public function setCodeLength($length)
|
||||
{
|
||||
$this->_codeLength = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to decode base32.
|
||||
*
|
||||
* @param $secret
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function _base32Decode($secret)
|
||||
{
|
||||
if (empty($secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$base32chars = $this->_getBase32LookupTable();
|
||||
$base32charsFlipped = array_flip($base32chars);
|
||||
|
||||
$paddingCharCount = substr_count($secret, $base32chars[32]);
|
||||
$allowedValues = array(6, 4, 3, 1, 0);
|
||||
if (!in_array($paddingCharCount, $allowedValues)) {
|
||||
return false;
|
||||
}
|
||||
for ($i = 0; $i < 4; ++$i) {
|
||||
if ($paddingCharCount == $allowedValues[$i] &&
|
||||
substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$secret = str_replace('=', '', $secret);
|
||||
$secret = str_split($secret);
|
||||
$binaryString = '';
|
||||
for ($i = 0; $i < count($secret); $i = $i + 8) {
|
||||
$x = '';
|
||||
if (!in_array($secret[$i], $base32chars)) {
|
||||
return false;
|
||||
}
|
||||
for ($j = 0; $j < 8; ++$j) {
|
||||
$x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$eightBits = str_split($x, 8);
|
||||
for ($z = 0; $z < count($eightBits); ++$z) {
|
||||
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : '';
|
||||
}
|
||||
}
|
||||
|
||||
return $binaryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array with all 32 characters for decoding from/encoding to base32.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _getBase32LookupTable()
|
||||
{
|
||||
return array(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
|
||||
'=', // padding char
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A timing safe equals comparison
|
||||
* more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
|
||||
*
|
||||
* @param string $safeString The internal (safe) value to be checked
|
||||
* @param string $userString The user submitted (unsafe) value
|
||||
*
|
||||
* @return bool True if the two strings are identical
|
||||
*/
|
||||
private function timingSafeEquals($safeString, $userString)
|
||||
{
|
||||
if (function_exists('hash_equals')) {
|
||||
return hash_equals($safeString, $userString);
|
||||
}
|
||||
$safeLen = strlen($safeString);
|
||||
$userLen = strlen($userString);
|
||||
|
||||
if ($userLen != $safeLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
|
||||
for ($i = 0; $i < $userLen; ++$i) {
|
||||
$result |= (ord($safeString[$i]) ^ ord($userString[$i]));
|
||||
}
|
||||
|
||||
// They are only identical strings if $result is exactly 0...
|
||||
return $result === 0;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\expose\utils\wechat;
|
||||
|
||||
use app\expose\utils\wechat\modules\Event;
|
||||
use app\expose\utils\wechat\modules\Reply;
|
||||
use support\Log;
|
||||
|
||||
class OfficialAccount
|
||||
{
|
||||
use Reply;
|
||||
use Event;
|
||||
public function handle($request, $config)
|
||||
{
|
||||
$data = $this->getData($request, $config);
|
||||
if ($data['MsgType'] === 'event') {
|
||||
return $this->handleEvent($data);
|
||||
} else {
|
||||
return $this->replyText($data, 'hello world');
|
||||
}
|
||||
}
|
||||
public function checkSignature($request, $config)
|
||||
{
|
||||
$signature = $request->get("signature");
|
||||
$timestamp = $request->get("timestamp");
|
||||
$nonce = $request->get("nonce");
|
||||
$token = $config['token'];
|
||||
$tmpArr = [$token, $timestamp, $nonce];
|
||||
sort($tmpArr, SORT_STRING);
|
||||
$tmpStr = implode($tmpArr);
|
||||
$tmpStr = sha1($tmpStr);
|
||||
if ($tmpStr == $signature) {
|
||||
return true;
|
||||
}
|
||||
throw new \Exception('signature error');
|
||||
}
|
||||
private function getData($request, $config)
|
||||
{
|
||||
$xml = $request->rawBody();
|
||||
$json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
$data = json_decode(json_encode($json), true);
|
||||
if ($request->get('encrypt_type') === 'aes') {
|
||||
$Encrypt = base64_decode($data['Encrypt']);
|
||||
$aes_key = $config['aes_key'];
|
||||
$aes_key = base64_decode($aes_key . '=');
|
||||
$iv = substr($aes_key, 0, 16);
|
||||
$xml = openssl_decrypt($Encrypt, 'aes-256-cbc', $aes_key, OPENSSL_RAW_DATA, $iv);
|
||||
$filterHeader = substr($xml, 20);
|
||||
$xml = preg_replace('/' . $config['app_id'] . '/', '', $filterHeader);
|
||||
$json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
$data = json_decode(json_encode($json), true);
|
||||
return $data;
|
||||
} else {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user