初始化本地仓库

This commit is contained in:
2026-08-31 11:33:59 +08:00
commit 663f74b633
2390 changed files with 402918 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# Application Environment
APP_ENV=local
APP_DEBUG=false
# Database Configuration
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=madong_enterprise
DB_USERNAME=root
DB_PASSWORD=root
DB_PREFIX=ma_
# Redis Configuration
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
REDIS_DB=0
# Queue Redis Configuration
QUEUE_REDIS_HOST=redis://127.0.0.1:6379
QUEUE_REDIS_PORT=6379
QUEUE_REDIS_PASSWORD=null
QUEUE_REDIS_DB=0
QUEUE_REDIS_PREFIX=queue
# Cache Configuration
CACHE_CUSTOM_REDIS_HOST=127.0.0.1
CACHE_CUSTOM_REDIS_PORT=6379
CACHE_CUSTOM_REDIS_PASSWORD=null
CACHE_CUSTOM_REDIS_DB=0
CACHE_CUSTOM_REDIS_PREFIX=cache_custom
# Feature Toggles
APP_TASK_ENABLED=false # Timer switch
APP_TENANT_ENABLED=true # Tenant mode switch
APP_TENANT_AUTO_SELECT_FIRST=true # Tenant auto
CAPTCHA_ENABLED=false # Captcha switch
CAPTCHA_MODE=session # Captcha mode redis|session
RECYCLE_BIN_ENABLED=true # Recycle bin mode switch
# Other Configuration
+31
View File
@@ -0,0 +1,31 @@
/runtime
/.idea
/.vscode
/vendor
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache
install.lock
.rnd
# 忽略 public目录下除了install,favicon.ico,logo.png之外的内容
public/*
!public/install/
!public/favicon.ico
!public/logo.png
!public/upload/logo.png
# 忽略 plugin目录下除了example,demo除外
plugin/*
!plugin/example/
!plugin/demo/
+201
View File
@@ -0,0 +1,201 @@
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.
+325
View File
@@ -0,0 +1,325 @@
# madong-server
## 介绍
madong-server 是 5.x 版本的后端服务,基于 PHP Webman 框架开发的极速开发平台,为前端应用提供稳定、高效的 API 服务。
## 技术栈
| 技术 | 版本 | 说明 |
|------|-----------|------|
| PHP | 8.2 ~ 8.4 | 核心开发语言 |
| Webman | 2.2 | 高性能 PHP 框架 |
| MySQL | 8.0+ | 数据库 |
| Redis | 7.0+ | 缓存服务 |
| Composer | 2.x | 依赖管理工具 |
| Nginx/Apache | - | Web 服务器 |
## 项目结构
```
madong/ # 项目根目录
├── backend/ # 后端目录(当前目录)
│ ├── app/ # 应用目录
│ │ ├── adminapi/ # 管理后台 API(控制器、验证器等)
│ │ ├── api/ # 前台 API(控制器、验证器等)
│ │ ├── bootstrap/ # 启动引导文件
│ │ ├── command/ # 命令行工具
│ │ ├── dao/ # 数据访问层
│ │ ├── enum/ # 枚举类
│ │ ├── event/ # 事件定义
│ │ ├── install/ # 安装脚本
│ │ ├── listener/ # 事件监听器
│ │ ├── middleware/ # 中间件
│ │ ├── model/ # 模型
│ │ ├── process/ # 自定义进程
│ │ ├── queue/ # 队列任务
│ │ ├── schema/ # 数据结构定义
│ │ ├── scope/ # 作用域定义
│ │ ├── service/ # 业务服务层
│ │ └── functions.php # 全局函数
│ ├── config/ # 配置文件
│ │ ├── plugin/ # 插件配置
│ │ ├── route/ # 路由配置
│ │ └── *.php # 各类配置文件
│ ├── core/ # 核心框架
│ │ ├── base/ # 基础类
│ │ ├── cache/ # 缓存组件
│ │ ├── captcha/ # 验证码组件
│ │ ├── db/ # 数据库组件
│ │ ├── email/ # 邮件服务
│ │ ├── excel/ # Excel 导入导出
│ │ ├── exception/ # 异常处理
│ │ ├── generator/ # 代码生成器
│ │ ├── interface/ # 接口定义
│ │ ├── jwt/ # JWT 认证
│ │ ├── logger/ # 日志组件
│ │ ├── notify/ # 通知服务
│ │ ├── plugin/ # 插件核心
│ │ ├── route/ # 路由组件
│ │ ├── scheduler/ # 定时任务
│ │ ├── sms/ # 短信服务
│ │ ├── tool/ # 工具类
│ │ ├── trait/ # 特性类
│ │ ├── upload/ # 文件上传
│ │ ├── uuid/ # UUID 生成
│ │ └── functions.php # 核心函数
│ ├── plugin/ # 插件目录
│ ├── public/ # 静态资源
│ ├── resource/ # 资源文件
│ ├── runtime/ # 运行时文件
│ ├── support/ # 第三方支持库
│ ├── tests/ # 单元测试
│ ├── vendor/ # Composer 依赖包
│ ├── composer.json # Composer 配置
│ ├── composer.lock # 依赖锁定文件
│ ├── phinx.php # 数据库迁移配置
│ └── README.md # 项目说明文档
│
└── frontend/ # 前端目录(需单独下载)
├── admin/ # 管理后台前端
└── web/ # 前台前端
```
> **注意**:前后端分离部署,前端目录需单独下载或创建。
## 核心功能
- **用户认证**:基于 JWT 的身份验证机制
- **权限管理**:细粒度的权限控制体系
- **缓存服务**:集成 Redis 缓存,提升系统性能
- **邮件服务**:支持邮件发送功能
- **文件上传**:支持多种存储方式
- **国际化**:支持多语言配置
- **插件系统**:支持第三方扩展
- **命令行工具**:支持更多操作
- **SSE 实时推送**:支持 Server-Sent Events 实时进度反馈
- **服务层架构**:清晰的 DAO/Service 分层设计
- **代码生成器**:自动化生成控制器、模型、服务层代码
- **数据迁移**:支持数据库版本化管理
## 运行环境
- PHP 8.2 ~ 8.4
- MySQL 8.0+
- Redis 7.0+
- Nginx/Apache
- Composer 2.x+
## 安装部署
### 1. 环境准备
确保安装了以下软件:
- PHP 8.2 ~ 8.4
- MySQL 8.0+
- Redis 7.0+
- Nginx/Apache
- Composer 2.x+
- Git(用于下载前端资源)
### 2. 项目安装
```bash
# 进入后端目录
cd backend
# 安装后端依赖
composer install
```
### 3. 下载前端资源
**方式一:使用命令下载(推荐)**
```bash
# 进入后端目录
cd backend
# 下载所有前端代码(管理后台 + 前台)
php webman madong-download:frontend
# 仅下载管理后台
php webman madong-download:frontend --admin
# 仅下载前台
php webman madong-download:frontend --web
# 指定分支下载
php webman madong-download:frontend -b develop
# 强制覆盖更新
php webman madong-download:frontend -f
```
**方式二:手动下载**
如果没有安装 Git 或无法使用命令,可手动下载前端代码:
1. 访问 Gitee 仓库下载:
- 管理后台:https://gitee.com/motion-code/madong-vue
- 前台:https://gitee.com/motion-code/madong-nuxt
2. 解压后将代码放置到项目根目录:
```
madong/
├── backend/ # 后端代码
└── frontend/ # 前端代码
├── admin/ # 管理后台前端(来自 madong-vue)
└── web/ # 前台前端(来自 madong-nuxt)
```
### 4. 启动安装向导
完成以上步骤后,访问安装向导完成系统配置:
```
http://127.0.0.1:8001/install
```
### 5. 启动服务
```bash
# 进入后端目录
cd backend
# 开发环境启动
php start.php start
# 生产环境启动
php start.php start -d
```
服务默认运行在 `http://127.0.0.1:8001`
## 配置说明
### 数据库配置
修改 `config/database.php` 文件,配置数据库连接:
```php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'madong',
'username' => 'root',
'password' => '123456',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
],
],
];
```
### Redis 配置
修改 `config/redis.php` 文件,配置 Redis 连接:
```php
return [
'default' => [
'host' => '127.0.0.1',
'port' => 6379,
'auth' => '',
'db' => 0,
],
];
```
## API 文档
启动服务后,可通过以下方式访问 API 文档:
- 查看 `app/controller` 目录下的控制器文件
- 参考项目文档
## 开发指南
### 代码规范
- 遵循 PSR-4 自动加载规范
- 遵循 PSR-12 代码风格规范
- 使用 PHP 8.0+ 的特性
### 模块开发
1. 创建控制器:在 `app/adminapi`(管理后台)或 `app/api`(前台)目录下创建控制器
2. 创建服务层:在 `app/service` 目录下创建业务逻辑
3. 创建 DAO 层:在 `app/dao` 目录下创建数据访问对象
4. 创建模型:在 `app/model` 目录下创建 Eloquent 模型
5. 配置路由:在 `config/route/adminapi.php` 或 `config/route/api.php` 文件中添加路由
6. 测试:使用 Postman 或其他工具测试 API
## 系统演示
管理后台: http://demo.madong.tech
账号:admin
密码:123456
## 更新日志
### 5.x 版本
- 重构后端架构,基于 Webman 框架
- 实现 JWT 认证机制
- 集成 Redis 缓存服务
- 优化数据库操作
- 增强插件系统,支持第三方扩展
- 完善配置系统,支持多环境配置
- 新增国际化支持,支持多语言
- 优化错误处理和日志系统
- 新增文件上传功能,支持多种存储方式
- 完善命令行工具,支持更多操作
- 新增 SSE 实时推送,支持插件安装/卸载进度实时反馈
- 新增服务层(Service)和数据访问层(DAO)分层架构
- 新增代码生成器,提升开发效率
- 优化插件安装/卸载流程,统一由插件 Install.php 管理生命周期
- 新增枚举类规范,统一状态码和业务枚举定义
## 官方论坛
产品BUG、优化建议,欢迎社区反馈:http://www.madong.tech
## 如何贡献
非常欢迎你的加入![提一个 Issue](https://gitee.com/motion-code/madong/issues) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feature/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feature/xxxx`
5. 提交`pull request`
## Git 贡献提交规范
- 参考规范([Git](https://www.conventionalcommits.org/) [Vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 社区支持
- 官方文档:https://madong.tech
- 讨论交流:[点击链接加入群聊:690671595](https://qm.qq.com/q/dEfMqYL42c)
## 许可证
Apache License
Binary file not shown.
+297
View File
@@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi;
use app\model\system\Admin;
use app\scope\global\AccessPermissionScope;
use app\service\admin\system\AdminService;
use core\cache\CacheService;
use core\exception\handler\ForbiddenHttpException;
use core\jwt\JwtToken;
use core\logger\Logger;
use madong\swagger\attribute\Permission;
use support\Container;
final readonly class CurrentUser
{
// 缓存键前缀
public const CACHE_PREFIX = 'admin_user';
// 缓存过期时间(秒)
public const CACHE_EXPIRE = 3600;
private AdminService $service;
private CacheService $cache;
public function __construct(AdminService $service, CacheService $cache)
{
$this->service = $service;
$this->cache = $cache;
}
/**
* 生成管理员缓存键
* @param int|string $uid 管理员ID
* @return string 缓存键
*/
public static function generateCacheKey(int|string $uid): string
{
return self::CACHE_PREFIX . '_' . $uid;
}
public function admin(bool $toArray = false): null|Admin|array
{
if (!$this->getToken()) {
return null;
}
$uid = $this->id();
$cacheKey = self::generateCacheKey($uid);
// 使用缓存服务获取管理员信息
$admin = $this->cache->remember($cacheKey, function () use ($uid) {
return $this->service->get($uid, ['*'], [], '', [AccessPermissionScope::class]);
}, self::CACHE_EXPIRE);
return $toArray && $admin ? $admin->toArray() : $admin;
}
/**
* 清除管理员缓存
* @param int|string|null $uid 管理员ID,默认清除当前用户缓存
*/
public function clearCache(int|string|null $uid = null): void
{
$uid = $uid ?? $this->id();
if ($uid) {
$cacheKey = self::generateCacheKey($uid);
$this->cache->delete($cacheKey);
// 同时清除权限缓存
$permissionCacheKey = $cacheKey . '_permissions';
$this->cache->delete($permissionCacheKey);
}
}
public function refresh(): array
{
if (!$this->getToken()) {
Logger::debug("当前用户无有效 Token,无法刷新");
return [];
}
return (new JwtToken())->refresh()->toArray();
}
public function id(): int|string
{
$token = $this->getToken();
if (!$token) {
return 0;
}
$aid = (new JwtToken())->id();
if ($aid === null) {
return 0;
}
return $aid ?? 0;
}
public function isSuperAdmin(): bool
{
$admin = $this->admin();
return $admin && $admin->isSuperAdmin();
}
public function getToken(): ?string
{
$request = request();
if (empty($request)) {
return null;
}
$tokenName = config('core.jwt.app.token_name', 'Authorization');
$authorization = $request->header($tokenName);
if (empty($authorization) || $authorization === 'undefined') {
$authorization = $request->get('token');
}
if (!$authorization || $authorization === 'undefined') {
return null;
}
if (count(explode(' ', $authorization)) !== 2) {
return null;
}
[$type, $token] = explode(' ', $authorization);
if ($type !== 'Bearer') {
return null;
}
if (!$token || $token === 'undefined') {
return null;
}
return $token;
}
/**
* 生成新令牌
* @param array $userInfo 用户信息
* @param string $type 类型
* @return array 令牌信息
*/
public function generateToken(array $userInfo, string $type = 'admin'): array
{
$jwt = new JwtToken();
$tokenObj = $jwt->generate((string)$this->id(), $type, $userInfo);
return [
'access_token' => $tokenObj->accessToken,
'refresh_token' => $tokenObj->refreshToken,
'expires_in' => $tokenObj->expiresIn,
'expires_time' => time() + $tokenObj->expiresIn
];
}
/**
* 登出(将令牌加入黑名单)
* @param string|null $token 令牌,默认使用当前请求的令牌
* @return bool 是否成功
*/
public function logout(?string $token = null): bool
{
$token = $token ?? $this->getToken();
if (!$token) {
return false;
}
try {
(new JwtToken())->logout($token);
return true;
} catch (\Exception $e) {
Logger::error("登出失败: " . $e->getMessage());
return false;
}
}
/**
* 获取令牌负载信息
* @return array 负载信息
*/
public function getPayload(): array
{
try {
$jwt = new JwtToken();
// 使用 getPayloadFromRequest() 方法获取负载信息
$payload = $jwt->getPayloadFromRequest();
return $payload['ext'] ?? $payload;
} catch (\Exception $e) {
return [];
}
}
/**
* 获取用户所有菜单权限码
*
* @return array 权限码数组
*/
public function getPermissions(): array
{
$admin = $this->admin();
if (!$admin) {
return [];
}
if ($admin->isSuperAdmin()) {
return ['*']; // 超级管理员拥有所有权限
}
$uid = $this->id();
$cacheKey = self::generateCacheKey($uid) . '_permissions';
// 使用缓存服务获取权限信息
return $this->cache->remember($cacheKey, function () use ($admin) {
$permissions = [];
// 通过角色获取菜单权限码
$roles = $admin->roles()->with('menus')->get();
foreach ($roles as $role) {
foreach ($role->menus as $menu) {
if (!empty($menu->code)) {
$permissions[] = $menu->code;
}
}
}
return array_unique($permissions);
}, self::CACHE_EXPIRE);
}
/**
* 检查用户是否有权限
*
* @param string|array $codes 权限码,可以是字符串或数组
* @param string $operation 操作类型,支持 'and' 或 'or'
* @return bool 是否有权限
*/
public function hasPermission(string|array $codes, string $operation = 'and'): bool
{
// 如果是超级管理员,直接返回true
if ($this->isSuperAdmin()) {
return true;
}
$codes = is_array($codes) ? $codes : [$codes];
$userPermissions = $this->getPermissions();
// 如果有通配符权限,直接返回true
if (in_array('*', $userPermissions)) {
return true;
}
$operation = strtolower($operation);
if ($operation === Permission::OPERATION_AND) {
// AND模式:所有权限码都必须有
foreach ($codes as $code) {
if (!in_array($code, $userPermissions)) {
return false;
}
}
return true;
} else if ($operation === Permission::OPERATION_OR) {
// OR模式:至少有一个权限码
foreach ($codes as $code) {
if (in_array($code, $userPermissions)) {
return true;
}
}
return false;
}
throw new \InvalidArgumentException("不支持的操作类型: {$operation}");
}
/**
* 检查用户是否有权限,没有权限时抛出异常
*
* @param string|array $codes 权限码,可以是字符串或数组
* @param string $operation 操作类型,支持 'and' 或 'or'
* @throws ForbiddenHttpException 没有权限时抛出异常
*/
public function checkPermission(string|array $codes, string $operation = 'and'): void
{
if (!$this->hasPermission($codes, $operation)) {
$codes = is_array($codes) ? $codes : [$codes];
$codesStr = implode($operation === 'and' ? ',' : '或', $codes);
throw new ForbiddenHttpException("缺少权限: {$codesStr}");
}
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
/**
* This file is part of webman.
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use OpenApi\Annotations as OA;
use app\controller\OpenApiController;
use Webman\Route;
use WebmanTech\Swagger\DTO\ConfigOpenapiDocDTO;
use WebmanTech\Swagger\Integrations\RouteRegister;
use WebmanTech\Swagger\RouteAnnotation\Reader;
use app\adminapi\controller\terminal\TerminalSseController;
/**
* 注册admin APP路由
*/
Route::group('/adminapi', function () {
$openapiDocConfig = [
'format' => 'json',
'scan_path' => [
base_path('app/schema'), // 基础 schema
base_path('app/adminapi'),
base_path('app/install'),
],
'modify' => function (OA\OpenApi $openapi) {
$openapi->info->title = config('app.name') . ' API';
$openapi->info->version = '1.0.0';
$openapi->servers = [
new OA\Server([
'url' => '/adminapi',
'description' => request()->host(),
]),
];
/** @phpstan-ignore-next-line */
if (!$openapi->components instanceof OA\Components) {
$openapi->components = new OA\Components([]);
}
$openapi->components->securitySchemes = [
new OA\SecurityScheme([
'securityScheme' => 'api_key',
'type' => 'apiKey',
'name' => config('madong.jwt.app.token_name', 'Authorization'),
'in' => 'header',
]),
];
},
];
$openApiController = new OpenApiController();
Route::get('/openapi', static fn () => $openApiController->index('/adminapi/openapi.json'));
Route::get('/openapi.json', static fn () => $openApiController->document($openapiDocConfig));
$config = ConfigOpenapiDocDTO::fromConfig($openapiDocConfig);
RouteRegister::create()->register((new Reader())->getData($config->getScanSources()));
});
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller;
use core\base\BaseController;
/**
* 基类控制器继承的类
*
* @author Mr.April
* @since 1.0
*/
class Base extends BaseController
{
/**
* 初始化
*/
protected function initialize(): void
{
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,311 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\crontab;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\crontab\CrontabFormRequest;
use app\adminapi\schema\request\crontab\CrontabQueryRequest;
use app\adminapi\schema\response\crontab\CrontabResponse;
use app\adminapi\validate\crontab\CrontabValidate;
use app\enum\system\OperationResult;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\crontab\CrontabService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\helper\Arr;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class CrontabController extends Crud
{
public function __construct(CrontabService $service, CrontabValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/crontab/task',
summary: '列表',
tags: ['定时任务'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => CrontabQueryRequest::class,
]
)]
#[Permission(code: 'crontab:task:list')]
#[PageResponse(CrontabResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/crontab/task/{id}',
summary: '详情',
tags: ['定时任务'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'crontab:task:read')]
#[DataResponse(schema: CrontabResponse::class, example: [])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => 400]);
}
$dataArray = $data->toArray();
if (!empty($dataArray['cycle_rule'])) {
$cycleRule = $dataArray['cycle_rule'];
if (is_array($cycleRule)) {
$cycleRule = array_map(function ($value) {
if (is_string($value) && $value !== '' && is_numeric($value)) {
return intval($value);
}
return $value;
}, $cycleRule);
$dataArray = array_merge($dataArray, $cycleRule);
}
unset($dataArray['cycle_rule']);
}
return Json::success('ok', $dataArray);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/crontab/task',
summary: '添加',
tags: ['定时任务'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => CrontabFormRequest::class,
]
)]
#[Permission(code: 'crontab:task:create')]
#[DataResponse(schema: CrontabResponse::class, example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $this->inputFilter($request->all(), ['minute', 'hour', 'day', 'week', 'month', 'second']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/crontab/task/{id}',
summary: '删除',
tags: ['定时任务'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '任务ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'crontab:task:delete')]
#[DataResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $request->input('data', []);
$data = !empty($id) && $id !== '0' ? $id : $data;
if (empty($data)) {
throw new AdminException('参数错误');
}
$this->service->destroy($data);
return Json::success('操作成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/crontab/task',
summary: '批量删除',
tags: ['定时任务'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'crontab:task:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
try {
$data = $request->input('ids', []);
if (empty($data)) {
throw new AdminException('参数错误');
}
$this->service->destroy($data);
return Json::success('操作成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/crontab/task/{id}',
summary: '更新',
tags: ['定时任务'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => CrontabFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '任务ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'crontab:task:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->inputFilter($request->all(), ['minute', 'hour', 'day', 'week', 'month', 'second']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->update($id, $data);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/crontab/task/{id}/resume',
summary: '恢复',
tags: ['定时任务'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '任务ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'crontab:task:resume')]
#[SimpleResponse(schema: [], example: [])]
public function resume(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$this->service->resumeCrontab(Arr::normalize($id));
return Json::success('执行完成');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/crontab/task/{id}/pause',
summary: '暂停',
tags: ['定时任务'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '任务ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'crontab:task:pause')]
#[SimpleResponse(schema: [], example: [])]
public function pause(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$this->service->pauseCrontab($id);
return Json::success('执行完成');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/crontab/task/{id}/execute',
summary: '执行',
tags: ['定时任务'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'crontab:task:execute')]
#[SimpleResponse(schema: [], example: [])]
public function execute(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$result = $this->service->runOneTask(['id' => $id]);
if ($result['code'] == OperationResult::FAILURE->value) {
throw new AdminException('执行失败' . $result['log']);
}
return Json::success('执行完成', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage(), []);
}
}
}
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\crontab;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\crontab\CrontabLogQueryRequest;
use app\adminapi\schema\response\crontab\CrontabLogResponse;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\crontab\CrontabLogService;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class CrontabLogController extends Crud
{
public function __construct(CrontabLogService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/crontab/log',
summary: '列表',
tags: ['定时任务.日志'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => CrontabLogQueryRequest::class,
]
)]
#[Permission(code: 'crontab:log:list')]
#[PageResponse(schema: CrontabLogResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/crontab/log/{id}',
summary: '详情',
tags: ['定时任务.日志'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'crontab:log:read')]
#[DataResponse(schema: CrontabLogResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Delete(
path: '/crontab/log/{id}',
summary: '删除',
tags: ['定时任务.日志'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'crontab:log:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/crontab/log',
summary: '批量删除',
tags: ['定时任务.日志'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'crontab:log:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\dict;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\dict\DictFormRequest;
use app\adminapi\schema\request\dict\DictQueryRequest;
use app\adminapi\schema\response\system\DictResponse;
use app\adminapi\validate\system\DictValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\dict\DictService;
use app\service\core\enum\EnumService;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Container;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class DictController extends Crud
{
public function __construct(DictService $service, DictValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/dict/dict',
summary: '列表',
tags: ['字典管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictQueryRequest::class]
)]
#[Permission(code: 'dict:dict:list')]
#[PageResponse(schema: DictResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Post(
path: '/dict/dict',
summary: '新增',
tags: ['字典管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictFormRequest::class]
)]
#[Permission(code: 'dict:dict:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/dict/dict/{id}',
summary: '更新',
tags: ['字典管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictFormRequest::class]
)]
#[OA\Parameter(
name: 'id',
description: '字典ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'integer', default: 0),
)]
#[Permission(code: 'dict:dict:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/dict/dict/export',
summary: '导出',
tags: ['字典管理']
)]
#[Permission(code: 'dict:dict:list', description: '导出')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/dict/dict/{id}',
summary: '删除',
tags: ['字典管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'dict:dict:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/dict/dict',
summary: '批量删除',
tags: ['字典管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'dict:dict:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Get(
path: '/dict/dict/{id}',
summary: '详情',
tags: ['字典管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'dict:dict:read')]
#[SimpleResponse(schema: DictResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Get(
path: '/dict/dict/options/by-type',
summary: '根据字典类型获取字典项',
tags: ['字典管理'],
)]
#[OA\Parameter(
name: 'dict_type',
description: '字典类型',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string', default: 0),
)]
#[Permission(code: 'dict:dict:options')]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": [{"label": "是","value": 1,"color": "#4CAF50","ext": []},{"label": "否","value": 0,"color": "#FF5252","ext": []}]}')]
public function getByDictType(Request $request): \support\Response
{
try {
$dictType = $request->input('dict_type');
$service = Container::make(EnumService::class);
$data = $service->getEnumByCode($dictType);
if (empty($data)) {
$data = $this->service->findItemsByCode($dictType);
}
return Json::success('ok', $data);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/dict/dict/enum/list',
summary: '枚举字典列表',
tags: ['字典管理'],
)]
#[Permission(code: 'dict:dict:enum_list')]
#[SimpleResponse(schema: [], example: [
'name' => 'CategoryEnum',
'namespace' => 'app\\enum\\common\\CategoryEnum',
'category' => 'common',
'category_label' => '通用枚举',
'code' => 'common.categoryenum',
'options' => [
[
'label' => '通用枚举',
'value' => 'common',
'color' => 'blue',
'ext' => [],
],
[
'label' => '系统枚举',
'value' => 'system',
'color' => 'green',
'ext' => [],
],
[
'label' => '开发枚举',
'value' => 'dev',
'color' => 'orange',
'ext' => [],
],
[
'label' => '监控枚举',
'value' => 'monitor',
'color' => 'red',
'ext' => [],
],
[
'label' => '业务枚举',
'value' => 'business',
'color' => 'purple',
'ext' => [],
],
],
'count' => 5,
])]
public function enumDictList(Request $request): \support\Response
{
$page = $request->input('page', 1);
$limit = $request->input('limit', 10);
$search = $request->input('search', '');
$category = $request->input('category', '');
$service = Container::make(EnumService::class);
$result = $service->getEnumsWithPagination($page, $limit, $search, $category);
return Json::success('ok', $result);
}
}
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\dict;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\dict\DictItemFormRequest;
use app\adminapi\schema\request\dict\DictItemQueryRequest;
use app\adminapi\schema\response\system\DictItemResponse;
use app\adminapi\validate\system\DictItemValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\dict\DictItemService;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class DictItemController extends Crud
{
public function __construct(DictItemService $service, DictItemValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/dict/item',
summary: '列表',
tags: ['字典项管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictItemQueryRequest::class]
)]
#[Permission(code: 'dict:item:list')]
#[PageResponse(schema: DictItemResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/dict/item/{id}',
summary: '详情',
tags: ['字典项管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'dict:item:read')]
#[DataResponse(schema: DictItemResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/dict/item',
summary: '新增',
tags: ['字典项管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictItemFormRequest::class]
)]
#[Permission(code: 'dict:item:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/dict/item/{id}',
summary: '更新',
tags: ['字典项管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DictItemFormRequest::class]
)]
#[OA\Parameter(
name: 'id',
description: '字典项ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '1')
)]
#[Permission(code: 'dict:item:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/dict/item/export',
summary: '导出',
tags: ['字典项管理']
)]
#[Permission(code: 'dict:item:list', description: '导出')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/dict/item/{id}',
summary: '删除',
tags: ['字典项管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '字典项ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', default: '0', example: 0)
)]
#[Permission(code: 'dict:item:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/dict/item',
summary: '批量删除',
tags: ['字典项管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'dict:item:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\gateway;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\gateway\RateLimiterFormRequest;
use app\adminapi\schema\request\gateway\RateLimiterQueryRequest;
use app\adminapi\schema\response\gateway\RateLimiterResponse;
use app\adminapi\validate\gateway\RateLimiterValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\schema\response\Result;
use app\service\admin\gateway\RetaLimiterService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RateLimiterController extends Crud
{
public function __construct(RetaLimiterService $service, RateLimiterValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/gateway/limiter',
summary: '列表',
tags: ['限访规则'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateLimiterQueryRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:list')]
#[PageResponse(schema: RateLimiterResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/gateway/limiter/{id}',
summary: '详情',
tags: ['限访规则'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:read')]
#[DataResponse(schema: RateLimiterResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/gateway/limiter',
summary: '保存',
tags: ['限访规则'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateLimiterFormRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
//这里对入库的start_date end_date 转换时间戳 合并到start_time end_time
if (isset($data['start_date']) && !empty($data['start_date'])) {
$data['start_time'] = strtotime($data['start_date']);
unset($data['start_date']);
}
if (isset($data['end_date']) && !empty($data['end_date'])) {
$data['end_time'] = strtotime($data['end_date']);
unset($data['end_date']);
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
//添加后移除缓存
$this->service->cacheDriver()->delete(RetaLimiterService::CACHE_KEY);
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/gateway/limiter/{id}/change-status',
summary: '更新状态',
tags: ['限访规则'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:status')]
#[SimpleResponse(schema: [], example: [])]
public function changeStatus(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
$model = $this->service->getModel();
$primaryKey = $model->getKeyName();
if (!array_key_exists($primaryKey, $data)) {
throw new \Exception('参数异常缺少主键');
}
$targetModel = $model->findOrFail($data[$primaryKey]);
if (empty($targetModel)) {
throw new \Exception('资源不存在' . $primaryKey . '=', $data[$primaryKey]);
}
$targetModel->fill($data);
if (!$targetModel->save()) {
throw new \RuntimeException('数据保存失败');
}
//清空缓存
$this->service->cacheDriver()->delete(RetaLimiterService::CACHE_KEY);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/gateway/limiter/{id}',
summary: '更新',
tags: ['限访规则'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateLimiterFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '限访规则ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '247298887944531968')
)]
#[Permission(code: 'gateway:limiter:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
//这里对入库的start_date end_date 转换时间戳 合并到start_time end_time
if (isset($data['start_date']) && !empty($data['start_date'])) {
$data['start_time'] = strtotime($data['start_date']);
unset($data['start_date']);
}
if (isset($data['end_date']) && !empty($data['end_date'])) {
$data['end_time'] = strtotime($data['end_date']);
unset($data['end_date']);
}
$this->service->update($data['id'], $data);
//清空缓存
$this->service->cacheDriver()->delete(RetaLimiterService::CACHE_KEY);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/gateway/limiter/{id}',
summary: '删除',
tags: ['限访规则'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$data = $this->getDeleteIds($request);
if (empty($data)) {
throw new AdminException('参数错误');
}
$result = $this->service->transaction(function () use ($data) {
$data = is_array($data) ? $data : explode(',', $data);
$deletedIds = [];
foreach ($data as $id) {
$item = $this->service->get($id);
if (!$item) {
continue; // 如果找不到项,跳过
}
$item->delete();
$primaryKey = $item->getPk();
$deletedIds[] = $item->{$primaryKey};
}
return $deletedIds;
});
//清空缓存
$this->service->cacheDriver()->delete(RetaLimiterService::CACHE_KEY);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/gateway/limiter',
summary: '批量删除',
tags: ['限访规则'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'gateway:limiter:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
try {
$data = $this->getDeleteIds($request);
if (empty($data)) {
throw new AdminException('参数错误');
}
$result = $this->service->transaction(function () use ($data) {
$data = is_array($data) ? $data : explode(',', $data);
$deletedIds = [];
foreach ($data as $id) {
$item = $this->service->get($id);
if (!$item) {
continue; // 如果找不到项,跳过
}
$item->delete();
$primaryKey = $item->getPk();
$deletedIds[] = $item->{$primaryKey};
}
return $deletedIds;
});
//清空缓存
$this->service->cacheDriver()->delete(RetaLimiterService::CACHE_KEY);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\gateway;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\gateway\RateRestrictionsFormRequest;
use app\adminapi\schema\request\gateway\RateRestrictionsQueryRequest;
use app\adminapi\schema\response\gateway\RateRestrictionsResponse;
use app\adminapi\validate\gateway\RateRestrictionsValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\ChangeStatusRequest;
use app\schema\request\IdRequest;
use app\service\admin\gateway\RetaRestrictionsService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RateRestrictionsController extends Crud
{
public function __construct(RetaRestrictionsService $service, RateRestrictionsValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/gateway/blacklist',
summary: '列表',
tags: ['限访名单'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateRestrictionsQueryRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:list')]
#[PageResponse(schema: RateRestrictionsResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request); // TODO: Change the autogenerated stub
}
#[OA\Get(
path: '/gateway/blacklist/{id}',
summary: '详情',
tags: ['限访名单'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:read')]
#[SimpleResponse(schema: RateRestrictionsResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/gateway/blacklist',
summary: '保存',
tags: ['限访名单'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateRestrictionsFormRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:create')]
#[SimpleResponse(schema: RateRestrictionsResponse::class, example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
if (isset($data['start_date']) && !empty($data['start_date'])) {
$data['start_time'] = strtotime($data['start_date']);
unset($data['start_date']);
}
if (isset($data['end_date']) && !empty($data['end_date'])) {
$data['end_time'] = strtotime($data['end_date']);
unset($data['end_date']);
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
//添加后移除缓存
$this->service->cacheDriver()->delete(RetaRestrictionsService::CACHE_KEY);
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/gateway/blacklist/{id}/status',
summary: '切换状态',
tags: ['限访名单'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:status')]
#[SimpleResponse(schema: [], example: [])]
public function changeStatus(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
$model = $this->service->getModel();
$primaryKey = $model->getKeyName();
if (!array_key_exists($primaryKey, $data)) {
throw new \Exception('参数异常缺少主键');
}
$targetModel = $model->findOrFail($data[$primaryKey]);
if (empty($targetModel)) {
throw new \Exception('资源不存在' . $primaryKey . '=', $data[$primaryKey]);
}
$targetModel->fill($data);
if (!$targetModel->save()) {
throw new \RuntimeException('数据保存失败');
}
//清空缓存
$this->service->cacheDriver()->delete(RetaRestrictionsService::CACHE_KEY);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/gateway/blacklist/{id}',
summary: '更新',
tags: ['限访名单'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RateRestrictionsFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string'))]
#[Permission(code: 'gateway:blacklist:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$data = $this->insertInput($request);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->update($data['id'], $data);
//清空缓存
$this->service->cacheDriver()->delete(RetaRestrictionsService::CACHE_KEY);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/gateway/blacklist/{id}',
summary: '删除',
tags: ['限访名单'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$id = $request->route->param('id'); // 获取路由地址 id从
$data = $request->input('data', []);
$data = !empty($id) && $id !== '0' ? $id : $data;
if (empty($data)) {
throw new AdminException('参数错误');
}
$result = $this->service->transaction(function () use ($data) {
$data = is_array($data) ? $data : explode(',', $data);
$deletedIds = [];
foreach ($data as $id) {
$item = $this->service->get($id);
if (!$item) {
continue; // 如果找不到项,跳过
}
$item->delete();
$primaryKey = $item->getPk();
$deletedIds[] = $item->{$primaryKey};
}
return $deletedIds;
});
//清空缓存
$this->service->cacheDriver()->delete(RetaRestrictionsService::CACHE_KEY);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/gateway/blacklist',
summary: '批量删除',
tags: ['限访名单'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'gateway:blacklist:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
try {
$data = $request->input('ids', []);
if (empty($data)) {
throw new AdminException('参数错误');
}
$result = $this->service->transaction(function () use ($data) {
$data = is_array($data) ? $data : explode(',', $data);
$deletedIds = [];
foreach ($data as $id) {
$item = $this->service->get($id);
if (!$item) {
continue; // 如果找不到项,跳过
}
$item->delete();
$primaryKey = $item->getPk();
$deletedIds[] = $item->{$primaryKey};
}
return $deletedIds;
});
//清空缓存
$this->service->cacheDriver()->delete(RetaRestrictionsService::CACHE_KEY);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\generator;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\generator\GeneratorValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\generator\GeneratorService;
use app\service\admin\generator\GeneratorTableService;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\helper\Arr;
use support\Container;
use support\Request;
use support\annotation\Middleware;
use OpenApi\Attributes as OA;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class GeneratorController extends Crud
{
public function __construct(GeneratorService $service, GeneratorValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/generator/code',
summary: '列表',
tags: ['代码生成器']
)]
#[Permission(code: 'generator:code:list')]
#[PageResponse(schema: [], example: [[
'id' => '273048217217138688',
'table_name' => 'sys_admin_post',
'table_content' => '用户与岗位关联表',
'module_name' => 'sys_admin_post',
'class_name' => 'sys_admin_post',
'edit_type' => 1,
'plugin_name' => '',
'order_type' => 0,
'parent_menu' => '',
'relations' => null,
'push_sync_count' => 0,
'created_at' => '2026-01-23T03:16:07.000000Z',
'updated_at' => '2026-01-23T03:16:07.000000Z',
'created_date' => '2026-01-23 11:16:07',
'updated_date' => '2026-01-23 11:16:07',
]])]
public function index(Request $request): \support\Response
{
return parent::index($request); // TODO: Change the autogenerated stub
}
#[OA\Get(
path: '/generator/code/table-list',
summary: '数据库表-列表',
tags: ['代码生成器']
)]
#[Permission(code: 'generator:code:list')]
#[DataResponse(schema: [], example: [])]
public function tableList(Request $request): \support\Response
{
$data = $request->all();
$page = $request->input('page', 1);
$limit = $request->input('limit', 10);
$result = $this->service->tableList($data, (int)$page, (int)$limit);
return Json::success($result);
}
#[OA\Post(
path: '/generator/code',
summary: '生成代码',
tags: ['代码生成器']
)]
#[Permission(code: 'generator:code:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $request->input('table', []);
foreach ($data as $item) {
if (!$this->validate->scene('store')->check($item)) {
return Json::fail($this->validate->getError());
}
}
foreach ($data as $item) {
$this->service->store($item);
}
return Json::success();
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/generator/code/{id}',
summary: '删除',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$this->service->destroy(Arr::normalize($id));
return Json::success();
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/generator/code',
summary: '批量删除',
tags: ['代码生成器'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'generator:code:delete')]
#[SimpleResponse(schema: [], example: [])]
public function remove(Request $request): \support\Response
{
try {
$data = $request->input('ids', []);
$this->service->destroy(Arr::normalize($data));
return Json::success();
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/generator/code/{id}',
summary: '详情',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:read')]
#[DataResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$service = Container::make(GeneratorTableService::class);
$data = $service->getDetail($id);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => 400]);
}
return Json::success('ok', $data->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Put(
path: '/generator/code/{id}',
summary: '更新',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:update')]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $request->all();
if (!$this->validate->scene('update')->check($data)) {
return Json::fail($this->validate->getError());
}
$this->service->update($id, $data);
return Json::success();
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/generator/code/{id}/preview',
summary: '预览',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:preview')]
public function preview(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$result = $this->service->preview(['id' => $id]);
return Json::success($result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/generator/code/{id}/download',
summary: '下载',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:download')]
public function download(Request $request)
{
try {
$id = $request->route->param('id');
$result = $this->service->download($id);
return response()->download($result['file_path'], $result['file_name']);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/generator/code/{id}/deploy',
summary: '部署',
tags: ['代码生成器'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'generator:code:deploy')]
public function deploy(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$result = $this->service->deploy(['id' => $id]);
return Json::success($result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\lang;
use app\adminapi\controller\Base;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\service\core\lang\TranslationService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(AccessTokenMiddleware::class, OperationMiddleware::class)]
final class LangController extends Base
{
public function __construct(TranslationService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/lang/supported',
summary: '获取支持的语言列表',
tags: ['语言'],
responses: [
new OA\Response(
response: 200,
description: '成功',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(type: 'string')
)
)
]
)]
#[SimpleResponse(example: ['code' => 0,'message' => 'success','data' => []])]
public function supported(Request $request): Response
{
try {
$languages = $this->service->getSupportedLanguages();
return json(['code' => 200, 'msg' => 'success', 'data' => $languages]);
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
#[OA\Get(
path: '/lang',
summary: '获取语言包列表',
tags: ['语言'],
parameters: [
new OA\Parameter(
name: 'language',
description: '语言类型(如en|zh_CN等)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string', example: 'zh_CN')
),
new OA\Parameter(
name: 'file',
description: '翻译文件名称',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string', example: 'messages')
),
new OA\Parameter(
name: 'keyword',
description: '搜索关键词(支持key和翻译内容)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string')
),
new OA\Parameter(
name: 'page',
description: '页码',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 1)
),
new OA\Parameter(
name: 'limit',
description: '每页数量',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 10)
)
]
)]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): Response
{
try {
$language = $request->get('language');
$file = $request->get('file');
$keyword = $request->get('keyword');
$page = (int)$request->get('page', 1);
$limit = (int)$request->get('limit', 10);
$result = $this->service->getList($language, $file, $keyword, $page, $limit);
return json(['code' => 200, 'msg' => 'success', 'data' => $result]);
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
#[OA\Get(
path: '/lang/files',
summary: '获取翻译文件列表',
tags: ['语言'],
responses: [
new OA\Response(
response: 200,
description: '成功',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(type: 'string')
)
)
]
)]
#[SimpleResponse(schema:[],example: [])]
public function files(Request $request): Response
{
try {
$files = $this->service->getFileList();
return json(['code' => 200, 'msg' => 'success', 'data' => $files]);
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
#[OA\Get(
path: '/lang/statistics',
summary: '获取语言包统计信息',
tags: ['语言'],
)]
#[SimpleResponse(schema:[],example: [])]
public function statistics(Request $request): Response
{
try {
$stats = $this->service->getStatistics();
return json(['code' => 200, 'msg' => 'success', 'data' => $stats]);
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
/**
* @throws \core\exception\handler\AdminException
*/
#[OA\Get(
path: '/lang/translate',
summary: '根据key获取翻译内容',
tags: ['语言'],
parameters: [
new OA\Parameter(
name: 'key',
description: '翻译key(格式:文件.键,如:common.operation.success)',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string', example: 'common.operation.success')
),
new OA\Parameter(
name: 'language',
description: '语言类型(如en|zh_CN等)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string', example: 'zh_CN')
)
]
)]
#[SimpleResponse(schema:[],example: [])]
public function translate(Request $request): Response
{
try {
$key = $request->get('key');
$language = $request->get('language');
$parameters = $request->get('parameters', []);
if(empty($language)){
$language = locale();
}
// 如果parameters是字符串,尝试解析为JSON
if (is_string($parameters)) {
$parameters = json_decode($parameters, true) ?: [];
}
$translation = $this->service->getTranslationByKey($key, $language, $parameters);
if ($translation === null) {
return json(['code' => 404, 'msg' => 'Translation key not found', 'data' => $key]);
}
return Json::success('common.operation.success',$translation);
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
}
@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\login;
use app\adminapi\controller\Crud;
use app\adminapi\schema\request\login\LoginRequest;
use app\adminapi\schema\request\ThirdPartyLoginRequest;
use app\service\admin\system\AdminService;
use core\cache\CacheService;
use core\captcha\Captcha;
use core\exception\handler\AdminException;
use core\jwt\JwtToken;
use core\tool\Json;
use core\tool\RSAService;
use core\uuid\UUIDGenerator;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\Container;
use support\Request;
final class LoginController extends Crud
{
public function __construct(AdminService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/login/get-captcha-open-flag',
summary: '验证码开启状态',
tags: ['无需授权'],
)]
#[SimpleResponse(schema: ['flag' => true], example:[])]
public function getCaptchaOpenFlag(Request $request): \support\Response
{
try {
// 生成密钥对
$cache = Container::make(CacheService::class, []);
$keyId = md5(UUIDGenerator::generate());
$keys = RSAService::generateKeys();
// 存储私钥到缓存,用于解密密码
$cache->set("rsa_private_key_$keyId", $keys['private'], 60); // 5分钟过期
return Json::success('ok', ['flag' => config('core.captcha.app.enable', false), 'key_id' => $keyId, 'public_key' => $keys['public']]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/login/captcha',
summary: '验证码',
tags: ['无需授权'],
)]
#[SimpleResponse(schema: [], example:[])]
public function captcha(Request $request): \support\Response
{
try {
$captcha = new Captcha();
$type = $request->input('type', 'admin-login');
$result = $captcha->captcha($request, $type);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/login/send-sms',
summary: '手机验证码',
tags: ['无需授权']
)]
#[OA\Parameter(
name: 'mobile_phone',
description: '手机号码',
in: 'query',
required: true,
schema: new OA\Schema(type: 'integer', default: 18888888888)
)]
#[SimpleResponse(schema:[],example: '{"code": 185841,"uid":18888888888}}')]
public function sendSms(Request $request): \support\Response
{
// 1.0生成验证码
$captcha = new Captcha();
$result = $captcha->generateSmsCaptcha($request);
// 2.0发送验证码到手机
// 3.0 存储验证码到 Redis,设置过期时间(如 5 分钟)
return Json::success('验证码发送成功', $result);
}
#[OA\Post(
path: '/login/login',
summary: '登录',
tags: ['无需授权']
)]
#[RequestBody(
description: '请求参数',
required: true,
content: new OA\JsonContent(ref: LoginRequest::class)
)]
#[SimpleResponse(schema:[],example: '{"token_type": "Bearer","expires_in": 7200,"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYWRvbmcudGVjaCIsImF1ZCI6Im1hZG9uZy50ZWNoIiwiaWF0IjoxNzYyNDExMjY5LCJuYmYiOjE3NjI0MTEyNjksImV4cCI6MTc2MjQxODQ2OSwiZXh0ZW5kIjp7ImlkIjoiMiIsInVzZXJfbmFtZSI6ImFkbWluIiwicmVhbF9uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4Iiwibmlja19uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4IiwiaXNfc3VwZXIiOjEsImlzX3RlbmFudF9hZG1pbiI6MSwibW9iaWxlX3Bob25lIjpudWxsLCJlbWFpbCI6bnVsbCwiYXZhdGFyIjoiL3VwbG9hZC80MmY4NmI5YjM2Nzk0ZmI5ZTM4MDkxN2MyNTFmNmQ4MS5wbmciLCJzaWduZWQiOm51bGwsImRhc2hib2FyZCI6bnVsbCwiZGVwdF9pZCI6bnVsbCwiZW5hYmxlZCI6MSwibG9naW5faXAiOiIxMjAuNDEuMjQ2LjIyNSIsImxvZ2luX3RpbWUiOjE3NjI0MTEyNjksInVwZGF0ZWRfYXQiOiIyMDI1LTExLTA2VDA2OjQxOjA5LjAwMDAwMFoiLCJzZXgiOjAsImJpcnRoZGF5IjpudWxsLCJ0ZWwiOm51bGwsImlzX2xvY2tlZCI6MCwiZGVwdHMiOltdLCJwb3N0cyI6W10sInRlbmFudHMiOlt7ImlkIjoiMSIsImRiX25hbWUiOiJtYWRvbmdfYWRtaW5fc2FhcyIsImNvZGUiOiJwbGF0Zm9ybSIsInR5cGUiOjAsImNvbnRhY3RfcGVyc29uIjoiYWRtaW4iLCJjb250YWN0X3Bob25lIjoiMTg4ODg4ODg4ODgiLCJjb21wYW55X25hbWUiOiJ4eHh4IFx1NjcwOVx1OTY1MFx1NTE2Y1x1NTNmOCIsImxpY2Vuc2VfbnVtYmVyIjoiIiwiYWRkcmVzcyI6Ilx1NGUyZFx1NTZmZCIsImRlc2NyaXB0aW9uIjoiXHU1MTg1XHU3ZjZlXHU4ZDI2XHU1M2Y3IiwiZG9tYWluIjoiaHR0cHM6Ly93d3cubWFkb25nLnRlY2giLCJlbmFibGVkIjoxLCJpc29sYXRpb25fbW9kZSI6MiwiaXNfZGVmYXVsdCI6MSwiZXhwaXJlZF9hdCI6bnVsbCwiZGVsZXRlZF9hdCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjUtMTAtMDNUMDM6MjM6NDcuMDAwMDAwWiIsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOm51bGwsInVwZGF0ZWRfYXQiOm51bGwsImNyZWF0ZWRfZGF0ZSI6IjIwMjUtMTAtMDMgMTE6MjM6NDciLCJ1cGRhdGVkX2RhdGUiOm51bGwsImV4cGlyZWRfZGF0ZSI6bnVsbCwicGl2b3QiOnsiYWRtaW5faWQiOjIsInRlbmFudF9pZCI6MSwiY3JlYXRlZF9kYXRlIjpudWxsLCJ1cGRhdGVkX2RhdGUiOm51bGx9fV0sImNhc2JpbiI6W119fQ.rCaHoiI1gU7xyoJazt3V5JQrS2guV34PH-JjkWnceE4","refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYWRvbmcudGVjaCIsImF1ZCI6Im1hZG9uZy50ZWNoIiwiaWF0IjoxNzYyNDExMjY5LCJuYmYiOjE3NjI0MTEyNjksImV4cCI6MTc2MzAxNjA2OSwiZXh0ZW5kIjp7ImlkIjoiMiIsInVzZXJfbmFtZSI6ImFkbWluIiwicmVhbF9uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4Iiwibmlja19uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4IiwiaXNfc3VwZXIiOjEsImlzX3RlbmFudF9hZG1pbiI6MSwibW9iaWxlX3Bob25lIjpudWxsLCJlbWFpbCI6bnVsbCwiYXZhdGFyIjoiL3VwbG9hZC80MmY4NmI5YjM2Nzk0ZmI5ZTM4MDkxN2MyNTFmNmQ4MS5wbmciLCJzaWduZWQiOm51bGwsImRhc2hib2FyZCI6bnVsbCwiZGVwdF9pZCI6bnVsbCwiZW5hYmxlZCI6MSwibG9naW5faXAiOiIxMjAuNDEuMjQ2LjIyNSIsImxvZ2luX3RpbWUiOjE3NjI0MTEyNjksInVwZGF0ZWRfYXQiOiIyMDI1LTExLTA2VDA2OjQxOjA5LjAwMDAwMFoiLCJzZXgiOjAsImJpcnRoZGF5IjpudWxsLCJ0ZWwiOm51bGwsImlzX2xvY2tlZCI6MCwiZGVwdHMiOltdLCJwb3N0cyI6W10sInRlbmFudHMiOlt7ImlkIjoiMSIsImRiX25hbWUiOiJtYWRvbmdfYWRtaW5fc2FhcyIsImNvZGUiOiJwbGF0Zm9ybSIsInR5cGUiOjAsImNvbnRhY3RfcGVyc29uIjoiYWRtaW4iLCJjb250YWN0X3Bob25lIjoiMTg4ODg4ODg4ODgiLCJjb21wYW55X25hbWUiOiJ4eHh4IFx1NjcwOVx1OTY1MFx1NTE2Y1x1NTNmOCIsImxpY2Vuc2VfbnVtYmVyIjoiIiwiYWRkcmVzcyI6Ilx1NGUyZFx1NTZmZCIsImRlc2NyaXB0aW9uIjoiXHU1MTg1XHU3ZjZlXHU4ZDI2XHU1M2Y3IiwiZG9tYWluIjoiaHR0cHM6Ly93d3cubWFkb25nLnRlY2giLCJlbmFibGVkIjoxLCJpc29sYXRpb25fbW9kZSI6MiwiaXNfZGVmYXVsdCI6MSwiZXhwaXJlZF9hdCI6bnVsbCwiZGVsZXRlZF9hdCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjUtMTAtMDNUMDM6MjM6NDcuMDAwMDAwWiIsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOm51bGwsInVwZGF0ZWRfYXQiOm51bGwsImNyZWF0ZWRfZGF0ZSI6IjIwMjUtMTAtMDMgMTE6MjM6NDciLCJ1cGRhdGVkX2RhdGUiOm51bGwsImV4cGlyZWRfZGF0ZSI6bnVsbCwicGl2b3QiOnsiYWRtaW5faWQiOjIsInRlbmFudF9pZCI6MSwiY3JlYXRlZF9kYXRlIjpudWxsLCJ1cGRhdGVkX2RhdGUiOm51bGx9fV0sImNhc2JpbiI6W119fQ.IgLkKhjjsgjAsjS4qReg51WpzwjiuU27_tbqTpUuD3M","client_id": "127.0.0.1-690c430548ecd5.55813566","expires_time": 1762418469}')]
public function login(Request $request): \support\Response
{
try {
$username = $request->input('user_name');
$password = $request->input('password', '');
$phone = $request->input('mobile_phone', '');
$code = $request->input('code', '');
$uuid = $request->input('uuid', '');
$type = $request->input('type', 'admin');
$grantType = $request->input('grant_type', 'deafult');//refresh_token sms default 可以自行定义拓展登录方式
$keyId = $request->input('key_id', '');//获取公钥Id
$captcha = new Captcha();
if (config('core.captcha.app.enable') && $grantType === 'default') {
if (!$captcha->check($uuid, $code)) {
throw new AdminException('图片验证码错误!');
}
}
if ($grantType == 'sms' && !empty($phone)) {
if (!$captcha->check($phone, $code)) {
throw new AdminException('手机验证码错误!');
}
$info = $this->service->get(['mobile_phone' => $phone]);
if (empty($info)) {
throw new AdminException('当前手机未绑定用户!');
}
$username = $info->getData('user_name');
}
$data = $this->service->login($username, $password, $type, $grantType, ['key_id' => $keyId ?? '']);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/login/logout',
summary: '注销',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['无需授权'],
)]
#[OA\Parameter(
name: 'token',
description: '令牌(可选,也可通过Authorization头传递)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema:[],example: [])]
public function logout(Request $request): \support\Response
{
// 1. 获取token
$token = $request->header(config('core.jwt.app.token_name', 'Authorization')) ?: $request->get('token');
// 2. 快速验证无效token情况
if (empty($token) || $token === 'undefined') {
return Json::success('ok', []);
}
// 3. 处理Bearer Token格式
if (str_starts_with($token, 'Bearer ')) {
$token = substr($token, 7); // 移除"Bearer "前缀
if (empty($token) || $token === 'undefined') {
return Json::success('ok', []);
}
}
// 4. 退出登录
(new JwtToken())->logout($token);
return Json::success('ok', []);
}
#[OA\Get(
path: '/login/auth/public-key',
summary: '生成RSA密钥对',
tags: ['无需授权'],
)]
#[SimpleResponse(schema:[],example: [])]
public function generateRSAKeyPair(Request $request): \support\Response
{
try {
// 生成密钥对
$cache = Container::make(CacheService::class, []);
$keyId = md5(UUIDGenerator::generate());
$keys = RSAService::generateKeys();
// 存储私钥到缓存,用于解密密码
$cache->set("rsa_private_key_$keyId", $keys['private'], 60); // 5分钟过期
return Json::success('ok', ['public_key' => $keys['public'], 'key_id' => $keyId]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/login/third-party/login',
summary: '第三方测试登录接口(免RSA)',
tags: ['无需授权']
)]
#[RequestBody(
description: '请求参数',
required: true,
content: new OA\JsonContent(ref: ThirdPartyLoginRequest::class)
)]
#[SimpleResponse(schema:[],example: '{"code": 0,"msg": "ok","data": {"token_type": "Bearer","expires_in": 7200,"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYWRvbmcudGVjaCIsImF1ZCI6Im1hZG9uZy50ZWNoIiwiaWF0IjoxNzYyNDExMjY5LCJuYmYiOjE3NjI0MTEyNjksImV4cCI6MTc2MjQxODQ2OSwiZXh0ZW5kIjp7ImlkIjoiMiIsInVzZXJfbmFtZSI6ImFkbWluIiwicmVhbF9uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4Iiwibmlja19uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4IiwiaXNfc3VwZXIiOjEsImlzX3RlbmFudF9hZG1pbiI6MSwibW9iaWxlX3Bob25lIjpudWxsLCJlbWFpbCI6bnVsbCwiYXZhdGFyIjoiL3VwbG9hZC80MmY4NmI5YjM2Nzk0ZmI5ZTM4MDkxN2MyNTFmNmQ4MS5wbmciLCJzaWduZWQiOm51bGwsImRhc2hib2FyZCI6bnVsbCwiZGVwdF9pZCI6bnVsbCwiZW5hYmxlZCI6MSwibG9naW5faXAiOiIxMjAuNDEuMjQ2LjIyNSIsImxvZ2luX3RpbWUiOjE3NjI0MTEyNjksInVwZGF0ZWRfYXQiOiIyMDI1LTExLTA2VDA2OjQxOjA5LjAwMDAwMFoiLCJzZXgiOjAsImJpcnRoZGF5IjpudWxsLCJ0ZWwiOm51bGwsImlzX2xvY2tlZCI6MCwiZGVwdHMiOltdLCJwb3N0cyI6W10sInRlbmFudHMiOlt7ImlkIjoiMSIsImRiX25hbWUiOiJtYWRvbmdfYWRtaW5fc2FhcyIsImNvZGUiOiJwbGF0Zm9ybSIsInR5cGUiOjAsImNvbnRhY3RfcGVyc29uIjoiYWRtaW4iLCJjb250YWN0X3Bob25lIjoiMTg4ODg4ODg4ODgiLCJjb21wYW55X25hbWUiOiJ4eHh4IFx1NjcwOVx1OTY1MFx1NTE2Y1x1NTNmOCIsImxpY2Vuc2VfbnVtYmVyIjoiIiwiYWRkcmVzcyI6Ilx1NGUyZFx1NTZmZCIsImRlc2NyaXB0aW9uIjoiXHU1MTg1XHU3ZjZlXHU4ZDI2XHU1M2Y3IiwiZG9tYWluIjoiaHR0cHM6Ly93d3cubWFkb25nLnRlY2giLCJlbmFibGVkIjoxLCJpc29sYXRpb25fbW9kZSI6MiwiaXNfZGVmYXVsdCI6MSwiZXhwaXJlZF9hdCI6bnVsbCwiZGVsZXRlZF9hdCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjUtMTAtMDNUMDM6MjM6NDcuMDAwMDAwWiIsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOm51bGwsInVwZGF0ZWRfYXQiOm51bGwsImNyZWF0ZWRfZGF0ZSI6IjIwMjUtMTAtMDMgMTE6MjM6NDciLCJ1cGRhdGVkX2RhdGUiOm51bGwsImV4cGlyZWRfZGF0ZSI6bnVsbCwicGl2b3QiOnsiYWRtaW5faWQiOjIsInRlbmFudF9pZCI6MSwiY3JlYXRlZF9kYXRlIjpudWxsLCJ1cGRhdGVkX2RhdGUiOm51bGx9fV0sImNhc2JpbiI6W119fQ.rCaHoiI1gU7xyoJazt3V5JQrS2guV34PH-JjkWnceE4","refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYWRvbmcudGVjaCIsImF1ZCI6Im1hZG9uZy50ZWNoIiwiaWF0IjoxNzYyNDExMjY5LCJuYmYiOjE3NjI0MTEyNjksImV4cCI6MTc2MzAxNjA2OSwiZXh0ZW5kIjp7ImlkIjoiMiIsInVzZXJfbmFtZSI6ImFkbWluIiwicmVhbF9uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4Iiwibmlja19uYW1lIjoiXHU4ZDg1XHU3ZWE3XHU3YmExXHU3NDA2XHU1NDU4IiwiaXNfc3VwZXIiOjEsImlzX3RlbmFudF9hZG1pbiI6MSwibW9iaWxlX3Bob25lIjpudWxsLCJlbWFpbCI6bnVsbCwiYXZhdGFyIjoiL3VwbG9hZC80MmY4NmI5YjM2Nzk0ZmI5ZTM4MDkxN2MyNTFmNmQ4MS5wbmciLCJzaWduZWQiOm51bGwsImRhc2hib2FyZCI6bnVsbCwiZGVwdF9pZCI6bnVsbCwiZW5hYmxlZCI6MSwibG9naW5faXAiOiIxMjAuNDEuMjQ2LjIyNSIsImxvZ2luX3RpbWUiOjE3NjI0MTEyNjksInVwZGF0ZWRfYXQiOiIyMDI1LTExLTA2VDA2OjQxOjA5LjAwMDAwMFoiLCJzZXgiOjAsImJpcnRoZGF5IjpudWxsLCJ0ZWwiOm51bGwsImlzX2xvY2tlZCI6MCwiZGVwdHMiOltdLCJwb3N0cyI6W10sInRlbmFudHMiOlt7ImlkIjoiMSIsImRiX25hbWUiOiJtYWRvbmdfYWRtaW5fc2FhcyIsImNvZGUiOiJwbGF0Zm9ybSIsInR5cGUiOjAsImNvbnRhY3RfcGVyc29uIjoiYWRtaW4iLCJjb250YWN0X3Bob25lIjoiMTg4ODg4ODg4ODgiLCJjb21wYW55X25hbWUiOiJ4eHh4IFx1NjcwOVx1OTY1MFx1NTE2Y1x1NTNmOCIsImxpY2Vuc2VfbnVtYmVyIjoiIiwiYWRkcmVzcyI6Ilx1NGUyZFx1NTZmZCIsImRlc2NyaXB0aW9uIjoiXHU1MTg1XHU3ZjZlXHU4ZDI2XHU1M2Y3IiwiZG9tYWluIjoiaHR0cHM6Ly93d3cubWFkb25nLnRlY2giLCJlbmFibGVkIjoxLCJpc29sYXRpb25fbW9kZSI6MiwiaXNfZGVmYXVsdCI6MSwiZXhwaXJlZF9hdCI6bnVsbCwiZGVsZXRlZF9hdCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjUtMTAtMDNUMDM6MjM6NDcuMDAwMDAwWiIsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOm51bGwsInVwZGF0ZWRfYXQiOm51bGwsImNyZWF0ZWRfZGF0ZSI6IjIwMjUtMTAtMDMgMTE6MjM6NDciLCJ1cGRhdGVkX2RhdGUiOm51bGwsImV4cGlyZWRfZGF0ZSI6bnVsbCwicGl2b3QiOnsiYWRtaW5faWQiOjIsInRlbmFudF9pZCI6MSwiY3JlYXRlZF9kYXRlIjpudWxsLCJ1cGRhdGVkX2RhdGUiOm51bGx9fV0sImNhc2JpbiI6W119fQ.IgLkKhjjsgjAsjS4qReg51WpzwjiuU27_tbqTpUuD3M","client_id": "127.0.0.1-690c430548ecd5.55813566","expires_time": 1762418469}}')]
public function thirdPartyLogin(Request $request): \support\Response
{
try {
$acctId = $request->get('acct_id');
$appId = $request->get('app_id');
$appSecret = $request->get('app_secret');
$userName = $request->get('user_name');
$data = $this->service->thirdPartyLogin($acctId, $appId, $appSecret, $userName);
return Json::success('登录成功', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\logs;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\logs\LoginLogQueryRequest;
use app\adminapi\schema\response\system\LoginLogResponse;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\logs\LoginLogService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class LoginLogController extends Crud
{
public function __construct(LoginLogService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/logs/login',
summary: '列表',
tags: ['日志管理.登录'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => LoginLogQueryRequest::class,
]
)]
#[Permission(code: 'logs:login:list')]
#[PageResponse(schema: LoginLogResponse::class, example: [])]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, ['account' => function ($query) {
$query->select(['id', 'user_name', 'real_name']);
}], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/logs/login/{id}',
summary: '详情',
tags: ['日志管理.登录'],
)]
#[OA\Parameter(
name: 'id',
description: '日志ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'logs:login:read')]
#[SimpleResponse(schema: LoginLogResponse::class, example: [])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id,['*'],['account' => function ($query) {
$query->select(['id', 'user_name', 'real_name']);
}]);
if (empty($data)) {
throw new AdminException('数据未找到');
}
return Json::success('ok', $data->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Delete(
path: '/logs/login/{id}',
summary: '删除',
tags: ['日志管理.登录'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'logs:login:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/logs/login',
summary: '批量删除',
tags: ['日志管理.登录'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'logs:login:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\logs;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\logs\OperateLogQueryRequest;
use app\adminapi\schema\response\system\OperateLogResponse;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\logs\OperateLogService;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class OperateLogController extends Crud
{
public function __construct(OperateLogService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/logs/operate',
summary: '列表',
tags: ['日志管理.操作'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => OperateLogQueryRequest::class,
]
)]
#[Permission(code: 'logs:operate:list')]
#[PageResponse(schema: OperateLogResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/logs/operate/{id}',
summary: '详情',
tags: ['日志管理.操作'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'logs:operate:read')]
#[SimpleResponse(schema: OperateLogResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Delete(
path: '/logs/operate/{id}',
summary: '删除',
tags: ['日志管理.操作'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', default: '123456789012345678'),
)]
#[Permission(code: 'logs:operate:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/logs/operate',
summary: '批量删除',
tags: ['日志管理.操作'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'logs:operate:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\member;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\member\MemberLevelValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\member\MemberLevelService;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class LevelController extends Crud
{
public function __construct(MemberLevelService $service, MemberLevelValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/member/level',
summary: '列表',
tags: ['会员等级'],
parameters: [
new OA\Parameter(name: "name", description: "等级名称", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "status", description: "状态", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
]
)]
#[Permission("member:level:list")]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Post(
path: '/member/level',
summary: '创建',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "普通会员"),
new OA\Property(property: "min_points", type: "integer", example: 0),
new OA\Property(property: "max_points", type: "integer", example: 999),
new OA\Property(property: "discount", type: "number", example: 1),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "sort", type: "integer", example: 100),
])
),
tags: ['会员等级'],
)]
#[Permission("member:level:create")]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/member/level/{id}',
summary: '更新',
tags: ['会员等级'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:level:update")]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/member/level/{id}',
summary: '删除会员等级',
tags: ['会员等级'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:level:delete")]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/member/level',
summary: '批量删除',
tags: ['会员等级'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class],
)]
#[Permission("member:level:delete")]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Get(
path: '/member/level/{id}',
summary: '详情',
tags: ['会员等级'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:level:show")]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
}
@@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\member;
use app\adminapi\controller\Crud;
use app\adminapi\CurrentUser;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\member\MemberAuthValidate;
use app\service\admin\member\MemberAuthService;
use app\service\admin\member\MemberService;
use app\service\admin\member\MemberTagPermissionService;
use app\service\admin\member\MemberTagRelationService;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\Container;
use support\Request;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class MemberAuthController extends Crud
{
public function __construct(MemberAuthService $service, MemberAuthValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/member/auth/permissions',
summary: '权限菜单-包含所有',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:permissions')]
#[SimpleResponse(schema: [], example: [])]
public function getPermissions(Request $request): \support\Response
{
try {
$format = input('format', 'table_tree');
$collection = $this->service->getAllMenus(true);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$items = $collection->toArray();
return call_user_func([$this, $format_function], $collection, count($items));
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/member/auth/{id}/tag-permissions',
summary: '权限菜单-包含所有',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:tag_permissions')]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": []}')]
public function getPermissionsByTag(Request $request, $id): \support\Response
{
try {
$format = input('format', 'tree');
$collection = $this->service->getMenusByMemberTags($id, true);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$items = $collection->toArray();
return call_user_func([$this, $format_function], $items, count($items));
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/member/auth/perm-code',
summary: '获取用户-权限码',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:perm_code')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => ['admin']])]
public function getUserCodes(Request $request): \support\Response
{
try {
/** @var CurrentUser $currUser */
$currUser = Container::make(CurrentUser::class);
$data = $this->service->getCodesByUserRoles($currUser);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/member/auth/tag-menu-ids',
summary: '通过角色ID获取权限ID集合',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:role_menu_ids')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function tagMenuIds(Request $request): \support\Response
{
try {
$tagId = $request->input('tag_id');
$result = $this->service->getMenusByTagId($tagId, true);
if (empty($result)) {
return Json::Success([]);
}
$data = [];
foreach ($result as $item) {
$data[] = $item->id;
}
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/member/auth/save-tag-menu',
summary: '保存角色菜单关系',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:role_menu')]
#[SimpleResponse( example: '{"code": 0,"msg": "ok"}')]
public function saveTagMenuRelation(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var MemberTagPermissionService $systemRoleMenuService */
$systemRoleMenuService = Container::make(MemberTagPermissionService::class);
$systemRoleMenuService->save($data);
return Json::success('success');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/member/auth/user-list-by-tag-id',
summary: '获取角色-关联用户列表',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[OA\Parameter(
name: 'tag_id',
description: '标签ID',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string'),
)]
#[Permission(code: 'member:auth:tag_user_list')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function getUsersByTagId(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request, [
'allowed_fields' => ['username', 'nickname', 'email', 'mobile', 'tag_id'], // 使用 null 表示不限制字段,允许所有模型字段
'skip_fields' => [],
// 添加自定义参数会自动合并到 where 中
'custom_fields' => [],
]);
/** @var MemberService $systemUserService */
$systemUserService = Container::make(MemberService::class);
$data = $systemUserService->getUsersListByTagId($where, $field, $page, $limit);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/member/auth/save-user-tag',
summary: '保存用户-关联标签',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:save_user_tag')]
#[SimpleResponse(example: '{"code": 0,"msg": "success"}')]
public function saveUserTags(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var MemberTagRelationService $memberTagRelationService */
$memberTagRelationService = Container::make(MemberTagRelationService::class);
$memberTagRelationService->saveUserTags($data);
return Json::success('success');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/member/auth/remove-user-tag',
summary: '移除用户-关联角色',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[Permission(code: 'member:auth:remove_user_tag')]
#[SimpleResponse(example: '{"code": 0,"msg": "ok"}')]
public function removeUserRole(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var MemberTagRelationService $MemberTagRelationService */
$MemberTagRelationService = Container::make(MemberTagRelationService::class);
$MemberTagRelationService->removeUserTag($data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/member/auth/user-list-exclude-tag-id',
summary: '获取用户列表-排除指定角色id',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关-会员'],
)]
#[OA\Parameter(
name: 'tag_id',
description: '标签ID',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string'),
)]
#[Permission(code: 'member:auth:user_list_exclude_tag')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function getUsersExcludingTag(Request $request): \support\Response
{
try {
// 自定义参数
$tagId = $request->input('tag_id');
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request, [
'allowed_fields' => null, // 使用 null 表示不限制字段,允许所有模型字段
'skip_fields' => ['token', 'deleted_at'],
// 添加自定义参数 tag_id,会自动合并到 where 中
'custom_fields' => [
'tag_id' => $tagId,
],
]);
/** @var MemberService $memberUserService */
$memberUserService = Container::make(MemberService::class);
$data = $memberUserService->getUsersExcludingTag($where, $field, $page, $limit);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
}
@@ -0,0 +1,409 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\member;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\member\MemberValidate;
use app\api\CurrentMember;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\member\MemberService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Container;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class MemberController extends Crud
{
public function __construct(MemberService $service, MemberValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/member/user',
summary: '会员用户列表',
tags: ['会员用户'],
parameters: [
new OA\Parameter(name: "username", description: "用户名", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "nickname", description: "昵称", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "phone", description: "手机号", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "email", description: "邮箱", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "level_id", description: "等级ID", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "status", description: "状态", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "获取成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "total", type: "integer", example: 10),
new OA\Property(property: "list", type: "array", items: new OA\Items(properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "username", type: "string", example: "testuser"),
new OA\Property(property: "nickname", type: "string", example: "测试用户"),
new OA\Property(property: "phone", type: "string", example: "13800138000"),
new OA\Property(property: "email", type: "string", example: "test@example.com"),
new OA\Property(property: "avatar", type: "string", example: ""),
new OA\Property(property: "level_id", type: "integer", example: 1),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "create_time", type: "integer", example: 1640995200),
])),
], type: "object"),
])
),
]
)]
#[Permission("member:user:list")]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, ['level', 'tags'], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/member/user/{id}',
summary: '会员用户详情',
tags: ['会员用户'],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "获取成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "username", type: "string", example: "testuser"),
new OA\Property(property: "nickname", type: "string", example: "测试用户"),
new OA\Property(property: "phone", type: "string", example: "13800138000"),
new OA\Property(property: "email", type: "string", example: "test@example.com"),
new OA\Property(property: "avatar", type: "string", example: ""),
new OA\Property(property: "level_id", type: "integer", example: 1),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "create_time", type: "integer", example: 1640995200),
], type: "object"),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:user:read")]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/member/user',
summary: '创建',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "username", type: "string", example: "testuser"),
new OA\Property(property: "nickname", type: "string", example: "测试用户"),
new OA\Property(property: "phone", type: "string", example: "13800138000"),
new OA\Property(property: "email", type: "string", example: "test@example.com"),
new OA\Property(property: "password", type: "string", example: "123456"),
new OA\Property(property: "avatar", type: "string", example: ""),
new OA\Property(property: "level_id", type: "integer", example: 1),
new OA\Property(property: "status", type: "integer", example: 1),
])
),
tags: ['会员用户'],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "创建成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
],
)]
#[Permission("member:user:create")]
#[SimpleResponse( example: '{"code":0,"msg":"created","id":1}')]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/member/user/{id}',
summary: '更新',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "nickname", type: "string", example: "新昵称"),
new OA\Property(property: "phone", type: "string", example: "13800138000"),
new OA\Property(property: "email", type: "string", example: "test@example.com"),
new OA\Property(property: "avatar", type: "string", example: ""),
new OA\Property(property: "level_id", type: "integer", example: 2),
new OA\Property(property: "status", type: "integer", example: 1),
])
),
tags: ['会员用户'],
parameters: [
new OA\Parameter(name: "id", description: "用户ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "更新成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
]
)]
#[Permission("member:user:update")]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/member/user/{id}',
summary: '删除',
tags: ['会员用户'],
parameters: [
new OA\Parameter(name: "id", description: "用户ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "删除成功"),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:user:delete")]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/member/user',
summary: '批量删除',
tags: ['会员用户'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class],
)]
#[Permission("member:user:delete")]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/member/user/{id}/reset-password',
summary: '重置密码',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "password", type: "string", example: "123456"),
])
),
tags: ['会员用户'],
parameters: [
new OA\Parameter(name: "id", description: "用户ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "密码重置成功"),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:user:reset_password")]
public function resetPassword(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->insertInput($request);
if (empty($data['password'])) {
return Json::fail('密码不能为空');
}
$this->service->update($id, ['password' => $data['password']]);
return Json::success('密码重置成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/member/user/{id}/assign-tags',
summary: '分配标签',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "tags", type: "array", example: [1, 2, 3]),
])
),
tags: ['会员用户'],
)]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
#[Permission("member:user:assign_tags")]
public function assignTags(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $request->input('tags', []);
if (empty($data)) {
return Json::fail('标签不能为空');
}
$this->service->assignTags($id, $data);
return Json::success('标签分配成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/member/user/{id}/adjust-points',
summary: '调整会员积分',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "points", type: "integer", example: 100),
])
),
tags: ['会员用户'],
parameters: [
new OA\Parameter(name: "id", description: "用户ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
#[Permission("member:user:adjust_points")]
public function adjustPoints(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$points = $request->input('points', 0);
$this->service->adjustPoints($id, (int)$points);
return Json::success('积分调整成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 格式化下拉列表
*
* @param $items
*
* @return \support\Response
*/
public function formatSelect($items): \support\Response
{
$formatted_items = [];
foreach ($items as $item) {
$formatted_items[] = [
'label' => $item->nickname ?? $item->id,
'value' => $item->id,
];
}
return Json::success('ok', $formatted_items);
}
// #[OA\Get(
// path: '/member/user/statistics',
// summary: '会员统计',
// tags: ['会员用户'],
// responses: [
// new OA\Response(
// response: 200,
// description: "成功",
// content: new OA\JsonContent(properties: [
// new OA\Property(property: "code", type: "integer", example: 0),
// new OA\Property(property: "msg", type: "string", example: "获取成功"),
// new OA\Property(property: "data", properties: [
// new OA\Property(property: "total", type: "integer", example: 100),
// new OA\Property(property: "active", type: "integer", example: 80),
// new OA\Property(property: "inactive", type: "integer", example: 20),
// ], type: "object"),
// ])
// ),
// ]
// )]
// #[Permission("member:user:statistics")]
public function statistics(Request $request): \support\Response
{
try {
$result = $this->service->getStatistics();
return Json::success('获取成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\member;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\member\MemberPointsValidate;
use app\service\admin\member\MemberPointsService;
use madong\swagger\attribute\Permission;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class PointsController extends Crud
{
public function __construct(MemberPointsService $service, MemberPointsValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/member/points',
summary: '列表',
tags: ['会员积分'],
parameters: [
new OA\Parameter(name: "member_id", description: "会员ID", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "type", description: "类型", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "start_time", description: "开始时间", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "end_time", description: "结束时间", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
]
)]
#[Permission("member:points:list")]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, ['member'], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/member/points/{id}',
summary: '详情',
tags: ['会员积分'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:points:read")]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id, ['*'], ['member'], 'created_at', []);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => 400]);
}
return Json::success('ok', $data->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/member/points',
summary: '新增',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "member_id", type: "integer", example: 1),
new OA\Property(property: "points", type: "integer", example: 100),
new OA\Property(property: "type", type: "integer", example: 1),
new OA\Property(property: "source", type: "string", example: "admin"),
new OA\Property(property: "remark", type: "string", example: "管理员操作"),
new OA\Property(property: "operator", type: "string", example: "admin"),
])
),
tags: ['会员积分']
)]
#[Permission("member:points:create")]
public function store(Request $request): \support\Response
{
try {
$data = $request->all();
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->operate($data);
return Json::success('操作成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
}
@@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\member;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\member\MemberTagValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\member\MemberTagService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class TagController extends Crud
{
public function __construct(MemberTagService $service, MemberTagValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/member/tag',
summary: '列表',
tags: ['会员标签'],
parameters: [
new OA\Parameter(name: "name", description: "标签名称", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "status", description: "状态", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
]
)]
#[Permission("member:tag:list")]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/member/tag/{id}',
summary: '详情',
tags: ['会员标签'],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "获取成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "name", type: "string", example: "活跃会员"),
new OA\Property(property: "color", type: "string", example: "#409EFF"),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "sort", type: "integer", example: 100),
new OA\Property(property: "create_time", type: "integer", example: 1640995200),
], type: "object"),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:tag:read")]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/member/tag',
summary: '新增',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "活跃会员"),
new OA\Property(property: "color", type: "string", example: "#409EFF"),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "sort", type: "integer", example: 100),
])
),
tags: ['会员标签']
)]
#[Permission("member:tag:create")]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/member/tag/{id}',
summary: '更新',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "活跃会员"),
new OA\Property(property: "color", type: "string", example: "#409EFF"),
new OA\Property(property: "status", type: "integer", example: 1),
new OA\Property(property: "sort", type: "integer", example: 100),
])
),
tags: ['会员标签'],
parameters: [
new OA\Parameter(name: "id", description: "标签ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "更新成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
]
)]
#[Permission("member:tag:update")]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/member/tag/{id}',
summary: '删除',
tags: ['会员标签'],
parameters: [
new OA\Parameter(name: "id", description: "标签ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("member:tag:delete")]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/member/tag',
summary: '批量删除',
tags: ['会员标签'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class],
)]
#[Permission("member:tag:delete")]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/member/{id}/permissions',
summary: '为标签分配权限',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "permissions", type: "array", items: new OA\Items(type: "string"), example: ["developer", "api_access"]),
])
),
tags: ['会员标签'],
parameters: [
new OA\Parameter(name: "id", description: "标签ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
)]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
#[Permission("member:tag:assign_permissions")]
public function assignPermissions(Request $request, $id): \support\Response
{
try {
$permissions = $request->input('permissions', []);
$this->service->assignPermissions($id, $permissions);
return Json::success('权限分配成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/member/{id}/permissions',
summary: '获取标签权限',
tags: ['会员标签'],
parameters: [
new OA\Parameter(name: "id", description: "标签ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
)]
#[Permission("member:tag:show_permissions")]
public function getPermissions(Request $request, $id): \support\Response
{
try {
$tag = $this->service->get($id, ['*'], ['permissions']);
if (!$tag) {
return Json::fail('标签不存在');
}
return Json::success('ok', $tag->permissions->toArray());
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/member/all-with-permissions',
summary: '获取所有启用的标签(含权限)',
tags: ['会员标签'],
)]
#[Permission("member:tag:all")]
public function getAllWithPermissions(Request $request): \support\Response
{
try {
$result = $this->service->getEnabledTagsWithPermissions();
return Json::success('ok', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/member/available-permissions',
summary: '获取所有可用的权限码(从菜单表)',
tags: ['会员标签'],
)]
#[Permission("member:tag:all")]
public function getAvailablePermissions(Request $request): \support\Response
{
try {
$result = $this->service->getAvailablePermissions();
return Json::success('ok', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,254 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\message;
use app\adminapi\CurrentUser;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\message\MessageFormRequest;
use app\adminapi\schema\request\message\MessageQueryRequest;
use app\adminapi\schema\response\system\MessageResponse;
use app\adminapi\validate\system\MessageValidate;
use app\enum\system\MessageStatus;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\message\MessageService;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\Request;
use support\annotation\Middleware;
use support\Container;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class MessageController extends Crud
{
public function __construct(MessageService $service, MessageValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/message',
summary: '列表',
tags: ['消息管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MessageQueryRequest::class,
]
)]
#[Permission(code: 'network:message:list')]
#[PageResponse(MessageResponse::class)]
public function index(Request $request): \support\Response
{
$uid = Container::make(CurrentUser::class)->id();
$data = $request->get();
$data['receiver_id'] = $uid;//消息接收人
$request->setGet($data);
return parent::index($request);
}
#[OA\Get(
path: '/message/{id}',
summary: '详情',
tags: ['消息管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'network:message:read')]
#[SimpleResponse(schema: MessageResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/message',
summary: '创建',
tags: ['消息管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MessageFormRequest::class,
]
)]
#[Permission(code: 'network:message:create')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/message/{id}',
summary: '更新',
tags: ['消息管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MessageFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '消息ID(雪花ID)',
in: 'path',
required: true,
schema: new OA\Schema(
type: 'string',
example: '123456789012345678'
)
)]
#[Permission(code: 'network:message:update')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/message/export',
summary: '导出',
tags: ['消息管理']
)]
#[Permission(code: 'network:message:list', description: '导出')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/message/{id}',
summary: '删除',
tags: ['消息管理'],
parameters: [
new OA\Parameter(
name: 'id',
description: '消息ID',
in: 'path',
required: true,
schema: new OA\Schema(
type: 'string',
default: 0,
example: '123456789012345678',
)
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'network:message:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/message',
summary: '批量删除',
tags: ['消息管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'network:message:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/message/{id}/update-read',
summary: '消息状态标记',
tags: ['消息管理'],
)]
#[OA\Parameter(
name: 'id',
description: '消息ID',
in: 'path',
required: true,
schema: new OA\Schema(
type: 'string',
default: 0,
example: '123456789012345678',
)
)]
#[RequestBody(required: true, content: new OA\JsonContent(
required: ['status'],
properties: [
new OA\Property(
property: 'status',
description: '消息状态(0=未读,1=已读)',
type: 'integer',
enum: [0, 1],
example: 1
),
]
))]
#[Permission(code: 'network:message:update_read')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function updateRead(Request $request): \support\Response
{
try {
$data = $request->all();
if ($this->validate) {
$this->validate->scene('update-read')->check($data);
}
$actionMap = [
MessageStatus::READ->value => 'isRead',
MessageStatus::UNREAD->value => 'isUnread',
];
if (!isset($actionMap[$data['status']])) {
throw new \InvalidArgumentException('Invalid message status');
}
$this->service->{$actionMap[$data['status']]}(
$data['id'] ?? throw new \InvalidArgumentException('Missing message ID')
);
return Json::success('ok');
} catch (\Throwable $e) {
// 统一异常处理(生产环境建议记录日志)
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/message/notify-on-first-login-to-all',
summary: '推送广播&消息',
tags: ['消息管理'],
)]
#[Permission(code: 'network:message:push')]
#[AllowAnonymous(requireToken: true,requirePermission: false)]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function notifyOnFirstLoginToAll(Request $request): \support\Response
{
try {
$id = Container::make(CurrentUser::class)->id();
$this->service->notifyOnFirstLoginToAll($id);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\monitor;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\service\admin\monitor\OnlineUserService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Container;
use support\Request;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class OnlineUserController extends Crud
{
#[OA\Get(
path: '/monitor/online-user',
summary: '在线用户列表',
tags: ['系统监控']
)]
#[Permission(code: 'monitor:online_user:list')]
#[SimpleResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
try {
/** @var OnlineUserService $service */
$service = Container::get(OnlineUserService::class);
return Json::success('ok', $service->list($request->all()));
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Delete(
path: '/monitor/online-user/{jti}',
summary: '踢下线',
tags: ['系统监控']
)]
#[OA\Parameter(
name: 'jti',
description: '会话JTI',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[Permission(code: 'monitor:online_user:kickout')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$jti = (string)$request->route->param('jti', '');
if ($jti === '') {
return Json::fail('会话JTI不能为空');
}
/** @var OnlineUserService $service */
$service = Container::get(OnlineUserService::class);
if (!$service->kickout($jti)) {
return Json::fail('会话不存在或已失效');
}
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/monitor/online-user/{jti}/revoke',
summary: '撤销Token',
tags: ['系统监控']
)]
#[OA\Parameter(
name: 'jti',
description: '会话JTI',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[Permission(code: 'monitor:online_user:revoke')]
#[SimpleResponse(schema: [], example: [])]
public function revoke(Request $request): \support\Response
{
return $this->destroy($request);
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\monitor;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\response\monitor\RedisMonitorResponse;
use core\monitor\ServerMonitor;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Container;
use support\Request;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RedisController extends Crud
{
#[OA\Get(
path: '/monitor/redis',
summary: 'Redis监控',
tags: ['系统监控']
)]
#[Permission(code: 'monitor:redis:read')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => RedisMonitorResponse::class])]
public function index(Request $request): \support\Response
{
try {
/** @var ServerMonitor $service */
$service = Container::get(ServerMonitor::class);
$data = $service->getRedisInfo();
return Json::success('ok', $data);
} catch (\Exception $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\monitor;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\response\monitor\ServerMonitorResponse;
use core\monitor\ServerMonitor;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Container;
use support\Request;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ServerController extends Crud
{
#[OA\Get(
path: '/monitor/server',
summary: '性能监控',
tags: ['系统监控']
)]
#[Permission(code: 'monitor:server:read')]
#[SimpleResponse(schema:ServerMonitorResponse::class, example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function index(Request $request): \support\Response
{
try {
/** @var ServerMonitor $service */
$service = Container::get(ServerMonitor::class);
$serverInfo = [
'cpu' => $service->getCpuInfo(),
'memory' => $service->getMemoryInfo(),
'disk' => $service->getDiskInfo(),
'php' => $service->getPhpInfo(),
];
return Json::success('ok', $serverInfo);
} catch (\Exception $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
}
@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\notice;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\notice\NoticeFormRequest;
use app\adminapi\schema\request\notice\NoticeQueryRequest;
use app\adminapi\schema\response\system\NoticeResponse;
use app\adminapi\validate\system\NoticeValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\notice\NoticeService;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use Webman\RedisQueue\Client;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class NoticeController extends Crud
{
// 注入服务和验证器
public function __construct(NoticeService $service, NoticeValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/notice',
summary: '列表',
tags: ['公告管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => NoticeQueryRequest::class,
]
)]
#[Permission(code: 'network:notice:list')]
#[PageResponse(schema:NoticeResponse::class,example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request); // 调用父类Crud的列表方法
}
#[OA\Get(
path: '/notice/{id}',
summary: '详情',
tags: ['公告管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'network:notice:read')]
#[SimpleResponse(schema: NoticeResponse::class, example: ['code' => 0, 'message' => 'success', 'data' => []])] // 详情响应
public function show(Request $request): \support\Response
{
return parent::show($request); // 调用父类Crud的详情方法
}
#[OA\Post(
path: '/notice',
summary: '创建',
tags: ['公告管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => NoticeFormRequest::class,
]
)]
#[Permission(code: 'network:notice:create')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
// 4. 更新公告
#[OA\Put(
path: '/notice/{id}',
summary: '更新',
tags: ['公告管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => NoticeFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
)]
#[Permission(code: 'network:notice:update')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])] // 更新响应
public function update(Request $request): \support\Response
{
return parent::update($request); // 调用父类Crud的更新方法
}
#[OA\Post(
path: '/notice/export',
summary: '导出',
tags: ['公告管理']
)]
#[Permission(code: 'network:notice:list', description: '导出')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/notice/{id}',
summary: '删除',
tags: ['公告管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'network:notice:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])] // 删除响应
public function destroy(Request $request): \support\Response
{
return parent::destroy($request); // 调用父类Crud的删除方法
}
#[OA\Delete(
path: '/notice',
summary: '批量删除',
tags: ['公告管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'network:notice:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Post(
path: '/notice/publish',
summary: '发布',
tags: ['公告管理'],
)]
#[Permission(code: 'network:notice:publish')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function publish(Request $request): \support\Response
{
try {
$id = $request->input('id', null);
$model = $this->service->get($id);
$queue = 'admin-announcement-push';
Client::send($queue, $model->makeVisible('tenant_id')->toArray(), 0);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\org;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\org\DeptFormRequest;
use app\adminapi\schema\request\org\DeptQueryRequest;
use app\adminapi\schema\response\system\DeptResponse;
use app\adminapi\validate\system\DeptValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\org\DeptService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class DeptController extends Crud
{
public function __construct(DeptService $service, DeptValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/org/dept',
summary: '列表',
tags: ['部门管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => DeptQueryRequest::class]
)]
#[Permission(code: 'org:dept:list')]
#[PageResponse(DeptResponse::class)]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Post(
path: '/org/dept',
summary: '创建',
tags: ['部门管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => DeptFormRequest::class,
]
)]
#[Permission(code: 'org:dept:create')]
#[SimpleResponse(DeptResponse::class, example: '{"code":0,"msg":"created","id":1}')]
public function store(Request $request): \support\Response
{
try {
$data = $this->inputFilter($request->all(), ['leader_id_list']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/org/dept/{id}',
summary: '删除',
tags: ['部门管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'org:dept:delete')]
#[SimpleResponse( example: '{"code":0,"msg":"success","data":null}')]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/org/dept',
summary: '批量删除',
tags: ['部门管理'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'org:dept:delete')]
#[SimpleResponse(example: '{"code":0,"msg":"success","data":null}')]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/org/dept/{id}',
summary: '更新',
tags: ['部门管理'],
)]
#[Permission(code: 'org:dept:update')]
#[SimpleResponse( example: '{"code":0,"msg":"success","data":null}')]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->inputFilter($request->all(), ['leader_id_list']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->update($id, $data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/org/dept/{id}',
summary: '详情',
tags: ['部门管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'org:dept:read')]
#[SimpleResponse(DeptResponse::class, example: '{"code":0,"msg":"success","data":null}')]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
}
@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\org;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\org\PostFormRequest;
use app\adminapi\schema\request\org\PostQueryRequest;
use app\adminapi\schema\response\system\PostResponse;
use app\adminapi\validate\system\PostValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\org\PostService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class PostController extends Crud
{
public function __construct(PostService $service, PostValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/org/post',
summary: '列表',
tags: ['岗位管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => PostQueryRequest::class,
]
)]
#[Permission(code: 'org:post:list')]
#[PageResponse(PostResponse::class, example: '{"code":0,"msg":"success","data":[]}')] // 分页响应关联岗位Schema
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, ['dept']);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/org/post/{id}',
summary: '详情',
tags: ['岗位管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'org:post:read')]
#[SimpleResponse(PostResponse::class, example: [])] // 详情响应
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id,['*'],['dept']);
if (empty($data)) {
throw new AdminException('数据未找到');
}
return Json::success('ok', $data->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/org/post',
summary: '创建',
tags: ['岗位管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => PostFormRequest::class,
]
)]
#[Permission(code: 'org:post:create')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/org/post/{id}',
summary: '更新',
tags: ['岗位管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => PostFormRequest::class,
]
)]
#[Permission(code: 'org:post:update')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/org/post/{id}',
summary: '删除',
tags: ['岗位管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
)]
#[Permission(code: 'org:post:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/org/post',
summary: '批量删除',
tags: ['岗位管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
)]
#[Permission(code: 'org:post:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\plugin;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\plugin\ApplicationAuthFormRequest;
use app\adminapi\schema\request\plugin\ApplicationAuthQueryRequest;
use app\adminapi\schema\response\plugin\ApplicationAuthResponse;
use app\adminapi\validate\plugin\ApplicationAuthValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\plugin\ApplicationAuthService;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ApplicationAuthController extends Crud
{
public function __construct(ApplicationAuthService $service, ApplicationAuthValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/plugin/application-auth',
summary: '列表',
tags: ['应用授权'],
x: [SchemaConstants::X_SCHEMA_REQUEST => ApplicationAuthQueryRequest::class]
)]
#[Permission(code: 'plugin:application-auth:list')]
#[PageResponse(schema: ApplicationAuthResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/plugin/application-auth/{id}',
summary: '详情',
tags: ['应用授权'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'plugin:application-auth:read')]
#[SimpleResponse(schema: ApplicationAuthResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/plugin/application-auth',
summary: '创建',
tags: ['应用授权'],
x: [SchemaConstants::X_SCHEMA_REQUEST => ApplicationAuthFormRequest::class]
)]
#[Permission(code: 'plugin:application-auth:create')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/plugin/application-auth/{id}',
summary: '更新',
tags: ['应用授权'],
x: [SchemaConstants::X_SCHEMA_REQUEST => ApplicationAuthFormRequest::class]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
)]
#[Permission(code: 'plugin:application-auth:update')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/plugin/application-auth/export',
summary: '导出',
tags: ['应用授权']
)]
#[Permission(code: 'plugin:application-auth:list', description: '导出')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/plugin/application-auth/{id}',
summary: '删除',
tags: ['应用授权'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'plugin:application-auth:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/plugin/application-auth',
summary: '批量删除',
tags: ['应用授权'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'plugin:application-auth:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Post(
path: '/plugin/application-auth/{id}/reset-secret',
summary: '重置密钥',
tags: ['应用授权'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'plugin:application-auth:reset-secret')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => ['id' => '123456789012345678', 'secret_key' => 'SK...']])]
public function resetSecret(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
return Json::success('ok', $this->service->resetSecret($id));
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,482 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\plugin;
use app\adminapi\controller\Base;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\service\core\plugin\PluginDownloadService;
use app\service\core\plugin\PluginInstallService;
use app\service\core\plugin\PluginRemoteService;
use app\service\core\plugin\PluginUninstallService;
use app\service\core\plugin\PluginService;
use core\tool\Sse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Container;
use support\Request;
use support\Response;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class PluginController extends Base
{
#[OA\Get(
path: '/plugin',
summary: '列表',
tags: ['模块市场'],
parameters: [
new OA\Parameter(
name: 'page',
description: '页码',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 1)
),
new OA\Parameter(
name: 'limit',
description: '每页数量',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 10)
),
new OA\Parameter(
name: 'type',
description: '模块类型(module: 系统模块, plugin: 插件)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string', enum: ['module', 'plugin'])
),
new OA\Parameter(
name: 'status',
description: '状态(0:禁用,1:启用)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', enum: [0, 1])
),
new OA\Parameter(
name: 'keyword',
description: '搜索关键词',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string')
),
new OA\Parameter(
name: 'installed',
description: '是否已安装(0:未安装,1:已安装)',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', enum: [0, 1])
),
],
)]
#[PageResponse(schema: [], example: [[
'id' => 3,
'code' => 'official',
'name' => 'official',
'type' => 'madong',
'version' => '1.0.0',
'status' => 1,
'description' => '官方应用插件',
'detail_description' => '',
'author' => 'Madong',
'cover' => '',
'poster' => '',
'price' => 0,
'downloads' => 0,
'rating' => 0,
'created_at' => '2026-03-19 16:26:19',
'updated_at' => '2026-03-19 16:26:19',
'update_time' => '',
'update_logs' => [],
'category' => null,
'category_name' => '',
'tags' => [],
'is_new' => 0,
'is_hot' => 0,
'purchased' => 0,
'installed' => 0,
'manual_uninstall' => 0,
'composer_dependencies' => [],
'npm_dependencies' => [],
'is_installed' => 1,
'installed_version' => '1.0.0',
'has_update' => 0,
'is_purchased' => 1,
'is_downloaded' => 1,
'can_uninstall' => 1,
'can_download' => 1,
'undeletable' => 1,
'version_info' => [
'remote' => [
'version' => '1.0.0',
'release_date' => '2026-03-19 16:26:19',
],
'local' => [
'version' => '1.0.0',
'install_date' => '2026-03-19 16:26:19',
],
'comparison' => [
'needs_update' => 0,
'is_latest' => 1,
'version_difference' => null,
],
],
]])]
#[Permission(code: 'plugin:module:list')]
public function index(Request $request): \support\Response
{
try {
$category = $request->input('category', null);
$pageInput = $request->input('current', $request->input('page', 1));
$limitInput = $request->input('size', $request->input('limit', 15));
$page = max(1, (int)$pageInput);
$limit = max(1, (int)$limitInput);
$type = $request->input('type');
$keyword = $request->input('keyword');
/** @var PluginService $service */
$service = Container::make(PluginService::class);
$result = $service->getList($category, $type, $keyword, $page, $limit);
return Json::success('common.operation.success', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), []);
}
}
#[OA\Get(
path: '/plugin/{name}/download',
summary: '下载模块',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'name',
description: '模块标识',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
public function download(Request $request): \support\Response
{
try {
$pluginCode = $request->route->param('name');
// 1. 获取下载链接
$remoteService = Container::make(PluginRemoteService::class);
$authCode = config('madong.auth_code');
$secretKey = config('madong.auth_secret');
$downloadInfo = $remoteService->getRemoteDownloadService($authCode, $secretKey, $pluginCode, 'latest');
if (empty($downloadInfo['url'])) {
return Json::fail('获取下载链接失败');
}
// 2. 下载并解压插件
$downloadService = Container::make(PluginDownloadService::class);
$result = $downloadService->downloadAndExtract($pluginCode, $downloadInfo['url']);
return Json::success('下载成功', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/plugin/detail/{name}',
summary: '获取模块详情',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'name',
description: '模块标识',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
public function detail(Request $request): \support\Response
{
try {
$moduleName = $request->route->param('name');
/** @var PluginService $service */
$service = Container::make(PluginService::class);
$result = $service->getList(null, null, $moduleName);
return Json::success('common.operation.success', $result['items'][0]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), []);
}
}
#[OA\Get(
path: '/plugin/{name}/upgrade-logs',
summary: '获取模块升级日志',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'name',
description: '模块标识',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
public function upgradeLogs(Request $request): \support\Response
{
try {
$moduleName = $request->route->param('name');
/** @var PluginService $service */
$service = Container::make(PluginService::class);
$result = $service->getUpgradeLogs($moduleName);
return Json::success('common.operation.success', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), []);
}
}
#[OA\Get(
path: '/plugin/check-environment',
summary: '环境检测',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'name',
description: '模块标识',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
public function checkEnvironment(Request $request): \support\Response
{
try {
$name = $request->input('name');
/** @var PluginInstallService $service */
$service = Container::make(PluginInstallService::class);
$result = $service->installCheck($name);
// 提取路径列表和状态
$paths = [];
foreach ($result['checks'] as $check) {
if (isset($check['path'])) {
// 只展示从madong开始的路径
$path = $check['path'];
$madongPos = strpos(strtolower($path), 'madong');
if ($madongPos !== false) {
// 截取从madong开始的路径
$path = substr($path, $madongPos);
// 确保路径格式正确,madong后面应该有目录分隔符
if (substr($path, 6, 1) !== DIRECTORY_SEPARATOR) {
$path = 'madong' . DIRECTORY_SEPARATOR . substr($path, 6);
}
}
$paths[] = [
'path' => $path,
'requirement' => $check['permission_type'] ?? 'readable',
'status' => $check['status'] === 'success' ? 'success' : 'error',
];
}
}
return Json::success('common.operation.success', [
'paths' => $paths,
'overall_status' => $result['overall_status'],
'message' => $result['message'],
]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), []);
}
}
#[OA\Get(
path: '/plugin/install',
summary: '插件安装',
tags: ['模块市场'],
)]
#[SimpleResponse(schema: [], example: [])]
public function install(Request $request): void
{
$connection = $request->connection;
$code = $request->input('code');
$mode = $request->input('mode', 'local');
// 验证参数
if (empty($code)) {
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], "\r\n"));
$connection->send(\core\tool\Sse::error('参数错误:缺少code参数'));
return;
}
// 发送SSE响应头
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], "\r\n"));
try {
// 发送开始安装消息
$connection->send(Sse::progress('开始安装插件 ' . $code, 0));
// 调用安装服务
$generator = (new PluginInstallService())->install($code, $mode);
foreach ($generator as $chunk) {
$connection->send($chunk);
}
} catch (\Exception $e) {
// 发送错误消息
$connection->send(Sse::error('安装失败:' . $e->getMessage()));
}
}
#[OA\Get(
path: '/plugin/uninstall',
summary: '插件卸载(SSE流式)',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'code',
description: '插件编码',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
public function uninstall(Request $request): void
{
$connection = $request->connection;
$code = $request->input('code');
// 验证参数
if (empty($code)) {
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], "\r\n"));
$connection->send(Sse::error('参数错误:缺少code参数'));
return;
}
// 发送SSE响应头
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], "\r\n"));
try {
// 调用卸载服务(统一入口)
$generator = (new PluginUninstallService())->uninstall($code);
foreach ($generator as $chunk) {
$connection->send($chunk);
}
} catch (\Exception $e) {
// 发送错误消息
$connection->send(Sse::error('卸载失败:' . $e->getMessage()));
}
}
#[OA\Delete(
path: '/plugin/{name}',
summary: '插件删除',
tags: ['模块市场'],
)]
#[OA\Parameter(
name: 'name',
description: '插件名称',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[Permission(code: 'plugin:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$name = $request->route->param('name');
if (empty($name)) {
return Json::fail('参数错误:缺少name参数', []);
}
/** @var PluginUninstallService $service */
$service = Container::get(PluginUninstallService::class);
$service->delete($name);
return Json::success('common.operation.delete.success', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/plugin/select',
summary: '获取已安装插件列表(用于下拉框)',
tags: ['模块市场'],
)]
#[SimpleResponse(schema: [], example: [])]
public function select(Request $request): \support\Response
{
try {
/** @var PluginService $service */
$service = Container::make(PluginService::class);
// 获取本地插件列表(包括已安装和未安装的)
$localModules = $service->getLocalModules();
// 过滤出已安装的插件
$installedPlugins = array_filter($localModules, function ($module) {
return $module['is_installed'] === true && $module['status'] == 1;
});
// 构建下拉框格式数据
$selectItems = [];
foreach ($installedPlugins as $plugin) {
$selectItems[] = [
'value' => $plugin['name'],
'label' => $plugin['name'] . ' - ' . $plugin['description'],
'name' => $plugin['name'],
'description' => $plugin['description'],
'version' => $plugin['version'],
'type' => $plugin['type'] ?? 'plugin',
];
}
return Json::success('common.operation.success', array_values($selectItems));
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), []);
}
}
}
@@ -0,0 +1,301 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\plugin;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\plugin\PluginValidate;
use app\service\admin\plugin\PluginDevelopService;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class PluginDevelopController extends Crud
{
public function __construct(PluginDevelopService $service, PluginValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
/**
* 插件列表
* 扫描插件目录,获取type为madong:前缀的插件,同步到数据库并返回列表
*
* @param Request $request
*
* @return \support\Response
*/
#[OA\Get(
path: '/plugin/develop',
summary: '插件列表',
tags: ['插件开发'],
parameters: [
new OA\Parameter(
name: 'page',
description: '页码',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 1)
),
new OA\Parameter(
name: 'limit',
description: '每页数量',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 15)
),
new OA\Parameter(
name: 'title',
description: '插件标题',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string')
),
new OA\Parameter(
name: 'author',
description: '作者',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string')
),
new OA\Parameter(
name: 'status',
description: '状态',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer')
),
]
)]
#[Permission(code: 'plugin:develop:list')]
#[PageResponse(schema: [], example: [[
"id" => "2032735331152958596",
"name" => "官网管理",
"icon" => null,
"code" => "official",
"description" => "官网管理插件,用于搭建和管理企业官方网站",
"enabled" => 1,
"author" => "Mr.April",
"version" => "1.0.0",
"cover" => null,
"type" => "madong:plugin",
"support_app" => "admin",
"installed_at" => null,
"created_at" => 1773476844,
"updated_at" => 1773476844,
"variables" => null,
"created_date" => "2026-03-14 16:27:24",
"updated_date" => "2026-03-14 16:27:24",
]])]
public function index(Request $request): \support\Response
{
try {
// 使用标准模式:通过 selectInput 处理查询参数
[$where, , $limit, , , $page] = $this->selectInput($request);
// 调用服务层获取列表数据
$result = $this->service->getList($where, $page, $limit);
// 使用标准格式返回
return Json::success($result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/plugin/develop/{id}',
summary: '插件详情',
tags: ['插件开发'],
parameters: [
new OA\Parameter(
name: 'id',
description: '插件ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'integer')
),
]
)]
#[Permission(code: 'plugin:develop:read')]
#[DataResponse(schema: [], example: [
"id" => "2032735331152958596",
"title" => "官网管理",
"icon" => null,
"key" => "official",
"desc" => "官网管理插件,用于搭建和管理企业官方网站",
"status" => 1,
"author" => "Mr.April",
"version" => "1.0.0",
"cover" => null,
"type" => "madong:plugin",
"support_app" => "admin",
"installed_at" => null,
"created_at" => 1773476844,
"updated_at" => 1773476844,
"variables" => null,
"created_date" => "2026-03-14 16:27:24",
"updated_date" => "2026-03-14 16:27:24",
])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$plugin = $this->service->read($id);
if (!$plugin) {
return Json::fail('插件不存在');
}
return Json::success($plugin->toArray());
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 创建插件
*
* @param Request $request
*
* @return \support\Response
* @throws \Throwable
*/
#[OA\Post(
path: '/plugin/develop',
summary: '创建插件',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['title', 'key'],
properties: [
new OA\Property(property: 'title', description: '插件标题', type: 'string'),
new OA\Property(property: 'key', description: '插件标识(唯一值)', type: 'string'),
new OA\Property(property: 'desc', description: '插件描述', type: 'string'),
new OA\Property(property: 'author', description: '作者', type: 'string'),
new OA\Property(property: 'version', description: '版本', type: 'string', default: '1.0.0'),
new OA\Property(property: 'type', description: '类型', type: 'string', default: 'madong:plugin'),
new OA\Property(property: 'icon', description: '图标(base64)', type: 'string'),
new OA\Property(property: 'cover', description: '封面(base64)', type: 'string'),
new OA\Property(property: 'frontend_type', description: '前端类型', type: 'string', default: 'admin'),
]
)
),
tags: ['插件开发']
)]
#[Permission(code: 'plugin:develop:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$this->validate->scene('store')->check($request->all());
$this->service->store($request->all());
return Json::success();
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 打包插件
* 将后端、后台前端、前台前端复制到后端server/plugin/xxx/resource目录,并打包为zip
*
* @param Request $request
*
* @return \support\Response
*/
#[OA\Post(
path: '/plugin/develop/build',
summary: '打包插件',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['plugin_key'],
properties: [
new OA\Property(property: 'plugin_key', description: '插件标识', type: 'string'),
]
)
),
tags: ['插件开发']
)]
#[Permission(code: 'plugin:develop:build')]
#[SimpleResponse(schema: [], example: [
"plugin_key" => "test",
"zip_path" => "D:\\MyProject\\private\\madong/server/runtime/adminapi/test.zip",
"has_frontend" => false,
])]
public function build(Request $request): \support\Response
{
try {
$pluginKey = $request->input('plugin_key');
if (empty($pluginKey)) {
return Json::fail('插件标识不能为空');
}
$result = $this->service->buildPlugin($pluginKey);
return Json::success('插件打包成功',$result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/plugin/develop/{id}',
summary: '编辑插件',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', description: '插件标题', type: 'string'),
new OA\Property(property: 'desc', description: '插件描述', type: 'string'),
new OA\Property(property: 'author', description: '作者', type: 'string'),
new OA\Property(property: 'version', description: '版本', type: 'string'),
new OA\Property(property: 'icon', description: '图标(base64)', type: 'string'),
new OA\Property(property: 'cover', description: '封面(base64)', type: 'string'),
]
)
),
tags: ['插件开发'],
parameters: [
new OA\Parameter(
name: 'id',
description: '插件ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'integer')
),
]
)]
#[Permission(code: 'plugin:develop:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$this->service->update($id, $request->all());
return Json::success('更新成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\plugin;
use app\adminapi\controller\Base;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\service\core\plugin\PluginRemoteService;
use core\tool\Json;
use madong\swagger\annotation\auth\AllowAnonymous;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class PluginMadongAuthController extends Base
{
public function __construct(PluginRemoteService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/madong/auth-info',
summary: '获取插件授权信息',
tags: ['应用授权']
)]
#[Permission('madong:delegation:read')]
#[AllowAnonymous(requireToken: true, requirePermission: true)]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": {"company_name": "-","domain": "-","auth_code": "202601***********888888"}}')]
public function read(Request $request): \support\Response
{
$data = [
'company_name' => '-',
'domain' => '-',
'auth_code' => config('madong.auth_code'),
];
return Json::success($data);
}
#[OA\Post(
path: '/madong/auth-info',
summary: '设置插件授权信息',
tags: ['应用授权']
)]
#[Permission('madong:delegation:setting')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$param = $request->all();
if (empty($param['auth_code'])) {
throw new \Exception('auth_code 不能为空');
}
if (empty($param['auth_secret'])) {
throw new \Exception('auth_secret 不能为空');
}
$this->service->verifyRemoteAuthorization($param['auth_code'],$param['auth_secret']);
//追加到系统配置
$this->updateMadongConfig($param['auth_code'], $param['auth_secret']);
return Json::success('授权设置成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
/**
* 更新server/config/madong.php文件中的配置
*
* @param string $authCode 授权码
* @param string $authSecret 授权密钥
*
* @throws \Exception
*/
private function updateMadongConfig(string $authCode, string $authSecret): void
{
$configPath = base_path() . '/config/madong.php';
if (!file_exists($configPath)) {
throw new \Exception('madong.php配置文件不存在');
}
$configContent = file_get_contents($configPath);
// 定义需要替换的参数映射
$replacements = [
'auth_code' => $authCode,
'auth_secret' => $authSecret,
];
foreach ($replacements as $key => $value) {
$pattern = '/\'' . $key . '\'\s*=>\s*env\(\'madong\..*?\'\s*,\s*\'(.*?)\'\)/';
$replacement = "'$key' => env('madong." . ($key === 'auth_code' ? 'code' : 'secret') . "', '$value')";
$configContent = preg_replace($pattern, $replacement, $configContent);
}
file_put_contents($configPath, $configContent);
}
}
@@ -0,0 +1,305 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\profile;
use app\adminapi\controller\Crud;
use app\adminapi\CurrentUser;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\profile\ProfileUpdateRequest;
use app\adminapi\schema\request\profile\PasswordUpdateRequest;
use app\adminapi\schema\request\profile\AvatarUpdateRequest;
use app\adminapi\validate\profile\ProfileValidate;
use app\dao\logs\LoginLogDao;
use app\schema\request\IdRequest;
use app\service\admin\logs\LoginLogService;
use app\service\admin\system\AdminService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Container;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[OA\Tag(name: '个人中心')]
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ProfileController extends Crud
{
public function __construct(AdminService $service, ProfileValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/profile',
summary: '获取当前用户信息',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[SimpleResponse(example: '{"id": 1, "user_name": "admin", "email": "test@example.com"}')]
#[Permission(code: 'admin:profile:info')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function show(Request $request): \support\Response
{
try {
$userId = Container::make(CurrentUser::class)->id();
$data = $this->service->get($userId);
return Json::success('ok', $data);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/profile',
summary: '更新个人信息',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(ref: ProfileUpdateRequest::class)
)]
#[SimpleResponse(example: '{"code": 0, "msg": "success"}')]
#[Permission(code: 'admin:profile:update_info')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function update(Request $request): \support\Response
{
try {
$userId = Container::make(CurrentUser::class)->id();
$data = $this->inputFilter($request->all());
// 验证数据
if (!$this->validate->scene('update-profile')->check($data)) {
throw new \Exception($this->validate->getError());
}
$this->service->updateUserInfo($userId, $data);
return Json::success('个人信息更新成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/profile/password',
summary: '修改密码',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(ref: PasswordUpdateRequest::class)
)]
#[SimpleResponse(example: '{"code": 0, "msg": "密码修改成功"}')]
#[Permission(code: 'admin:profile:password')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function updatePassword(Request $request): \support\Response
{
try {
$userId = Container::make(CurrentUser::class)->id();
$data = $this->inputFilter($request->all(), ['old_password', 'new_password', 'confirm_password']);
// 验证数据
if (!$this->validate->scene('update-password')->check($data)) {
throw new \Exception($this->validate->getError());
}
$this->service->updateUserPwd($userId, $data);
return Json::success('密码修改成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/profile/avatar',
summary: '更新头像',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[SimpleResponse(schema: [], example: [])]
#[Permission(code: 'admin:profile:avatar')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function updateAvatar(Request $request): \support\Response
{
try {
$userId = Container::make(CurrentUser::class)->id();
$avatar = $request->input('avatar');
// 验证数据
if (!$this->validate->scene('update-avatar')->check(['avatar' => $avatar])) {
throw new \Exception($this->validate->getError());
}
$this->service->updateAvatarUser($userId, $avatar);
return Json::success('头像更新成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/profile/sessions',
summary: '获取在线设备列表',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[OA\Parameter(
name: 'page',
description: '页码',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 1)
)]
#[OA\Parameter(
name: 'limit',
description: '每页数量',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer', default: 10)
)]
#[SimpleResponse(schema: [], example: [])]
#[Permission(code: 'admin:profile:sessions')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function getSessions(Request $request): \support\Response
{
try {
$page = (int) $request->input('page', 1);
$limit = (int) $request->input('limit', 10);
$currUser = Container::make(CurrentUser::class);
$jwt = new \core\jwt\JwtToken();
// 获取当前用户信息(用于兜底)
$adminInfo = $currUser->admin(true) ?? [];
$defaultUserName = $adminInfo['user_name'] ?? '';
$defaultRealName = $adminInfo['real_name'] ?? '';
$defaultAvatar = $adminInfo['avatar'] ?? '';
// 从 JWT 获取真实的会话列表
$allSessions = $jwt->getSessions($currUser->id());
$total = count($allSessions);
// 分页处理
$offset = ($page - 1) * $limit;
$sessions = array_slice($allSessions, $offset, $limit);
// 格式化数据
$items = array_map(function ($session) use ($defaultUserName, $defaultRealName, $defaultAvatar) {
$extra = $session['extra'] ?? [];
return [
'jti' => $session['jti'],
'client_type' => $session['client_type'] ?? 'admin',
'login_time' => $session['created_at'] ?? time(),
'ip' => $extra['ip'] ?? '',
'ip_location'=>$extra['ip_location'] ?? '',
'os' => $extra['os'] ?? '',
'browser' => $extra['browser'] ?? '',
'user_name' => $extra['user_name'] ?? $defaultUserName,
'real_name' => $extra['real_name'] ?? $defaultRealName,
'avatar' => $extra['avatar'] ?? $defaultAvatar,
];
}, $sessions);
return Json::success('ok', compact('total', 'items'));
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* @throws \Throwable
*/
#[OA\Delete(
path: '/profile/sessions/{jti}',
summary: '强制下线指定设备',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['个人中心']
)]
#[OA\Parameter(
name: 'jti',
description: '会话JTI',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)]
#[SimpleResponse(schema: [], example: [])]
#[Permission(code: 'admin:profile:kickout')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
public function kickoutSession(Request $request): \support\Response
{
try {
$jti = $request->route->param('jti', null);
$jwt = new \core\jwt\JwtToken();
$result = $jwt->kickoutByJti($jti);
if (!$result) {
return Json::fail('会话不存在或已失效');
}
return Json::success('ok');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/profile/{id}/update-preferences',
summary: '更新用户前端偏好设置',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'preferences',
description: '偏好设置',
type: 'string',
example: '{"theme": "dark"}'
),
]
)
)]
#[Permission(code: 'system:admin:update_preferences')]
#[SimpleResponse(schema: [], example: [])]
public function updatePreferences(Request $request): \support\Response
{
try {
$uid = Container::make(CurrentUser::class)->id();
$data = $request->all();
if (isset($this->validate) && $this->validate) {
$data['id'] = $uid;
if (!$this->validate->scene('update-preferences')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->updateUserPreferences($uid, $data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,320 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\review;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\IdRequest;
use app\service\admin\review\ReviewService;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[OA\Tag(name: '审核管理', description: '通用审核模块')]
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ReviewController extends Crud
{
public function __construct(ReviewService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/review',
summary: '审核列表',
tags: ['审核管理'],
parameters: [
new OA\Parameter(name: "status", description: "审核状态:0待审核,1已通过,2已拒绝", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "reviewable_type", description: "审核类型(morph_map别名)", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "LIKE_title", description: "标题模糊搜索", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "LIKE_applicant", description: "申请人模糊搜索", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "LIKE_content", description: "内容模糊搜索", in: "query", schema: new OA\Schema(type: "string")),
],
)]
#[Permission(code: "review:list")]
#[PageResponse(schema: [], example: [[
'id' => '230104551592014400',
'reviewable_type' => 'question',
'reviewable_id' => '230104551591014400',
'status' => 0,
'reason' => '同意',
'reviewer_id' => 1,
'reviewed_at' => 1758899600,
'flow_instance_id' => null,
'extra_data' => null,
'created_by' => 1,
'updated_by' => 1,
'created_at' => '2026-03-02T09:12:13.000000Z',
'updated_at' => '2026-03-02T09:08:38.000000Z',
'title' => '请问Madong框架可以免费商用吗?',
'content' => '我想用到项目上可以吗!阿打发斯蒂芬阿斯顿发生大法师',
'applicant' => '未知',
'reviewer_name' => '超级管理员',
'display_name' => 'question',
'morph_alias' => null,
'status_text' => '待审核',
'is_workflow_mode' => false,
'reviewer' => [
'id' => '1',
'user_name' => 'superAdmin',
'real_name' => '超级管理员',
'nick_name' => '超级管理员',
'created_date' => null,
'updated_date' => null,
],
'reviewable' => [
'id' => '230104551591014400',
'category_id' => '2',
'title' => '请问Madong框架可以免费商用吗?',
'content' => '我想用到项目上可以吗!阿打发斯蒂芬阿斯顿发生大法师',
'deleted_at' => null,
'created_at' => '2025-09-26T15:13:20.000000Z',
'updated_at' => '2026-03-03T06:39:01.000000Z',
'view_count' => 3869,
'like_count' => 0,
'collect_count' => 0,
'comment_count' => 0,
'member_id' => '277105476800872448',
'is_sticky' => 0,
'sticky_order' => 0,
'is_excellent' => 0,
'is_solved' => 0,
'solved_answer_id' => null,
'created_date' => '2025-09-26 23:13:20',
'updated_date' => '2026-03-03 14:39:01',
],
]])]
public function index(Request $request): \support\Response
{
try {
// 使用 selectInput 处理参数(现在 title/applicant/content 字段已被跳过)
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
// 准备关联预加载
$with = [
'reviewer' => function ($query) {
$query->select(['id', 'user_name', 'real_name', 'nick_name']);
},
];
// 获取映射后的审核列表(支持 extra_data 字段搜索)
$items = $this->service->getMappedList($where, $field, $page, $limit, $order, $with, false);
// 使用支持搜索的计数方法
$total = $this->service->getCountWithSearch($where);
return $this->formatNormal($items, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/review/{id}',
summary: '审核详情',
tags: ['审核管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("review:read")]
#[DataResponse(schema: [], example: [
'id' => '230104551592014400',
'reviewable_type' => 'question',
'reviewable_id' => '230104551591014400',
'status' => 0,
'reason' => '同意',
'reviewer_id' => 1,
'reviewed_at' => 1758899600,
'flow_instance_id' => null,
'extra_data' => null,
'created_by' => 1,
'updated_by' => 1,
'created_at' => '2026-03-02T09:12:13.000000Z',
'updated_at' => '2026-03-02T09:08:38.000000Z',
'title' => '请问Madong框架可以免费商用吗?',
'content' => '我想用到项目上可以吗!阿打发斯蒂芬阿斯顿发生大法师',
'applicant' => '未知',
'reviewer_name' => '超级管理员',
'display_name' => 'question',
'morph_alias' => null,
'status_text' => '待审核',
'is_workflow_mode' => false,
'reviewer' => [],
'reviewable' => [],
])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$detail = $this->service->getMappedReview($id);
if (!$detail) {
return Json::fail('审核记录不存在');
}
return Json::success('获取成功', $detail);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/review/{id}/approve',
summary: '审核通过',
tags: ['审核管理'],
parameters: [
new OA\Parameter(name: "id", description: "审核ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
)]
#[Permission("review:approve")]
#[SimpleResponse(schema: [], example: [])]
public function approve(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$this->service->approve($id);
return Json::success('审核通过');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/review/{id}/reject',
summary: '审核拒绝',
requestBody: new OA\RequestBody(
required: false,
content: new OA\JsonContent(properties: [
new OA\Property(property: "reason", type: "string", example: "内容违规"),
])
),
tags: ['审核管理'],
parameters: [
new OA\Parameter(name: "id", description: "审核ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
)]
#[Permission("review:reject")]
#[SimpleResponse(schema: [], example: [])]
public function reject(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$reason = $request->input('reason', '');
$this->service->reject($id, $reason);
return Json::success('审核拒绝');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/review/batch/approve',
summary: '批量审核通过',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "ids", type: "array", items: new OA\Items(type: "integer"), example: [1, 2, 3]),
])
),
tags: ['审核管理'],
)]
#[Permission("review:approve")]
#[SimpleResponse(schema: [], example: [])]
public function batchApprove(Request $request): \support\Response
{
try {
$ids = $request->input('ids', []);
$reason = $request->input('reason', '');
if (empty($ids)) {
return Json::fail('请选择要审核的记录');
}
$count = $this->service->batchApprove($ids, ['reason' => $reason]);
return Json::success('批量审核通过成功', ['count' => $count]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/review/batch/reject',
summary: '批量审核拒绝',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "ids", type: "array", items: new OA\Items(type: "integer"), example: [1, 2, 3]),
new OA\Property(property: "reason", type: "string", example: "内容违规"),
])
),
tags: ['审核管理'],
)]
#[Permission("review:reject")]
#[SimpleResponse(schema: [],example: [])]
public function batchReject(Request $request): \support\Response
{
try {
$ids = $request->input('ids', []);
$reason = $request->input('reason', '');
if (empty($ids)) {
return Json::fail('请选择要审核的记录');
}
$count = $this->service->batchReject($ids, $reason, ['reason' => $reason]);
return Json::success('批量审核拒绝成功', ['count' => $count]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/review/review/statistics',
summary: '审核统计',
tags: ['审核管理'],
)]
#[Permission("review:list")]
#[SimpleResponse(schema: [],example: [])]
public function statistics(Request $request): \support\Response
{
try {
$result = $this->service->getStatistics();
return Json::success('获取成功', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/review/{id}',
summary: '删除审核记录',
tags: ['审核管理'],
parameters: [
new OA\Parameter(name: "id", description: "审核ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
]
)]
#[Permission("review:delete")]
#[SimpleResponse(schema: [],example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,409 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\AdminFormRequest;
use app\adminapi\schema\request\system\AdminQueryRequest;
use app\adminapi\schema\response\system\AdminResponse;
use app\adminapi\validate\system\AdminValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\system\AdminRoleService;
use app\service\admin\system\AdminService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Container;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class AdminController extends Crud
{
public function __construct(AdminService $service, AdminValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/system/admin',
summary: '列表',
tags: ['用户管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => AdminQueryRequest::class,
]
)]
#[Permission(code: 'system:admin:list')]
#[PageResponse(schema: AdminResponse::class, example: [
'id' => '290809605012324352',
'user_name' => 'test11',
'real_name' => '123456',
'nick_name' => null,
'is_super' => 0,
'mobile_phone' => '18973598654',
'email' => '405784684@qq.com',
'avatar' => null,
'signed' => null,
'dashboard' => null,
'dept_id' => null,
'enabled' => 1,
'login_ip' => null,
'login_time' => null,
'created_by' => 1,
'updated_by' => null,
'created_at' => '2026-03-13T03:33:32.000000Z',
'updated_at' => '2026-03-13T03:45:28.000000Z',
'deleted_at' => null,
'sex' => 1,
'remark' => 'sadf ',
'birthday' => null,
'tel' => null,
'is_locked' => 0,
'created_date' => '2026-03-13 11:33:32',
'updated_date' => '2026-03-13 11:45:28',
'depts' => [],
'posts' => [
[
'id' => '290793153412726784',
'dept_id' => '290786837193555968',
'code' => 'tex',
'name' => '测试题',
'sort' => 1,
'enabled' => 1,
'created_by' => 1,
'updated_by' => null,
'created_at' => '2026-03-13T02:28:10.000000Z',
'updated_at' => '2026-03-13T02:28:10.000000Z',
'deleted_at' => null,
'remark' => 'sadv ',
'created_date' => '2026-03-13 10:28:10',
'updated_date' => '2026-03-13 10:28:10',
'pivot' => [
'admin_id' => '290809605012324352',
'post_id' => '290793153412726784',
],
],
],
'roles' => [],
])]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
[$total, $list] = $this->service->getList($where, $field, $page, $limit, $order, [], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/system/admin',
summary: '插入',
tags: ['用户管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => AdminFormRequest::class,
]
)]
#[Permission(code: 'system:admin:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $this->inputFilter($request->all(), ['post_id_list', 'role_id_list', 'dept_id_list', 'main_dept_id', 'main_post_id']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/admin/{id}',
summary: '更新',
tags: ['用户管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => AdminFormRequest::class,
]
)]
#[Permission(code: 'system:admin:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->inputFilter($request->all(), ['post_id_list', 'role_id_list', 'dept_id_list', 'main_dept_id', 'main_post_id']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->update($id, $data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/system/admin/{id}',
summary: '详情',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:admin:read')]
#[SimpleResponse(schema: AdminResponse::class, example: [
'id' => '290809605012324352',
'user_name' => 'test11',
'real_name' => '123456',
'nick_name' => null,
'is_super' => 0,
'mobile_phone' => '18973598654',
'email' => '405784684@qq.com',
'avatar' => null,
'signed' => null,
'dashboard' => null,
'dept_id' => null,
'enabled' => 1,
'login_ip' => null,
'login_time' => null,
'created_by' => 1,
'updated_by' => null,
'created_at' => '2026-03-13T03:33:32.000000Z',
'updated_at' => '2026-03-13T03:45:28.000000Z',
'deleted_at' => null,
'sex' => 1,
'remark' => 'sadf ',
'birthday' => null,
'tel' => null,
'is_locked' => 0,
'created_date' => '2026-03-13 11:33:32',
'updated_date' => '2026-03-13 11:45:28',
'depts' => [],
'posts' => [
[
'id' => '290793153412726784',
'dept_id' => '290786837193555968',
'code' => 'tex',
'name' => '测试题',
'sort' => 1,
'enabled' => 1,
'created_by' => 1,
'updated_by' => null,
'created_at' => '2026-03-13T02:28:10.000000Z',
'updated_at' => '2026-03-13T02:28:10.000000Z',
'deleted_at' => null,
'remark' => 'sadv ',
'created_date' => '2026-03-13 10:28:10',
'updated_date' => '2026-03-13 10:28:10',
'pivot' => [
'admin_id' => '290809605012324352',
'post_id' => '290793153412726784',
],
],
],
'roles' => [],
])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$result = $this->service->getAdminById($id)->toArray();
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/admin/{id}/locked',
summary: '禁用',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:admin:locked')]
#[SimpleResponse(schema: [], example: [])]
public function locked(Request $request): \support\Response
{
try {
// 获取单个 ID 和多个 ID
$data = $request->input('data', []);
$id = $request->input('id');
// 如果提供了多个 ID,优先使用多个 ID
if (!empty($data)) {
$id = $data;
} else if (empty($id)) {
return Json::fail('Either id or data must be provided.');
}
$this->service->locked($id);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/admin/{id}/unlocked',
summary: '解除',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:admin:unlocked')]
#[SimpleResponse(schema: [], example: [])]
public function unLocked(Request $request): \support\Response
{
try {
// 获取单个 ID 和多个 ID
$data = $request->input('data', []);
$id = $request->input('id');
// 如果提供了多个 ID,优先使用多个 ID
if (!empty($data)) {
$id = $data;
} else if (empty($id)) {
return Json::fail('Either id or data must be provided.');
}
$this->service->unLocked($id);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/admin/{id}/change-password',
summary: '重置密码',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:admin:change_password')]
#[SimpleResponse(example: '{"code": 0,"msg": "success"}')]
public function changePassword(Request $request): \support\Response
{
try {
$ids = $request->input('ids');
$password = $request->input('password', 123456);
$data = ['password' => password_hash($password, PASSWORD_DEFAULT)];
$this->service->batchUpdate(['id' => $ids], $data);
return Json::success('ok');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/system/admin/grant-role',
summary: '授权角色',
tags: ['用户管理'],
)]
#[Permission(code: 'system:admin:grant_role')]
#[SimpleResponse(schema: [], example: '{"code": 0,"msg": "ok"}')]
public function grantRole(Request $request): \support\Response
{
try {
$data = $this->inputFilter($request->all(), ['id', 'role_id_list']);
/** @var AdminRoleService $systemUserRoleService */
$systemUserRoleService = Container::make(AdminRoleService::class);
$systemUserRoleService->save($data);
return Json::success('ok');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/system/admin/{id}',
summary: '删除',
tags: ['用户管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:admin:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$data = $this->getDeleteIds($request);
$this->service->batchDelete($data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/system/admin',
summary: '批量删除',
tags: ['用户管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'system:admin:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
try {
$data = $this->getDeleteIds($request);
$this->service->batchDelete($data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\system;
use app\adminapi\controller\Base;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\attribute\SkipApiEncrypt;
use app\service\admin\system\ConfigService;
use core\tool\ApiEncryptService;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[SkipApiEncrypt]
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ApiEncryptController extends Base
{
protected ApiEncryptService $encryptService;
protected ConfigService $configService;
public function __construct(ApiEncryptService $encryptService, ConfigService $configService)
{
$this->encryptService = $encryptService;
$this->configService = $configService;
}
#[OA\Get(path: '/system/api-encrypt', summary: '获取接口加密配置', tags: ['系统配置'])]
#[Permission(code: 'system:config:update')]
#[DataResponse(description: '获取成功')]
public function detail(Request $request): Response
{
$config = $this->configService->config('api_encrypt_setting', [], ['group_code' => 'system']);
$config = is_array($config) ? $config : [];
return Json::success('获取成功', [
'enabled' => (int) ($config['enabled'] ?? ($this->encryptService->isEnabled() ? 1 : 0)),
'scope' => (string) ($config['scope'] ?? $this->encryptService->getScope()),
'key_version' => (string) ($config['key_version'] ?? $this->encryptService->getKeyVersion()),
'timestamp_tolerance' => (int) ($config['timestamp_tolerance'] ?? 300),
'exclude_paths' => $config['exclude_paths'] ?? [],
'encrypted_paths' => $config['encrypted_paths'] ?? $this->encryptService->getEncryptedPaths(),
'public_key_path' => (string) ($config['public_key_path'] ?? ''),
'private_key_path' => (string) ($config['private_key_path'] ?? ''),
'public_key' => $this->encryptService->getPublicKey(),
'private_key' => $this->encryptService->getPrivateKey(),
]);
}
#[OA\Post(path: '/system/api-encrypt/generate-keypair', summary: '生成接口加密密钥对', tags: ['系统配置'])]
#[Permission(code: 'system:config:update')]
#[DataResponse(description: '生成成功')]
public function generateKeypair(Request $request): Response
{
try {
$keys = $this->encryptService->generateKeyPair();
$this->encryptService->persistKeyPair($keys);
return Json::success('生成成功', [
'public_key' => $this->encryptService->getPublicKey(),
'private_key' => $this->encryptService->getPrivateKey(),
]);
} catch (\Throwable $exception) {
return Json::fail($exception->getMessage());
}
}
}
@@ -0,0 +1,346 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\CurrentUser;
use app\adminapi\event\MenuFormattingEvent;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use support\Container;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\system\AuthValidate;
use app\service\admin\system\AdminRoleService;
use app\service\admin\system\AdminService;
use app\service\admin\system\AuthService;
use app\service\admin\system\MenuService;
use app\service\admin\system\RoleMenuService;
use app\service\admin\system\RoleScopeDeptService;
use app\service\admin\system\RoleService;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\attribute\Permission;
use core\exception\handler\UnauthorizedHttpException;
use core\jwt\JwtToken;
use core\tool\Json;
use madong\helper\Dict;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use Webman\Event\Event;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class AuthController extends Crud
{
public function __construct(AuthService $service, AuthValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/system/auth/user-info',
summary: '获取登录用户',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:user_info')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": {"id": "1","user_name": "admin","real_name": "超级管理员","nick_name": "超级管理员","is_super": 1,"mobile_phone": null,"email": null,"avatar": null,"signed": null,"dashboard": null,"dept_id": "","enabled": 1,"login_ip": "127.0.0.1","login_time": 1762429533,"created_by": null,"updated_by": null,"created_at": "2025-10-30T12:24:09.000000Z","updated_at": "2025-11-06T11:45:34.000000Z","deleted_at": null,"sex": 0,"remark": null,"birthday": null,"tel": null,"is_locked": 0,"role_id_list": [],"post_id_list": [],"created_date": "2025-10-30 20:24:09","updated_date": "2025-11-06 19:45:34","depts": [],"posts": [],"casbin": [],"roles": []}}')]
public function getUserInfo(Request $request): \support\Response
{
try {
/** @var CurrentUser $currentUser */
$currentUser = Container::make(CurrentUser::class);
$data = $currentUser->admin(true);
if (empty($data)) {
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
}
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), null, 401);
}
}
#[OA\Get(
path: '/system/auth/user-menus',
summary: '获取用户-权限菜单',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:user_menus')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
#[SimpleResponse(schema: [], example: [])]
public function getPermissionsMenu(Request $request): \support\Response
{
try {
$format = input('format', 'default');
/** @var CurrentUser $currUser */
$currUser = Container::make(CurrentUser::class);
$collection = $this->service->getMenusByUserRoles($currUser, true);
$event = new MenuFormattingEvent($collection, $format);
$data = $event->dispatch();
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/user-permissions',
summary: '权限菜单-包含所有 菜单 按钮 接口',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:user_permissions')]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": []}')]
public function getPermissions(Request $request): \support\Response
{
try {
$format = input('format', 'default');
/** @var CurrentUser $currUser */
$currUser = Container::make(CurrentUser::class);
$collection = $this->service->getMenusByUserRoles($currUser, true);
$event = new MenuFormattingEvent($collection, $format);
$data = $event->dispatch();
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/perm-code',
summary: '获取用户-权限码',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:perm_code')]
#[AllowAnonymous(requireToken: true, requirePermission: false)]
#[SimpleResponse(example: '{"code": 0,"msg": "ok","data": ["admin"]}')]
public function getUserCodes(Request $request): \support\Response
{
try {
/** @var CurrentUser $currUser */
$currUser = Container::make(CurrentUser::class);
$data = $this->service->getCodesByUserRoles($currUser);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/role-menu-ids',
summary: '通过角色ID获取权限ID集合',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:role_menu_ids')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function roleMenuIds(Request $request): \support\Response
{
try {
$roleId = $request->input('role_id');
/** @var RoleService $systemRoleService */
$systemRoleService = Container::make(RoleService::class);
$data = $systemRoleService->getPermissionColumns($roleId);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/role-scope-ids',
summary: '通过角色ID获取权限范围自定义部门ID集合',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:role_scope_ids')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function roleScopeIds(Request $request): \support\Response
{
try {
$roleId = $request->input('role_id');
/** @var RoleScopeDeptService $systemRoleMenuService */
$systemRoleMenuService = Container::make(RoleScopeDeptService::class);
$data = $systemRoleMenuService->getColumn(['role_id' => $roleId], 'dept_id');
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/system/auth/save-role-menu',
summary: '保存角色菜单关系',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:role_menu')]
#[SimpleResponse(schema: [], example: [])]
public function saveRoleMenuRelation(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var RoleMenuService $systemRoleMenuService */
$systemRoleMenuService = Container::make(RoleMenuService::class);
$systemRoleMenuService->save($data);
return Json::success('success');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/user-list-by-role-id',
summary: '获取角色-关联用户列表',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[OA\Parameter(
name: 'role_id',
description: '角色ID',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string'),
)]
#[Permission(code: 'system:auth:role_user_list')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function getUsersByRoleId(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request, [
'exclude_filters' => true,
'filter_format' => 'simple', // 简洁模式没有操作符
'custom_fields' => [
'user_name',
'real_name',
'mobile_phone',
'role_id',
],
]);
/** @var AdminService $systemUserService */
$systemUserService = Container::make(AdminService::class);
$data = $systemUserService->getUsersListByRoleId($where, $field, $page, $limit);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/system/auth/save-user-role',
summary: '保存用户-关联角色',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:save_user_role')]
#[SimpleResponse(example: '{"code": 0,"msg": "success"}')]
public function saveUserRoles(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var AdminRoleService $systemUserRoleService */
$systemUserRoleService = Container::make(AdminRoleService::class);
$systemUserRoleService->saveUserRoles($data);
return Json::success('success');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/system/auth/remove-user-role',
summary: '移除用户-关联角色',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:remove_user_role')]
#[SimpleResponse(example: '{"code": 0,"msg": "ok"}')]
public function removeUserRole(Request $request): \support\Response
{
try {
$data = $request->all();
/** @var AdminRoleService $systemUserRoleService */
$systemUserRoleService = Container::make(AdminRoleService::class);
$systemUserRoleService->removeUserRole($data);
return Json::success('ok');
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/system/auth/user-list-exclude-role-id',
summary: '获取用户列表-排除指定角色id',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[OA\Parameter(
name: 'role_id',
description: '角色ID',
in: 'query',
required: true,
schema: new OA\Schema(type: 'string'),
)]
#[Permission(code: 'system:auth:user_list_exclude_role')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function getUsersExcludingRole(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request, [
'exclude_filters' => true,
'filter_format' => 'simple', // 简洁模式没有操作符
'custom_fields' => [
'LIKE_user_name',
'LIKE_real_name',
'LIKE_mobile_phone',
'role_id',
]]);
$systemUserService = Container::make(AdminService::class);
$data = $systemUserService->getUsersExcludingRole($where, $field, $page, $limit);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
/**
* @throws \core\jwt\ex\JwtRefreshTokenExpiredException
*/
#[OA\Post(
path: '/system/auth/refresh-token',
summary: '刷新Token',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['权限相关'],
)]
#[Permission(code: 'system:auth:refresh_token')]
#[AllowAnonymous(requireToken: false, requirePermission: false)]
#[SimpleResponse(example: '{"data": "new_access_token"}')]
public function refreshToken(Request $request): \support\Response
{
$token = (new JwtToken())->refresh();
return Json::success('ok', [
'access_token' => $token->accessToken,
'refresh_token' => $token->refreshToken,
'expires_in' => $token->expiresIn,
'expires_at' => $token->expiresAt->getTimestamp(),
]);
}
}
@@ -0,0 +1,579 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\ConfigGroupQueryRequest;
use app\adminapi\schema\request\system\ConfigQueryRequest;
use app\adminapi\schema\request\system\ConfigValueRequest;
use app\service\admin\system\ConfigService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class ConfigController extends Crud
{
public function __construct(ConfigService $service)
{
$this->service = $service;
}
#[OA\Get(
path: "/system/config/group/{group_code}",
summary: "按分组获取配置",
tags: ["系统配置"],
x: [
SchemaConstants::X_SCHEMA_REQUEST => ConfigGroupQueryRequest::class,
]
)]
#[AllowAnonymous(requireToken: false, requirePermission: false)]
#[SimpleResponse(schema: [], example: '{"site_open": "1","site_url": "http://127.0.0.1:8001","site_name": "madong-admin","site_logo": "https://madong.tech/assets/images/logo.svg","site_network_security": "2024042441号-2","site_description": "快速开发框架","site_record_no": "2024042442","site_icp_url": "https://beian.miit.gov.cn/","site_network_security_url": ""}')]
public function getByGroup(Request $request, string $group_code): \support\Response
{
try {
// 参数验证
if (empty($group_code)) {
return Json::fail('分组编码不能为空');
}
$options = [
'enabled_only' => $request->input('enabled_only', true),
'with_metadata' => $request->input('with_metadata', false),
];
$result = $this->service->getByGroup($group_code, [], $options);
return Json::success('操作成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: "/system/config/code/{code}",
summary: "按code获取配置",
tags: ["系统配置"],
x: [
SchemaConstants::X_SCHEMA_REQUEST => ConfigQueryRequest::class,
]
)]
#[OA\Parameter(
name: "code",
description: "配置编码",
in: "path",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[AllowAnonymous(requireToken: false, requirePermission: false)]
#[SimpleResponse(schema: [], example: ' {"site_open": "1","site_url": "http://127.0.0.1:8001"}')]
public function getByCode(Request $request, string $code): \support\Response
{
try {
// 参数验证
if (empty($code)) {
return Json::fail('配置编码不能为空');
}
$groupCode = $request->input('group_code', '');
$options = [];
if (!empty($groupCode)) {
$options['group_code'] = $groupCode;
}
$result = $this->service->config($code, [], $options);
return Json::success('操作成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 保存单个配置
*
* @param Request $request
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Post(
path: "/system/config",
summary: "保存单个配置",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $request->all();
// 参数验证
if (empty($data['group_code']) || empty($data['code']) || empty($data['name'])) {
return Json::fail('参数不能为空');
}
$options = [
'group_code' => $data['group_code'],
'name' => $data['name'],
];
if (isset($data['enabled'])) {
$options['enabled'] = $data['enabled'];
}
$this->service->update(
$data['code'],
$data['content'],
$options
);
return Json::success('保存成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 保存单个配置
*
* @param Request $request
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Put(
path: "/system/config/{code}",
summary: "更新配置",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$data = $request->all();
$code = $request->route->param('code');
$options = [
'name' => $data['name'],
'group_code' => $data['group_code'] ?? '',
'enabled' => $data['enabled'] ?? 0,
];
$this->service->update(
$code,
$data['content'],
$options
);
return Json::success('更新成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 获取特定配置项的特定键值
*
* @param Request $request
* @param string $code
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Get(
path: "/system/config/{code}/value",
summary: "获取特定配置项的特定键值",
tags: ["系统配置"],
x: [
SchemaConstants::X_SCHEMA_REQUEST => ConfigValueRequest::class,
]
)]
#[SimpleResponse(schema: [], example: '{"data": "value"}')]
public function getValue(Request $request, string $code): \support\Response
{
try {
// 参数验证
if (empty($code)) {
return Json::fail('配置项编码不能为空');
}
$groupCode = $request->input('group_code');
$key = $request->input('key');
if (empty($groupCode) || empty($key)) {
return Json::fail('分组编码和配置键不能为空');
}
$result = $this->service->getValue($code, $key, null, ['group_code' => $groupCode]);
return Json::success('操作成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: "/system/config/{code}/value",
summary: "更新特定配置项的特定键值",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:update_value')]
#[SimpleResponse(schema: [], example: [])]
public function setValue(Request $request, string $code): \support\Response
{
try {
// 参数验证
if (empty($code)) {
return Json::fail('配置项编码不能为空');
}
$groupCode = $request->post('group_code');
$key = $request->post('key');
$value = $request->post('value');
if (empty($groupCode) || empty($key)) {
return Json::fail('分组编码和配置键不能为空');
}
$result = $this->service->setValue($code, $key, $value, ['group_code' => $groupCode]);
if ($result) {
return Json::success('更新成功');
} else {
return Json::fail('更新失败');
}
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: "/system/config/all-grouped",
summary: "获取所有配置并按分组整理",
tags: ["系统配置"],
)]
#[OA\Parameter(
name: "enabled_only",
description: "是否只获取启用的配置项",
in: "query",
required: false,
schema: new OA\Schema(type: "boolean", default: true),
)]
#[OA\Parameter(
name: "group_filter",
description: "分组过滤,多个分组用逗号分隔",
in: "query",
required: false,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "with_metadata",
description: "是否包含配置项的完整元数据",
in: "query",
required: false,
schema: new OA\Schema(type: "boolean", default: false),
)]
#[OA\Parameter(
name: "key_by",
description: "按指定字段作为键名('code' 或 'id')",
in: "query",
required: false,
schema: new OA\Schema(type: "string", enum: ["code", "id"]),
)]
#[OA\Parameter(
name: "sort_groups",
description: "是否对分组进行排序",
in: "query",
required: false,
schema: new OA\Schema(type: "boolean", default: false),
)]
#[OA\Parameter(
name: "sort_configs",
description: "是否对配置项进行排序",
in: "query",
required: false,
schema: new OA\Schema(type: "boolean", default: false),
)]
#[SimpleResponse(schema: [], example: '{"site": {"site_name": "网站名称"}, "oss": {"accessKeyId": "xxx"}}')]
public function getAllGrouped(Request $request): \support\Response
{
try {
$options = [
'enabled_only' => $request->input('enabled_only', true),
'with_metadata' => $request->input('with_metadata', false),
'key_by' => $request->input('key_by', null),
'sort_groups' => $request->input('sort_groups', false),
'sort_configs' => $request->input('sort_configs', false),
];
// 处理分组过滤参数
$groupFilter = $request->input('group_filter', '');
if (!empty($groupFilter)) {
$options['group_filter'] = explode(',', $groupFilter);
}
$result = $this->service->getAllGrouped($options);
return Json::success('操作成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: "/system/config/items",
summary: "获取配置项列表",
tags: ["系统配置"],
)]
#[OA\Parameter(
name: "page",
description: "页码",
in: "query",
required: false,
schema: new OA\Schema(type: "integer", default: 1),
)]
#[OA\Parameter(
name: "pageSize",
description: "每页数量",
in: "query",
required: false,
schema: new OA\Schema(type: "integer", default: 20),
)]
#[OA\Parameter(
name: "groupCode",
description: "分组代码",
in: "query",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "keyword",
description: "搜索关键字",
in: "query",
required: false,
schema: new OA\Schema(type: "string"),
)]
#[SimpleResponse(schema: [], example: ['items'=>[],'total'=>0])]
public function getItems(Request $request): \support\Response
{
try {
$page = $request->input('page', 1);
$pageSize = $request->input('pageSize', 20);
$groupCode = $request->input('groupCode', '');
$keyword = $request->input('keyword', '');
if (empty($groupCode)) {
return Json::fail('分组代码不能为空');
}
$result = $this->service->getItems((int)$page, (int)$pageSize, $groupCode, $keyword);
return Json::success('操作成功', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 创建单个配置项
*
* @param Request $request
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Post(
path: "/system/config/item",
summary: "创建配置项",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:create')]
#[SimpleResponse(schema: [], example: [])]
public function createItem(Request $request): \support\Response
{
try {
$data = $request->post();
// 参数验证
if (empty($data['group_code']) || empty($data['code']) || empty($data['name']) || !isset($data['value'])) {
return Json::fail('参数不能为空');
}
$type = $data['type'] ?? 'string';
// 值类型转换
$value = $this->convertValue($data['value'], $type);
$configData = [
'group_code' => $data['group_code'],
'code' => $data['code'],
'name' => $data['name'],
'content' => $type === 'json' ? $value : (string)$value,
'type' => $type,
'description' => $data['description'] ?? '',
'enabled' => $data['enabled'] ?? 1,
'is_sys' => 0,
'remark' => '',
];
$this->service->createItem($configData);
return Json::success('创建成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 更新单个配置项
*
* @param Request $request
* @param int $id
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Put(
path: "/system/config/item/{id}",
summary: "更新配置项",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:update')]
#[SimpleResponse(schema: [], example: [])]
public function updateItem(Request $request, int $id): \support\Response
{
try {
$data = $request->post();
$type = $data['type'] ?? 'string';
// 值类型转换
$value = $this->convertValue($data['value'], $type);
$configData = [
'name' => $data['name'],
'content' => $type === 'json' ? $value : (string)$value,
'type' => $type,
'description' => $data['description'] ?? '',
'enabled' => $data['enabled'] ?? 1,
];
$this->service->updateItem($id, $configData);
return Json::success('更新成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: "/system/config/item/{id}/toggle",
summary: "切换配置项状态",
tags: ["系统配置"],
)]
#[Permission(code: 'system:config:update')]
#[SimpleResponse(schema: [], example: [])]
public function toggleItem(Request $request, int $id): \support\Response
{
try {
$enabled = $request->post('enabled', 0);
$this->service->toggleItem($id, (bool)$enabled);
return Json::success('操作成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 值类型转换
*
* @param mixed $value 原始值
* @param string $type 目标类型
*
* @return mixed
*/
private function convertValue(mixed $value, string $type): mixed
{
return match ($type) {
'string' => (string)$value,
'number' => (string)$value,
'boolean' => $value ? '1' : '0',
'json' => is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE),
default => (string)$value
};
}
/**
* 删除配置项(按code)
*
* @param Request $request
* @param string $code 配置编码
*
* @return \support\Response
* @throws \Exception
*/
#[OA\Delete(
path: "/system/config/{code}",
summary: "删除配置项",
tags: ["系统配置"],
)]
#[OA\Parameter(
name: "code",
description: "配置编码",
in: "path",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "group_code",
description: "分组代码",
in: "query",
required: false,
schema: new OA\Schema(type: "string"),
)]
#[Permission(code: 'system:config:delete')]
#[SimpleResponse(schema: [], example: [])]
public function delete(Request $request, string $code): \support\Response
{
try {
if (empty($code)) {
return Json::fail('配置编码不能为空');
}
$options = [];
$groupCode = $request->input('group_code');
if (!empty($groupCode)) {
$options['group_code'] = $groupCode;
}
$result = $this->service->delete($code, $options);
if ($result) {
return Json::success('删除成功');
} else {
return Json::fail('配置不存在');
}
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\FeedbackFormRequest;
use app\adminapi\schema\request\system\FeedbackQueryRequest;
use app\adminapi\schema\response\system\FeedbackResponse;
use app\adminapi\validate\system\FeedbackValidate;
use app\adminapi\CurrentUser;
use core\tool\Json;
use support\Container;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\system\FeedbackService;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class FeedbackController extends Crud
{
public function __construct(FeedbackService $service, FeedbackValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/feedback', summary: '问题反馈列表', tags: ['问题反馈'], x: [SchemaConstants::X_SCHEMA_REQUEST => FeedbackQueryRequest::class])]
#[Permission(code: 'setting:feedback:list')]
#[PageResponse(schema: FeedbackResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(path: '/feedback/{id}', summary: '问题反馈详情', tags: ['问题反馈'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'setting:feedback:read')]
#[SimpleResponse(schema: FeedbackResponse::class, example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/feedback', summary: '创建问题反馈', tags: ['问题反馈'], x: [SchemaConstants::X_SCHEMA_REQUEST => FeedbackFormRequest::class])]
#[Permission(code: 'setting:feedback:create')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(path: '/feedback/{id}', summary: '更新问题反馈', tags: ['问题反馈'], x: [SchemaConstants::X_SCHEMA_REQUEST => FeedbackFormRequest::class])]
#[OA\Parameter(name: 'id', description: '反馈ID', in: 'path', required: true, schema: new OA\Schema(type: 'string'))]
#[Permission(code: 'setting:feedback:update')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/feedback/{id}/reply',
summary: '回复问题反馈',
tags: ['问题反馈'],
parameters: [new OA\Parameter(name: 'id', description: '反馈ID', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['reply_content'],
properties: [new OA\Property(property: 'reply_content', description: '回复内容', type: 'string', maxLength: 2000)]
)
)
)]
#[Permission(code: 'setting:feedback:reply')]
#[SimpleResponse(schema: FeedbackResponse::class, example: ['code' => 0, 'message' => '回复成功', 'data' => []])]
public function reply(Request $request): \support\Response
{
try {
$data = [
'id' => $request->route->param('id'),
'reply_content' => trim((string)$request->input('reply_content')),
];
$this->validate->scene('reply')->check($data);
if ($data['reply_content'] === '') {
return Json::fail('请填写回复内容');
}
$feedback = $this->service->get($data['id']);
if (!$feedback) {
return Json::fail('反馈记录不存在');
}
$adminId = Container::make(CurrentUser::class)->id();
if (!$adminId) {
return Json::fail('登录状态已失效');
}
$this->service->update($data['id'], [
'reply_content' => $data['reply_content'],
'reply_by' => $adminId,
'reply_at' => time(),
]);
return Json::success('回复成功', $this->service->get($data['id'])?->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(path: '/feedback/{id}', summary: '删除问题反馈', tags: ['问题反馈'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'setting:feedback:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(path: '/feedback', summary: '批量删除问题反馈', tags: ['问题反馈'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'setting:feedback:delete')]
#[SimpleResponse(example: ['code' => 0, 'message' => 'success', 'data' => []])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\MenuFormRequest;
use app\adminapi\schema\request\system\MenuQueryRequest;
use app\adminapi\schema\response\system\MenuResponse;
use app\adminapi\validate\system\MenuValidate;
use app\schema\request\BatchDeleteRequest;
use app\scope\global\AccessPermissionScope;
use app\service\admin\system\MenuService;
use app\service\core\plugin\PluginService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\Container;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class MenuController extends Crud
{
public function __construct(MenuService $service, MenuValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/system/menu',
summary: '列表',
tags: ['菜单管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MenuQueryRequest::class,
]
)]
#[Permission(code: 'system:menu:list')]
#[PageResponse(schema: MenuResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/system/menu/{id}',
summary: '详情',
tags: ['菜单管理'],
parameters: [
new OA\Parameter(
name: 'id',
description: '菜单ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
),
],
)]
#[Permission(code: 'system:menu:read')]
#[SimpleResponse(schema: MenuResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/system/menu',
summary: '创建',
tags: ['菜单管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MenuFormRequest::class,
]
)]
#[Permission(code: 'system:menu:create')]
#[SimpleResponse(schema:[],example: [])]
public function store(Request $request): \support\Response
{
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_AUTHS_DATA);
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_MENUS_DATA);
return parent::store($request);
}
#[OA\Put(
path: '/system/menu/{id}',
summary: '更新',
tags: ['菜单管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => MenuFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: '菜单ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'system:menu:update')]
#[SimpleResponse(schema:[],example: [])]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $request->all();
$model = $this->service->update($id, $data);
return Json::success('更新成功', $model->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/system/menu',
summary: '删除',
tags: ['菜单管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'system:menu:delete')]
#[SimpleResponse(schema:[],example: [])]
public function destroy(Request $request): \support\Response
{
try {
$data = $this->getDeleteIds($request);
if (empty($data)) {
throw new AdminException('参数错误');
}
$result = $this->service->batchDelete($data);
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_AUTHS_DATA);
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_MENUS_DATA);
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/system/menu/batch-store',
summary: '批量添加菜单',
tags: ['菜单管理'],
)]
#[Permission(code: 'system:menu:batch_store')]
#[RequestBody(required: true, content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: MenuFormRequest::class)))]
#[SimpleResponse(schema:[],example: [])]
public function batchStore(Request $request): \support\Response
{
try {
$params = $request->input('menus', []);
$data = [];
if (isset($this->validate) && $this->validate) {
foreach ($params as $param) {
$data[] = $this->inputFilter($param);
if (!$this->validate->scene('batch-store')->check($param)) {
throw new \Exception($this->validate->getError());
}
}
}
foreach ($data as $item) {
$this->service->save($item);
}
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_AUTHS_DATA);
$this->service->cacheDriver()->delete(MenuService::CACHE_ALL_MENUS_DATA);
return Json::success('ok');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/system/menu/app/list',
summary: '获取应用列表',
tags: ['菜单管理'],
)]
#[Permission(code: 'system:menu:app_list')]
#[SimpleResponse(schema:[],example: [])]
public function appList(Request $request): \support\Response
{
try {
/**@var PluginService $service */
$service = Container::make(PluginService::class);
$model = $service->selectList([], '*', 0, 0, '', [], false, [AccessPermissionScope::class]);
return $this->formatAppSelect($model);
} catch (\Exception $e) {
return Json::fail('获取应用列表失败', []);
}
}
private function formatAppSelect($items): \support\Response
{
$formatted_items = [];
$formatted_items[] = ['label' => '系统应用', 'value' => 'admin'];
foreach ($items as $item) {
$formatted_items[] = [
'label' => $item->title ?? $item->name ?? $item->real_name ?? $item->id,
'value' => $item->id,
];
}
return Json::success('ok', $formatted_items);
}
}
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\RecycleBinQueryRequest;
use app\adminapi\schema\response\system\RecycleBinResponse;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\system\RecycleBinService;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RecycleBinController extends Crud
{
public function __construct(RecycleBinService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/system/recycle',
summary: '列表',
tags: ['数据回收站'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RecycleBinQueryRequest::class,
]
)]
#[Permission(code: 'system:recycle:list')]
#[PageResponse(schema: RecycleBinResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/system/recycle/{id}',
summary: '详情',
tags: ['数据回收站'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:recycle:read')]
#[SimpleResponse(schema: RecycleBinResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Delete(
path: '/system/recycle/{id}',
summary: '删除',
tags: ['数据回收站'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:recycle:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/system/recycle',
summary: '批量删除',
tags: ['数据回收站'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'system:recycle:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/system/recycle/{id}/restore',
summary: '恢复',
tags: ['数据回收站'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:recycle:recover')]
#[SimpleResponse(schema: [], example: [])]
public function restore(Request $request): \support\Response
{
try {
$id = $request->input('id');
$this->service->restoreRecycleBin($id);
return Json::success('ok');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,258 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\RoleFormRequest;
use app\adminapi\schema\request\system\RoleQueryRequest;
use app\adminapi\schema\response\system\RoleResponse;
use app\adminapi\validate\system\RoleValidate;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\scope\global\AccessPermissionScope;
use app\service\admin\org\DeptService;
use app\service\admin\system\RoleService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\Container;
use support\Request;
use support\annotation\Middleware;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RoleController extends Crud
{
public function __construct(RoleService $service, RoleValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/system/role',
summary: '列表',
tags: ['角色管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RoleQueryRequest::class,
]
)]
#[Permission(code: ['system:role:list'])]
#[PageResponse(schema: RoleResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/system/role/{id}',
summary: '详情',
tags: ['角色管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:role:read')]
#[SimpleResponse(schema: RoleResponse::class, example: [])]
public function show(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id, ['*'], ['scopes']);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => 400]);
}
return Json::success('ok', $data->toArray());
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Post(
path: '/system/role',
summary: '新增',
tags: ['角色管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RoleFormRequest::class,
]
)]
#[Permission(code: 'system:role:create')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
try {
$data = $this->inputFilter($request->all(), ['permissions']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('store')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$model = $this->service->save($data);
if (empty($model)) {
throw new AdminException('插入失败');
}
$pk = $model->getPk();
return Json::success('ok', [$pk => $model->getData($pk)]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/role/{id}',
summary: '更新',
tags: ['角色管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => RoleFormRequest::class,
]
)]
#[OA\Parameter(
name: 'id',
description: 'ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: 1)
)]
#[Permission(code: 'system:role:update')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->inputFilter($request->all(), ['permissions', 'scopes']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('update')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->update($id, $data);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/system/role/{id}',
summary: '删除',
tags: ['角色管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'system:role:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/system/role',
summary: '批量删除',
tags: ['角色管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'system:role:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Put(
path: '/system/role/{id}/data-scope',
summary: '分配数据权限',
tags: ['角色管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'id', type: 'integer', example: 1),
new OA\Property(property: 'data_scope', type: 'integer', example: 1),
new OA\Property(property: 'scopes', type: 'array', items: new OA\Items(type: 'string')),
]
))]
#[Permission(code: 'system:role:data_scope')]
#[SimpleResponse(schema: [], example: [])]
public function dataScope(Request $request): \support\Response
{
try {
$id = $request->route->param('id');
$data = $this->inputFilter($request->all(), ['scopes']);
if (isset($this->validate) && $this->validate) {
if (!$this->validate->scene('data-scope')->check($data)) {
throw new \Exception($this->validate->getError());
}
}
$this->service->updateScope($id, $data);
return Json::success('ok', []);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Put(
path: '/system/role/{id}/change-status',
summary: '切换状态',
tags: ['角色管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'status', type: 'string', example: 1),
]
))]
#[Permission(code: 'system:role:status')]
#[SimpleResponse(schema: [], example: [])]
public function changeStatus(Request $request): \support\Response
{
return parent::changeStatus($request);
}
#[OA\Get(
path: '/system/role/scope/dept',
summary: '获取部门权限树',
tags: ['角色管理']
)]
public function getDeptScope(Request $request): \support\Response
{
try {
/** @var DeptService $service */
$service = Container::make(DeptService::class);
$iterm = $service->selectList([], ['id', 'pid', 'name'], 0, 0, 'created_at', [], false, [AccessPermissionScope::class]);
return $this->formatTree($iterm);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\system;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\service\admin\system\RuleService;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class RuleController extends Crud
{
public function __construct(RuleService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/system/rule-cate',
summary: '获取接口分类(基于tags)',
tags: ['接口管理']
)]
#[Permission(code: 'system:rule:cate')]
#[SimpleResponse(schema: [], example: [])]
public function cate(Request $request): \support\Response
{
try {
$result = $this->service->getCategories();
return Json::success('ok', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/system/rule',
summary: '获取接口列表(支持分类和搜索)',
tags: ['接口管理']
)]
#[Permission(code: 'system:rule:list')]
#[SimpleResponse(schema: [], example: [])]
public function list(Request $request): \support\Response
{
try {
$cateId = $request->input('cate_id');
$keyword = $request->input('keyword');
$result = $this->service->getRoutesByCategory($cateId, $keyword);
return Json::success('ok', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/system/rule/refresh',
summary: '刷新接口扫描缓存',
tags: ['接口管理']
)]
#[Permission(code: 'system:rule:refresh')]
#[SimpleResponse(schema: [], example: [])]
public function refresh(Request $request): \support\Response
{
try {
$this->service->refresh();
return Json::success('刷新成功', []);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\system;
use app\adminapi\controller\Base;
use app\attribute\SkipApiEncrypt;
use core\tool\ApiEncryptService;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\attribute\AllowAnonymous;
use OpenApi\Attributes as OA;
use support\Request;
use support\Response;
#[SkipApiEncrypt]
final class SecurityController extends Base
{
protected ApiEncryptService $encryptService;
public function __construct(ApiEncryptService $encryptService)
{
$this->encryptService = $encryptService;
}
#[OA\Get(path: '/system/security/api-encrypt/public-key', summary: '获取接口加密公钥', tags: ['system-security'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
public function publicKey(Request $request): Response
{
$publicKey = $this->encryptService->getPublicKey();
if ($publicKey === '') {
return Json::fail('接口加密公钥未配置');
}
return Json::success('获取成功', [
'enabled' => $this->encryptService->isEnabled(),
'scope' => $this->encryptService->getScope(),
'algorithm' => $this->encryptService->getAlgorithm(),
'key_version' => $this->encryptService->getKeyVersion(),
'public_key' => $publicKey,
]);
}
}
@@ -0,0 +1,400 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\terminal;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\service\core\terminal\Terminal;
use core\tool\Json;
use core\tool\Sse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use support\Request;
use support\Response;
use support\annotation\Middleware;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class TerminalController
{
#[OA\Get(
path: "/terminal/config",
summary: "获取终端配置",
tags: ["终端管理"]
)]
#[Permission(code: "terminal:config:read")]
#[AllowAnonymous(requireToken: false, requirePermission: false)]
#[SimpleResponse(schema: [], example: [
'enabled' => true,
'npm_package_manager' => 'pnpm',
'npm_registry' => 'npm',
'composer_registry' => 'composer',
'package_managers' => [
'npm' => [
'name' => 'NPM',
'install' => 'npm install',
'build' => 'npm run build',
'check' => 'npm --version',
],
'cnpm' => [
'name' => 'CNPM',
'install' => 'cnpm install',
'build' => 'cnpm run build',
'check' => 'cnpm --version',
],
'pnpm' => [
'name' => 'PNPM',
'install' => 'pnpm install',
'build' => 'pnpm run build',
'check' => 'pnpm --version',
],
'yarn' => [
'name' => 'YARN',
'install' => 'yarn install',
'build' => 'yarn build',
'check' => 'yarn --version',
],
],
'frontend_programs' => [
'admin' => [
'enabled' => true,
'source_dir' => '{project_root}/admin/dist',
'target_dir' => '{backend_root}/public/admin',
'copy_mappings' => [
'*' => '.',
],
'clean_target' => true,
'preserve_files' => [
'.gitkeep',
],
'copy_options' => [
'recursive' => true,
'overwrite' => true,
'preserve_permissions' => true,
],
],
'web' => [
'enabled' => true,
'source_dir' => '{project_root}/web/.output/public',
'target_dir' => '{backend_root}/public',
'copy_mappings' => [
'*' => '.',
],
'clean_target' => true,
'preserve_files' => [
'.gitkeep',
],
'copy_options' => [
'recursive' => true,
'overwrite' => true,
'preserve_permissions' => true,
],
],
'h5' => [
'enabled' => true,
'source_dir' => '{project_root}/uni-app/dist/build/h5',
'target_dir' => '{backend_root}/public/h5',
'copy_mappings' => [
'*' => '.',
],
'clean_target' => true,
'preserve_files' => [
'.gitkeep',
],
'copy_options' => [
'recursive' => true,
'overwrite' => true,
'preserve_permissions' => true,
],
],
'app' => [
'enabled' => true,
'source_dir' => '{project_root}/uni-app/dist/build/app',
'target_dir' => '{backend_root}/public/app',
'copy_mappings' => [
'*' => '.',
],
'clean_target' => true,
'preserve_files' => [
'.gitkeep',
],
'copy_options' => [
'recursive' => true,
'overwrite' => true,
'preserve_permissions' => true,
],
],
'uni-app' => [
'enabled' => true,
'platforms' => [
'h5' => [
'source_dir' => '{project_root}/uni-app/dist/build/h5',
'target_dir' => '{backend_root}/public/uni-app/h5',
'copy_mappings' => [
'*' => '.',
],
],
'mp-weixin' => [
'source_dir' => '{project_root}/uni-app/dist/build/mp-weixin',
'target_dir' => '{backend_root}/public/uni-app/mp-weixin',
'copy_mappings' => [
'*' => '.',
],
],
'app-plus' => [
'source_dir' => '{project_root}/uni-app/dist/build/app-plus',
'target_dir' => '{backend_root}/public/uni-app/app-plus',
'copy_mappings' => [
'*' => '.',
],
],
],
'default_platform' => 'h5',
'clean_target' => true,
'preserve_files' => [
'.gitkeep',
],
'copy_options' => [
'recursive' => true,
'overwrite' => true,
'preserve_permissions' => true,
],
],
],
])]
public function config(Request $request): Response
{
try {
$config = [
'enabled' => config('terminal.enabled', false),
'npm_package_manager' => config('terminal.npm_package_manager', 'npm'),
'npm_registry' => config('terminal.registries.npm.current', 'npm'),
'composer_registry' => config('terminal.registries.composer.current', 'composer'),
'package_managers' => config('terminal.package_managers', []),
'frontend_programs' => config('terminal.frontend_programs', []),
];
return Json::success('获取配置成功', $config);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: "/terminal/config",
summary: "更新终端配置",
tags: ["终端管理"]
)]
#[Permission(code: "terminal:config:create")]
#[SimpleResponse(schema: [], example: [])]
public function updateConfig(Request $request): Response
{
try {
$data = $request->post();
// 读取当前配置
$configFile = base_path() . '/config/terminal.php';
$config = require $configFile;
// 更新包管理器配置
if (isset($data['npm_package_manager'])) {
$config['npm_package_manager'] = $data['npm_package_manager'];
}
// 保存配置到文件
$content = "<?php\n\nreturn " . $this->arrayToPhpConfig($config) . ";\n";
file_put_contents($configFile, $content);
return Json::success('配置更新成功');
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: "/terminal/commands",
summary: "获取命令列表(带分组)",
tags: ["终端管理"]
)]
#[Permission(code: "terminal:config:commands")]
#[AllowAnonymous(requireToken: false, requirePermission: false)]
#[SimpleResponse(schema: [], example: [])]
public function commands(Request $request): Response
{
try {
$webConfig = config('terminal.web.command_groups', []);
return Json::success('获取命令列表成功', $webConfig);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: "/terminal",
summary: "执行命令(SSE方式)",
tags: ["终端管理"]
)]
#[OA\Parameter(
name: "command",
description: "命令key",
in: "query",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "uuid",
description: "会话UUID",
in: "query",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "extend",
description: "扩展信息",
in: "query",
required: false,
schema: new OA\Schema(type: "string"),
)]
#[Permission(code: "terminal:exec")]
#[SimpleResponse(schema: [], example: [])]
public function exec(Request $request): void
{
$connection = $request->connection;
$command = $request->input('command');
$uuid = $request->input('uuid', '');
$extend = $request->input('extend', '');
// 发送SSE响应头
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
'X-Accel-Buffering' => 'no',
], "\r\n"));
// 验证参数
if (empty($command) || empty($uuid)) {
$connection->send(Sse::error('参数错误:缺少command或uuid参数', null, $uuid));
return;
}
try {
$terminal = Terminal::create($uuid, $extend);
foreach ($terminal->exec($command) as $sseMessage) {
$connection->send($sseMessage);
}
} catch (\Exception $e) {
$connection->send(Sse::error('执行失败:' . $e->getMessage(), null, $uuid));
}
}
#[OA\Post(
path: "/terminal/execute",
summary: "执行命令(简单方式)",
tags: ["终端管理"]
)]
#[OA\Parameter(
name: "command_key",
description: "命令key",
in: "query",
required: true,
schema: new OA\Schema(type: "string"),
)]
#[OA\Parameter(
name: "variables",
description: "命令变量",
in: "query",
required: false,
schema: new OA\Schema(type: "object"),
)]
#[OA\Parameter(
name: "session_uuid",
description: "会话UUID",
in: "query",
required: false,
schema: new OA\Schema(type: "string"),
)]
#[Permission(code: "terminal:execute")]
#[SimpleResponse(schema: [], example: [])]
public function execute(Request $request): Response
{
try {
$commandKey = $request->post('command_key');
$variables = $request->post('variables', []);
$sessionUuid = $request->post('session_uuid', '');
if (!$commandKey) {
return Json::fail('命令不能为空');
}
$terminal = Terminal::create($sessionUuid, '');
$result = $terminal->executeSimple($commandKey, $variables, $sessionUuid);
return Json::success($result['message'], $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 将数组转换为PHP配置字符串
*
* @param array $array
* @param int $indent
*
* @return string
*/
private function arrayToPhpConfig(array $array, int $indent = 0): string
{
$spaces = str_repeat(' ', $indent);
$result = "[\n";
foreach ($array as $key => $value) {
$result .= $spaces . " ";
// 处理键名
if (is_string($key)) {
$result .= "'" . str_replace("'", "\\'", $key) . "' => ";
} else {
$result .= $key . " => ";
}
// 处理值
if (is_array($value)) {
$result .= $this->arrayToPhpConfig($value, $indent + 1);
} elseif (is_string($value)) {
$result .= "'" . str_replace("'", "\\'", $value) . "'";
} elseif (is_bool($value)) {
$result .= $value ? 'true' : 'false';
} elseif (is_null($value)) {
$result .= 'null';
} else {
$result .= $value;
}
$result .= ",\n";
}
$result .= $spaces . "]";
return $result;
}
}
@@ -0,0 +1,502 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\upload;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\schema\request\system\UploadQueryRequest;
use app\adminapi\schema\response\system\UploadResponse;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use app\service\admin\system\UploadService;
use core\exception\handler\AdminException;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use OpenApi\Attributes\RequestBody;
use support\annotation\Middleware;
use support\Request;
use Webman\RedisQueue\Client;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class FilesController extends Crud
{
public function __construct(UploadService $service)
{
$this->service = $service;
}
#[OA\Get(
path: '/files',
summary: '列表',
tags: ['附件管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => UploadQueryRequest::class,
]
)]
#[Permission(code: 'upload:files:list')]
#[PageResponse(schema: UploadResponse::class, example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/files/{id}',
summary: '详情',
tags: ['附件管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'upload:files:read')]
#[SimpleResponse(schema: UploadResponse::class, example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Delete(
path: '/files/{id}',
summary: '删除',
tags: ['附件管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'upload:files:delete')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/files',
summary: '批量删除',
tags: ['附件管理'],
x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
]
)]
#[Permission(code: 'upload:files:delete')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Post(
path: '/files/fetch-and-save-image',
summary: '上传网络图片到服务器',
tags: ['附件管理'],
)]
#[RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'url',
description: '图片URL',
type: 'string',
example: 'https://example.com/image.jpg'
),
]
)
)]
#[Permission(code: 'upload:files:fetch_and_save_image')]
#[SimpleResponse(schema: [], example: [])]
public function downloadNetworkImage(Request $request): \support\Response
{
$url = $request->input('url', '');
$result = $this->service->saveNetworkImage($url);
return Json::success('操作成功', $result);
}
#[OA\Get(
path: '/files/download-by-id/{id}',
summary: '根据ID下载资源',
tags: ['附件管理'],
parameters: [
new OA\Parameter(
name: 'id',
description: '文件ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
),
]
)]
#[Permission(code: 'upload:files:download_by_id')]
#[SimpleResponse(schema: [], example: [])]
public function downloadResourceById(Request $request): \support\Response|\Webman\Http\Response
{
try {
$id = $request->route->param('id');
$data = $this->service->get($id);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => -1]);
}
return response()->download($data->path, $data->filename);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
#[OA\Get(
path: '/files/download-by-hash/{hash}',
summary: '根据hash下载资源',
tags: ['附件管理'],
parameters: [
new OA\Parameter(
name: 'hash',
description: '文件Hash',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', example: '123456789012345678')
),
]
)]
#[Permission(code: 'upload:files:download_by_hash')]
#[SimpleResponse(schema: [], example: [])]
public function downloadResourceByHash(Request $request): \support\Response|\Webman\Http\Response
{
try {
$hash = $request->route->param('hash');
$data = $this->service->get(['hash' => $hash]);
if (empty($data)) {
throw new AdminException('数据未找到', ['errorCode' => -1]);
}
return response()->download($data->path, $data->filename);
} catch (\Throwable $e) {
return Json::fail($e->getMessage(), [], $e->getCode());
}
}
/**
* @throws \Throwable
*/
#[OA\Post(
path: '/files/upload-image',
summary: '上传图片',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:upload_image')]
#[RequestBody(
required: true,
content: new OA\MediaType(
mediaType: 'multipart/form-data',
schema: new OA\Schema(
properties: [
new OA\Property(
property: 'file',
description: '文件',
type: 'string',
format: 'binary'
),
new OA\Property(
property: 'sub_dir',
description: '子目录路径,例如:image/202603',
type: 'string',
example: 'image/202603'
),
]
)
)
)]
#[SimpleResponse(schema: UploadResponse::class, example: [])]
public function uploadImage(Request $request): \support\Response
{
try {
$subDir = $request->input('sub_dir', 'image');
$subDir .= '/' . date('Ym');
$result = $this->service->upload($subDir);
return Json::success('ok', $result->toArray());
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/files/upload-file',
summary: '上传文件',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:upload_file')]
#[RequestBody(
required: true,
content: new OA\MediaType(
mediaType: 'multipart/form-data',
schema: new OA\Schema(
properties: [
new OA\Property(
property: 'file',
description: '文件',
type: 'string',
format: 'binary'
),
new OA\Property(
property: 'sub_dir',
description: '子目录路径,例如:file/202603',
type: 'string',
example: 'file/202603'
),
]
)
)
)]
#[SimpleResponse(schema: UploadResponse::class, example: [])]
public function uploadFile(Request $request): \support\Response
{
try {
$subDir = $request->input('sub_dir', 'file');
$subDir .= '/' . date('Ym');
$result = $this->service->upload($subDir);
return Json::success('ok', $result);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/files/svg-icons',
summary: 'SVG图标列表',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:upload_file')]
#[SimpleResponse(schema: [], example: [])]
public function svgIcons(Request $request): \support\Response
{
try {
$targetDir = public_path() . '/upload/svg-icons';
if (!is_dir($targetDir)) {
return Json::success('ok', []);
}
$icons = [];
foreach (glob($targetDir . '/*.svg') ?: [] as $file) {
$name = pathinfo($file, PATHINFO_FILENAME);
$content = file_get_contents($file);
if ($content === false) {
continue;
}
$icons[] = [
'icon' => 'svg:' . $name,
'name' => $name,
'filename' => basename($file),
'url' => '/upload/svg-icons/' . basename($file),
'content' => $content,
];
}
return Json::success('ok', $icons);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/files/upload-svg-icon',
summary: '上传SVG图标',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:upload_file')]
#[RequestBody(
required: true,
content: new OA\MediaType(
mediaType: 'multipart/form-data',
schema: new OA\Schema(
properties: [
new OA\Property(
property: 'file',
description: 'SVG图标文件',
type: 'string',
format: 'binary'
),
]
)
)
)]
#[SimpleResponse(schema: [], example: [])]
public function uploadSvgIcon(Request $request): \support\Response
{
try {
$file = $request->file('file');
if (!$file) {
return Json::fail('请选择SVG文件');
}
if ($file->getSize() > 512 * 1024) {
return Json::fail('SVG文件大小不能超过512KB');
}
$extension = strtolower($file->getUploadExtension());
$mime = $file->getUploadMimeType();
$allowedMimeTypes = ['image/svg+xml', 'image/svg', 'text/xml', 'application/xml', 'text/plain', ''];
if ($extension !== 'svg' || !in_array($mime, $allowedMimeTypes, true)) {
return Json::fail('只支持SVG格式文件');
}
$content = file_get_contents($file->getPathname());
if ($content === false || trim($content) === '') {
return Json::fail('SVG文件读取失败');
}
if (!preg_match('/<svg[\s>]/i', $content)) {
return Json::fail('请选择有效的SVG文件');
}
if (preg_match('/<script|<foreignObject|on\w+\s*=|javascript:/i', $content)) {
return Json::fail('SVG文件包含不安全内容');
}
$originalName = pathinfo($file->getUploadName(), PATHINFO_FILENAME);
$iconName = preg_replace('/[^a-zA-Z0-9_-]/', '-', $originalName) ?: 'icon';
$iconName = trim($iconName, '-_') ?: 'icon';
$targetDir = public_path() . '/upload/svg-icons';
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
return Json::fail('SVG图标目录创建失败');
}
$targetPath = $targetDir . '/' . $iconName . '.svg';
if (is_file($targetPath)) {
$iconName .= '-' . date('YmdHis');
$targetPath = $targetDir . '/' . $iconName . '.svg';
}
if (file_put_contents($targetPath, $content, LOCK_EX) === false) {
return Json::fail('SVG图标保存失败');
}
return Json::success('上传成功', [
'icon' => 'svg:' . $iconName,
'name' => $iconName,
'filename' => $iconName . '.svg',
'url' => '/upload/svg-icons/' . $iconName . '.svg',
'path' => str_replace('\\', '/', $targetPath),
'content' => $content,
]);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
/**
* 上传图片返回base64
* 用于插件开发等需要base64预览的场景
*/
#[OA\Post(
path: '/files/upload-image-base64',
summary: '上传图片返回base64',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:upload_image_base64')]
#[RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'file',
description: '图片文件',
type: 'string',
format: 'binary'
),
]
)
)]
public function uploadImageBase64(Request $request): \support\Response
{
try {
$file = $request->file('file');
if (!$file) {
return Json::fail('请选择图片文件');
}
// 验证文件类型
$allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
if (!in_array($file->getUploadMimeType(), $allowedTypes)) {
return Json::fail('只支持PNG、JPEG、JPG、GIF格式的图片');
}
// 验证文件大小(最大2MB)
$maxSize = 2 * 1024 * 1024;
if ($file->getSize() > $maxSize) {
return Json::fail('图片大小不能超过2MB');
}
// 读取文件内容并转换为base64
$imageData = file_get_contents($file->getPathname());
if ($imageData === false) {
return Json::fail('图片读取失败');
}
$base64 = 'data:' . $file->getUploadMimeType() . ';base64,' . base64_encode($imageData);
return Json::success('上传成功', [
'base64' => $base64,
'size' => $file->getSize(),
'mime' => $file->getUploadMimeType(),
]);
} catch (\Exception $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/export/download-excel',
summary: '下载导出的excel',//下载导出的excel-后期需要优化实现下载成功后删除原文件
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:download_excel')]
public function downloadExcel(Request $request): ?\support\Response
{
$param = $request->all();
$file = $param['file_path'];//文件路径
$downloadName = $param['file'] ?? date('Y-m-d His', time());//文件下载名称
$filePath = runtime_path() . $file;
//生成的文件5分钟后超时自动删除
$queue = 'remove-excel-file';
Client::send($queue, $param, 300);
return response()->download($filePath, $downloadName);
}
#[OA\Post(
path: '/files/common/wangeditor',
summary: '适配wangeditor编辑器上传接口',
tags: ['附件管理'],
)]
#[Permission(code: 'upload:files:wangeditor')]
public function wangeditor(Request $request): \support\Response
{
try {
$subDir = $request->input('sub_dir', 'image');
$subDir .= '/' . date('Ym');
$result = $this->service->upload($subDir);
$data = $result->toArray();
$data['url'] = $data['base_path'] ?? '';
return Json(['errno' => 0, 'data' => $data]);
} catch (\Exception $e) {
return Json([
'errno' => 1,
'message' => $e->getMessage(),
]);
}
}
}
@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\web;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\web\LinkValidate;
use app\schema\request\IdRequest;
use app\service\admin\web\LinkService;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class LinkController extends Crud
{
public function __construct(LinkService $service, LinkValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/web/link',
summary: '友情链接列表',
tags: ['友情链接管理'],
parameters: [
new OA\Parameter(name: "name", description: "链接名称", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "status", description: "状态", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "获取成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "total", type: "integer", example: 10),
new OA\Property(property: "items", type: "array", items: new OA\Items(properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "name", type: "string", example: "百度"),
new OA\Property(property: "url", type: "string", example: "https://www.baidu.com"),
new OA\Property(property: "logo", type: "string", example: ""),
new OA\Property(property: "logo_type", type: "integer", example: 2, description: "图标类型:1=上传,2=链接"),
new OA\Property(property: "sort", type: "integer", example: 1),
new OA\Property(property: "enabled", type: "integer", example: 1),
])),
], type: "object"),
])
),
]
)]
#[Permission("web:link:list")]
#[SimpleResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, [], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/web/link/{id}',
summary: '友情链接详情',
tags: ['友情链接管理'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("web:link:read")]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/web/link',
summary: '创建友情链接',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "百度"),
new OA\Property(property: "url", type: "string", example: "https://www.baidu.com"),
new OA\Property(property: "logo", type: "string", example: ""),
new OA\Property(property: "logo_type", type: "integer", example: 2, description: "图标类型:1=上传,2=链接"),
new OA\Property(property: "sort", type: "integer", example: 1),
new OA\Property(property: "enabled", type: "integer", example: 1),
])
),
tags: ['友情链接管理'],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "创建成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
],
)]
#[Permission("web:link:create")]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/web/link/{id}',
summary: '更新友情链接',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "百度"),
new OA\Property(property: "url", type: "string", example: "https://www.baidu.com"),
new OA\Property(property: "logo", type: "string", example: ""),
new OA\Property(property: "logo_type", description: "图标类型:1=上传,2=链接", type: "integer", example: 2),
new OA\Property(property: "sort", type: "integer", example: 1),
new OA\Property(property: "enabled", type: "integer", example: 1),
])
),
tags: ['友情链接管理'],
parameters: [
new OA\Parameter(name: "id", description: "链接ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "更新成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
]
)]
#[Permission("web:link:update")]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/web/link/{id}',
summary: '删除友情链接',
tags: ['友情链接管理'],
parameters: [
new OA\Parameter(name: "id", description: "链接ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "删除成功"),
])
),
]
)]
#[Permission("web:link:delete")]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\controller\web;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\AccessTokenMiddleware;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\adminapi\validate\web\MenuValidate;
use app\schema\request\IdRequest;
use app\service\admin\web\MenuService;
use madong\swagger\attribute\Permission;
use core\tool\Json;
use madong\swagger\annotation\response\SimpleResponse;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
final class MenuController extends Crud
{
public function __construct(MenuService $service, MenuValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/web/menu',
summary: '菜单列表',
tags: ['菜单管理'],
parameters: [
new OA\Parameter(name: "name", description: "菜单名称", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "type", description: "菜单类型", in: "query", schema: new OA\Schema(type: "string")),
new OA\Parameter(name: "page", description: "页码", in: "query", schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "limit", description: "每页数量", in: "query", schema: new OA\Schema(type: "integer")),
]
)]
#[Permission("web:menu:list")]
#[SimpleResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
if (empty($order)) {
$order = 'sort asc';
}
$format_function = $methods[$format] ?? 'formatNormal';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, [], false);
return call_user_func([$this, $format_function], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(
path: '/web/menu/{id}',
summary: '菜单详情',
tags: ['菜单管理'],
parameters: [
new OA\Parameter(name: "id", description: "菜单ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "获取成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "name", type: "string", example: "首页"),
new OA\Property(property: "type", type: "string", example: "page"),
new OA\Property(property: "path", type: "string", example: "/home"),
new OA\Property(property: "title", type: "string", example: "首页"),
new OA\Property(property: "url", type: "string", example: ""),
new OA\Property(property: "icon", type: "string", example: "home"),
new OA\Property(property: "sort", type: "integer", example: 1),
], type: "object"),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("web:menu:read")]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/web/menu',
summary: '创建',
tags: ['菜单管理'],
)]
#[Permission("web:menu:create")]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/web/menu/{id}',
summary: '更新菜单',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(properties: [
new OA\Property(property: "name", type: "string", example: "首页"),
new OA\Property(property: "type", type: "string", example: "page"),
new OA\Property(property: "path", type: "string", example: "/home"),
new OA\Property(property: "title", type: "string", example: "首页"),
new OA\Property(property: "url", type: "string", example: ""),
new OA\Property(property: "icon", type: "string", example: "home"),
new OA\Property(property: "sort", type: "integer", example: 1),
])
),
tags: ['菜单管理'],
parameters: [
new OA\Parameter(name: "id", description: "菜单ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "更新成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "id", type: "integer", example: 1),
], type: "object"),
])
),
]
)]
#[Permission("web:menu:update")]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(
path: '/web/menu',
summary: '删除菜单',
tags: ['菜单管理'],
parameters: [
new OA\Parameter(name: "id", description: "菜单ID", in: "path", required: true, schema: new OA\Schema(type: "integer")),
],
responses: [
new OA\Response(
response: 200,
description: "成功",
content: new OA\JsonContent(properties: [
new OA\Property(property: "code", type: "integer", example: 0),
new OA\Property(property: "msg", type: "string", example: "删除成功"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "ids", type: "array", items: new OA\Items(type: "integer")),
]),
])
),
],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission("web:menu:delete")]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$ids = $request->input('ids',[]);
$this->service->batchDelete($ids);
return Json::success('删除成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use Webman\Event\Event;
/**
* 登录日志事件
*/
class LoginLogEvent
{
/**
* 操作名称
*/
public string $name;
/**
* 应用名称
*/
public string $app;
/**
* IP地址
*/
public string $ip;
/**
* IP归属地
*/
public string $ipLocation;
/**
* 浏览器
*/
public string $browser;
/**
* 操作系统
*/
public string $os;
/**
* 状态
*/
public int $status;
/**
* 消息
*/
public string $message;
/**
* 用户名
*/
public string $userName;
/**
* 用户ID
*/
public string|null|int $userId;
/**
* 登录时间
*/
public int $loginTime;
/**
* 访问令牌
*/
public string $accessToken;
/**
* 过期时间
*/
public int $expiresAt;
/**
* 构造函数
*/
public function __construct(
string $name,
string $app,
string $ip,
string $ipLocation,
string $browser,
string $os,
int $status,
string $message,
string $userName,
string|null|int $userId,
int $loginTime,
string $accessToken,
int $expiresAt
) {
$this->name = $name;
$this->app = $app;
$this->ip = $ip;
$this->ipLocation = $ipLocation;
$this->browser = $browser;
$this->os = $os;
$this->status = $status;
$this->message = $message;
$this->userName = $userName;
$this->userId = $userId;
$this->loginTime = $loginTime;
$this->accessToken = $accessToken;
$this->expiresAt = $expiresAt;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.login.log', $this);
}
}
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use Webman\Event\Event;
/**
* 会员等级更新事件
*
* 用于处理会员等级变动的场景:
* - 积分增加导致等级提升
* - 积分扣减导致等级下降
* - 手动调整会员等级
*/
class MemberLevelUpdatedEvent
{
/**
* 会员ID
*/
public int|string $memberId;
/**
* 变动前等级ID
*/
public int|string|null $oldLevelId;
/**
* 变动后等级ID
*/
public int|string|null $newLevelId;
/**
* 变动前等级名称
*/
public ?string $oldLevelName;
/**
* 变动后等级名称
*/
public ?string $newLevelName;
/**
* 触发原因(points_increase=积分增加, points_deducted=积分扣减, admin=后台调整, other=其他)
*/
public string $reason;
/**
* 备注
*/
public string $remark;
/**
* 额外数据(可选,用于存储其他相关信息)
*/
public ?array $extra;
/**
* 构造函数
*/
public function __construct(
int|string $memberId,
int|string|null $oldLevelId,
int|string|null $newLevelId,
?string $oldLevelName,
?string $newLevelName,
string $reason,
string $remark = '',
?array $extra = null
) {
$this->memberId = $memberId;
$this->oldLevelId = $oldLevelId;
$this->newLevelId = $newLevelId;
$this->oldLevelName = $oldLevelName;
$this->newLevelName = $newLevelName;
$this->reason = $reason;
$this->remark = $remark;
$this->extra = $extra;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.member.level.updated', $this);
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use Illuminate\Support\Collection;
use Webman\Event\Event;
/**
* 菜单格式化事件
*/
class MenuFormattingEvent
{
/**
* 菜单数据
*/
public Collection|null $data;
/**
* 格式化类型
*/
public string $formatType;
/**
* 结果
*/
public array $result = [];
/**
* 构造函数
*/
public function __construct(
Collection|null $data,
string $formatType
) {
$this->data = $data;
$this->formatType = $formatType;
}
/**
* 触发事件
*/
public function dispatch(): array
{
Event::emit('adminapi.menu.formatting', $this);
return $this->result;
}
}
@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use Webman\Event\Event;
/**
* 操作日志事件
*/
class OperationLogEvent
{
/**
* 操作名称
*/
public string $name;
/**
* OA 标签
*/
public array $oaTags;
/**
* OA 描述
*/
public string $oaDescription;
/**
* 应用名称
*/
public string $app;
/**
* IP地址
*/
public string $ip;
/**
* IP归属地
*/
public string $ipLocation;
/**
* 浏览器
*/
public string $browser;
/**
* 操作系统
*/
public string $os;
/**
* 请求URL
*/
public string $url;
/**
* 控制器类名
*/
public string $className;
/**
* 操作方法
*/
public string $action;
/**
* 请求方法
*/
public string $method;
/**
* 请求参数
*/
public string $param;
/**
* 响应结果
*/
public array|string $result;
/**
* 用户名
*/
public string $userName;
/**
* 用户ID
*/
public int $userId;
/**
* 构造函数
*/
public function __construct(
string $name,
array $oaTags,
string $oaDescription,
string $app,
string $ip,
string $ipLocation,
string $browser,
string $os,
string $url,
string $className,
string $action,
string $method,
string $param,
array|string $result,
string $userName,
int $userId
) {
$this->name = $name;
$this->oaTags = $oaTags;
$this->oaDescription = $oaDescription;
$this->app = $app;
$this->ip = $ip;
$this->ipLocation = $ipLocation;
$this->browser = $browser;
$this->os = $os;
$this->url = $url;
$this->className = $className;
$this->action = $action;
$this->method = $method;
$this->param = $param;
$this->result = $result;
$this->userName = $userName;
$this->userId = $userId;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.operation.log', $this);
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use app\enum\member\PointType;
use app\enum\member\PointSource;
use Webman\Event\Event;
/**
* 积分变动事件
*
* 统一的积分变动事件,用于处理积分的增加和减少
*/
class PointsChangedEvent
{
/**
* 会员ID
*/
public int|string $memberId;
/**
* 变动类型
*/
public PointType $type;
/**
* 变动前积分
*/
public int $oldPoints;
/**
* 变动后积分
*/
public int $newPoints;
/**
* 变动的积分数量
*/
public int $points;
/**
* 积分来源
*/
public PointSource $source;
/**
* 变动原因/备注
*/
public string $remark;
/**
* 关联ID(如订单ID、活动ID等,可选)
*/
public int|string|null $relatedId;
/**
* 额外数据(可选,用于存储其他相关信息)
*/
public ?array $extra;
/**
* 构造函数
*/
public function __construct(
int|string $memberId,
int $points,
PointSource $source,
PointType $type,
int $oldPoints,
int $newPoints,
string $remark = '',
int|string|null $relatedId = null,
?array $extra = null
) {
$this->memberId = $memberId;
$this->points = $points;
$this->source = $source;
$this->type = $type;
$this->oldPoints = $oldPoints;
$this->newPoints = $newPoints;
$this->remark = $remark;
$this->relatedId = $relatedId;
$this->extra = $extra;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.points.changed', $this);
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use app\enum\review\ReviewStatus;
use app\model\review\Review;
use Webman\Event\Event;
/**
* 审核通过事件
*/
class ReviewApprovedEvent
{
/**
* 审核记录
*/
public Review $review;
/**
* 额外数据
*/
public array $data;
/**
* 构造函数
*/
public function __construct(Review $review, array $data = [])
{
$this->review = $review;
$this->data = $data;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.review.approved', $this);
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use app\model\review\Review;
use Webman\Event\Event;
/**
* 审核创建事件
*/
class ReviewCreatedEvent
{
/**
* 审核记录
*/
public Review $review;
/**
* 额外数据
*/
public array $data;
/**
* 构造函数
*/
public function __construct(Review $review, array $data = [])
{
$this->review = $review;
$this->data = $data;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.review.created', $this);
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\event;
use app\model\review\Review;
use Webman\Event\Event;
/**
* 审核拒绝事件
*/
class ReviewRejectedEvent
{
/**
* 审核记录
*/
public Review $review;
/**
* 额外数据
*/
public array $data;
/**
* 构造函数
*/
public function __construct(Review $review, array $data = [])
{
$this->review = $review;
$this->data = $data;
}
/**
* 触发事件
*/
public function dispatch(): void
{
Event::emit('adminapi.review.rejected', $this);
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\LoginLogEvent;
use app\dao\logs\LoginLogDao;
use support\Container;
/**
* 登录日志监听器
* 处理登录日志事件,将登录记录保存到数据库
*/
class LoginLogListener
{
/**
* 处理登录日志事件
*
* @param LoginLogEvent $event
*
* @return void
* @throws \Exception
*/
public function handle(LoginLogEvent $event): void
{
// 保存登录日志到数据库
$this->saveLoginLog($event);
}
/**
* 保存登录日志
*
* @param LoginLogEvent $event
*
* @return void
* @throws \Exception
*/
private function saveLoginLog(LoginLogEvent $event): void
{
/** @var LoginLogDao $dao */
$dao = Container::make(LoginLogDao::class);
$dao->save([
'user_id' => $event->userId,
'ip' => $event->ip,
'ip_location' => $event->ipLocation,
'os' => $event->os,
'browser' => $event->browser,
'status' => $event->status,
'message' => $event->message,
'login_time' => $event->loginTime,
'key' => md5($event->accessToken),
'expires_at' => $event->expiresAt,
'remark' => $event->status == 1 ? '登录成功' : '登录失败',
]);
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\MemberLevelUpdatedEvent;
/**
* 会员等级更新监听器
*
* 处理会员等级更新的事件:
* 1. 发送等级变更通知
* 2. 记录等级变更日志
*/
class MemberLevelUpdatedListener
{
/**
* 处理会员等级更新事件
*/
public function handle(MemberLevelUpdatedEvent $event): void
{
// 发送等级变更通知(如果需要)
// $this->sendLevelChangeNotification($event);
// 记录等级变更日志(如果需要)
// $this->recordLevelChangeLog($event);
// 其他等级更新相关的处理
$this->handleLevelChange($event);
}
/**
* 处理等级变更
*/
private function handleLevelChange(MemberLevelUpdatedEvent $event): void
{
// 这里可以添加等级变更后的处理逻辑
// 例如:
// 1. 给会员发送等级提升的奖励
// 2. 更新会员的特权
// 3. 记录等级变更的历史
// 示例:如果是等级提升,可以发送通知
if ($event->oldLevelId && $event->newLevelId) {
// 等级变更的处理
echo "会员 {$event->memberId} 等级从 {$event->oldLevelName} 变更为 {$event->newLevelName}\n";
} elseif (!$event->oldLevelId && $event->newLevelId) {
// 首次获得等级的处理
echo "会员 {$event->memberId} 获得等级 {$event->newLevelName}\n";
} elseif ($event->oldLevelId && !$event->newLevelId) {
// 失去等级的处理
echo "会员 {$event->memberId} 失去等级 {$event->oldLevelName}\n";
}
}
}
@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\MenuFormattingEvent;
use madong\helper\Tree;
/**
* 菜单格式化监听器
*/
class MenuFormattingListener
{
/**
* 处理菜单格式化事件
*
* @param MenuFormattingEvent $event
*
* @return void
*/
public function handle(MenuFormattingEvent $event): void
{
switch ($event->formatType) {
case 'vben':
$event->result = $this->formatForVben($event->data);
break;
case 'art':
$event->result = $this->formatForArt($event->data);
break;
case 'tree':
case 'default':
$event->result = $this->formatForTree($event->data);
break;
default:
$event->result = [];
}
}
/**
* 基础树形结构格式化(用于Vben Ui)
*
* @param \Illuminate\Support\Collection|null $data
*
* @return array
*/
private function formatForVben(null|\Illuminate\Support\Collection $data): array
{
if (empty($data)) {
return [];
}
$filteredData = $data->filter(function ($item) {
return !in_array($item->type, [3, 4]);
})->map(function ($item) {
$meta = [
// 基础信息
'title' => $item->title,
'icon' => $item->icon ?? null,
'order' => $item->sort ?? 0,
// 显示控制
'hideInMenu' => !$item->is_show ?? false,
'hideInTab' => $item->is_hide_tab ?? false,
'affixTab' => $item->is_affix ?? false, // 固定标签页
// 权限控制
'authority' => $item->variable ? explode(',', $item->variable) : [],
// 功能控制
'keepAlive' => $item->is_cache ?? false, // 缓存
// 链接相关
'link' => $item->type == 6 ? $item->link_url : null, // 外链(6)使用link_url
'iframeSrc' => $item->is_frame ? $item->link_url : null, // iframe链接
'openInNewWindow' => $item->open_type === '_blank' ?? false, // 新窗口打开
];
// 3. 徽章配置(如有文本徽章)
if (!empty($item->show_text_badge)) {
$meta['badge'] = $item->show_text_badge;
$meta['badgeType'] = 'normal';
$meta['badgeVariants'] = 'default';
}
// 4. 返回格式化后的菜单项结构
return [
'id' => $item->id,
'pid' => $item->pid,
'path' => $item->path,
'name' => $item->code,
'component' => $item->component,
'redirect' => $item->redirect ?? '',
'meta' => $meta,
];
})->toArray();
$tree = new Tree($filteredData);
return $tree->getTree();
}
/**
* 基础树形结构格式化(用于Art Ui)
*
* @param \Illuminate\Support\Collection|null $data
*
* @return array
*/
private function formatForArt(null|\Illuminate\Support\Collection $data): array
{
if (empty($data)) {
//可以输出默认主页菜单
return [];
}
$items = $data->all();
$grouped = collect($items)->groupBy('pid')->all();
$buildMenu = function ($parentId = 0) use (&$buildMenu, $grouped) {
$menuItems = [];
$currentItems = $grouped[$parentId] ?? collect();
foreach ($currentItems as $item) {
// 跳过按钮(3)和接口(4)类型,它们不直接作为菜单项
if (in_array($item->type, [3, 4])) {
continue;
}
// 提取当前菜单项下的接口(4)和按钮(3)作为authList
$authItems = $grouped[$item->id] ?? collect();
$authList = $authItems->filter(function ($child) {
return in_array($child->type, [3, 4]);
})->map(function ($authItem) {
return [
'title' => $authItem->title,//可以优化多语言
'authMark' => $authItem->code ?? $authItem->path,
];
})->all();
$meta = [
'title' => $item->title,
'icon' => $item->icon ?? null,
'isHide' => !$item->is_show ?? false, // (0=隐藏,1=显示)
'isHideTab' => $item->is_affix ?? false, // 否隐藏标签页
'link' => in_array($item->type, [5, 6]) ? $item->link_url : null, //外链使用link_url字段
'isIframe' => $item->is_frame ?? false, // 修正:is_frame对应是否为iframe
'keepAlive' => $item->is_cache ?? false, // 修正:is_cache对应是否缓存
'authList' => $authList,
'isFirstLevel' => $parentId === 0, // 顶级菜单标记为一级菜单
'roles' => $item->variable ? explode(',', $item->variable) : [], // 假设variable存储角色列表(逗号分隔)
];
// 插件菜单添加 module 字段,值为 app 字段
if (($item->source ?? '') === 'plugin' && !empty($item->app)) {
$meta['module'] = $item->app;
}
$component = $item->component;
$path = $item->path;
if ($item->type === 1 && $parentId === 0) {
$component = '/layout';
$path = $path ?: '#'; // 目录默认路径为#
}
$children = $buildMenu($item->id);
$menuItems[] = [
'id' => $item->id,
'path' => $path,
'name' => $item->code,
'component' => $component,
'meta' => $meta,
'children' => $children,
];
}
return $menuItems;
};
return $buildMenu();
}
/**
* 基础树形结构格式化(用于权限设置等场景)
*
* @param \Illuminate\Support\Collection|null $data
*
* @return array
*/
private function formatForTree(null|\Illuminate\Support\Collection $data): array
{
if (empty($data)) {
return [];
}
$tree = new Tree($data->toArray());
return $tree->getTree();
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\OperationLogEvent;
use app\dao\logs\OperateLogDao;
use support\Container;
/**
* 操作日志监听器
*
* 处理操作日志事件,将操作记录保存到数据库
*/
class OperationLogListener
{
/**
* 处理操作日志事件
*
* @param OperationLogEvent $event
* @return void
* @throws \Exception
*/
public function handle(OperationLogEvent $event): void
{
// 保存操作日志到数据库
$this->saveOperateLog($event);
}
/**
* 保存操作日志
*
* @param OperationLogEvent $event
* @return void
* @throws \Exception
*/
private function saveOperateLog(OperationLogEvent $event): void
{
/** @var OperateLogDao $dao */
$dao = Container::make(OperateLogDao::class);
$dao->save([
'name' => $event->name,
'app' => $event->app,
'ip' => $event->ip,
'ip_location' => $event->ipLocation,
'browser' => $event->browser,
'os' => $event->os,
'url' => $event->url,
'class_name' => $event->className,
'action' => $event->action,
'method' => $event->method,
'param' => $event->param,
'result' => is_array($event->result) ? json_encode($event->result, JSON_UNESCAPED_UNICODE) : $event->result,
'user_name' => $event->userName,
]);
}
}
@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\PointsChangedEvent;
use app\adminapi\event\MemberLevelUpdatedEvent;
use app\dao\member\MemberDao;
use app\dao\member\MemberLevelDao;
use app\dao\member\MemberPointsDao;
use app\model\member\MemberLevel;
use app\enum\member\PointType;
use app\enum\common\EnabledStatus;
use support\Container;
/**
* 积分变动监听器
*
* 处理积分变动的事件:
* 1. 更新会员积分
* 2. 记录积分流水
* 3. 触发会员等级更新事件
*/
class PointsChangedListener
{
/**
* 处理积分变动事件
*
* @throws \Exception
*/
public function handle(PointsChangedEvent $event): void
{
// 更新会员积分
$this->updateMemberPoints($event);
// 记录积分流水
$this->recordPointsLog($event);
// 触发会员等级更新事件
$this->updateMemberLevel($event);
}
/**
* 更新会员积分
*/
private function updateMemberPoints(PointsChangedEvent $event): void
{
/** @var MemberDao $dao */
$dao = Container::make(MemberDao::class);
$member = $dao->get($event->memberId);
if (!$member) {
return;
}
$member->points = $event->newPoints;
$member->save();
}
/**
* 记录积分流水
*
* @throws \Exception
*/
private function recordPointsLog(PointsChangedEvent $event): void
{
/** @var MemberPointsDao $dao */
$dao = Container::make(MemberPointsDao::class);
$dao->save([
'member_id' => $event->memberId,
'type' => $event->type->value,
'points' => $event->points,
'balance' => $event->newPoints,
'remark' => $event->remark,
'related_id' => $event->relatedId
]);
}
/**
* 更新会员等级
*
* @throws \Exception
*/
private function updateMemberLevel(PointsChangedEvent $event): void
{
$memberId = $event->memberId;
$newPoints = $event->newPoints;
// 获取会员对象
/** @var MemberDao $dao */
$dao = Container::make(MemberDao::class);
$member = $dao->get($memberId);
if (!$member) {
return;
}
$oldLevelId = $member->level_id;
$oldLevelName = null;
/** @var MemberLevelDao $memberLevelDao */
$memberLevelDao = Container::make(MemberLevelDao::class);
// 获取旧等级名称
if ($oldLevelId) {
$oldLevel = $memberLevelDao->get($oldLevelId);
if ($oldLevel) {
$oldLevelName = $oldLevel->name;
}
}
// 根据新积分获取等级
$newLevel = $memberLevelDao->query()
->where('enabled', EnabledStatus::ENABLED->value)
->where('min_points', '<=', $newPoints)
->where(function ($query) use ($newPoints) {
$query->where('max_points', '>=', $newPoints)
->orWhere('max_points', 0);
})
->orderBy('level', 'desc')
->first();
// 如果等级存在且与当前等级不同,更新等级
if ($newLevel && $newLevel->id != $member->level_id) {
$member->level_id = $newLevel->id;
$member->save();
$levelEvent = new MemberLevelUpdatedEvent(
$memberId,
$oldLevelId,
$newLevel->id,
$oldLevelName,
$newLevel->name,
$event->type === PointType::INCREASE ? 'points_increase' : 'points_deducted',
''
);
$levelEvent->dispatch();
}
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\ReviewApprovedEvent;
use app\enum\review\ReviewStatus;
use app\model\review\Review;
use support\Log;
/**
* 审核通过事件监听器
*/
class ReviewApprovedListener
{
/**
* 处理审核通过事件
*
* @param ReviewApprovedEvent $event
*
* @return void
*/
public function handle(ReviewApprovedEvent $event): void
{
try {
$review = $event->review;
// 记录日志
Log::channel('default')->info('审核通过', [
'review_id' => $review->id,
'reviewable_type' => $review->reviewable_type,
'reviewable_id' => $review->reviewable_id,
'reviewer_id' => $review->reviewer_id,
]);
// 根据不同的审核类型处理业务逻辑
$this->handleReviewable($review, ReviewStatus::APPROVED->value);
} catch (\Throwable $e) {
Log::channel('default')->error('审核通过事件处理失败', [
'error' => $e->getMessage(),
]);
}
}
/**
* 更新审核记录状态
*
* @param Review $review 审核记录
* @param int $status 审核状态
*/
protected function handleReviewable(Review $review, int $status): void
{
$review->status = $status;
$review->reviewed_at = time();
$review->save();
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\ReviewCreatedEvent;
use support\Log;
/**
* 审核创建事件监听器
*/
class ReviewCreatedListener
{
/**
* 处理审核创建事件
*
* @param ReviewCreatedEvent $event
*
* @return void
*/
public function handle(ReviewCreatedEvent $event): void
{
try {
$review = $event->review;
// 记录日志
Log::channel('default')->info('创建审核记录', [
'review_id' => $review->id,
'reviewable_type' => $review->reviewable_type,
'reviewable_id' => $review->reviewable_id,
]);
} catch (\Throwable $e) {
Log::channel('default')->error('审核创建事件处理失败', [
'error' => $e->getMessage(),
]);
}
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\listener;
use app\adminapi\event\ReviewRejectedEvent;
use app\model\review\Review;
use support\Log;
/**
* 审核拒绝事件监听器
*/
class ReviewRejectedListener
{
/**
* 处理审核拒绝事件
*
* @param ReviewRejectedEvent $event
*
* @return void
*/
public function handle(ReviewRejectedEvent $event): void
{
try {
$review = $event->review;
$reason = $event->data['reason'] ?? '';
// 记录日志
Log::channel('default')->info('审核拒绝', [
'review_id' => $review->id,
'reviewable_type' => $review->reviewable_type,
'reviewable_id' => $review->reviewable_id,
'reviewer_id' => $review->reviewer_id,
'reason' => $reason,
]);
// 根据不同的审核类型处理业务逻辑
$this->handleReviewable($review, 2, $reason);
} catch (\Throwable $e) {
Log::channel('default')->error('审核拒绝事件处理失败', [
'error' => $e->getMessage(),
]);
}
}
/**
* 更新审核记录状态
*
* @param Review $review 审核记录
* @param int $status 审核状态
* @param string $reason 拒绝原因
*/
protected function handleReviewable(Review $review, int $status, string $reason = ''): void
{
$review->status = $status;
$review->reviewed_at = time();
$review->reason = $reason;
$review->save();
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\middleware;
use app\adminapi\middleware\helper\SseHelper;
use core\exception\handler\UnauthorizedHttpException;
use core\jwt\JwtToken;
use core\tool\Json;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\helper\AnnotationHelper;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* AccessToken 中间件(JWT Token 验证)
* 功能:
* 1. 检查请求是否需要跳过 Token 验证(通过 AllowAnonymous 注解)
* 2. 验证 JWT Token 有效性,无效则返回 401
*/
#\[\Attribute\]
final class AccessTokenMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
$route = $request->route;
if (!$route || !isset($request->action)) {
return $handler($request);
}
$controllerClass = $request->controller;
$action = $request->action;
$skipAuth = AnnotationHelper::getMethodAnnotation($controllerClass, $action, AllowAnonymous::class);
if ($skipAuth && !$skipAuth->requireToken) {
return $handler($request);
}
try {
$jwt = new JwtToken();
$payload = $jwt->verify();
$userId = $payload['id'] ?? null;
if (empty($userId)) {
throw new UnauthorizedHttpException();
}
$jwt->touchCurrentToken();
} catch (\Exception $e) {
// 检查是否为SSE请求
if (SseHelper::isSseRequest($request)) {
return SseHelper::sendSseErrorViaConnection($request, $e->getMessage());
}
return Json::fail($e->getMessage(), [], 401);
}
return $handler($request);
}
}
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\middleware;
use app\adminapi\CurrentUser;
use app\adminapi\event\OperationLogEvent;
use madong\helper\Arr;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\helper\AnnotationHelper;
use support\Container;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
#[\Attribute]
final class OperationMiddleware implements MiddlewareInterface
{
private CurrentUser $currentUser;
public function __construct()
{
$this->currentUser = Container::make(CurrentUser::class);
}
public function process(Request $request, callable $handler): Response
{
$controllerClass = $request->controller;
$action = $request->action;
$oaAnnotations = AnnotationHelper::getMethodOaAnnotations($controllerClass, $action);
$oaData = $this->extractOaData($oaAnnotations);
$firstOa = Arr::first($oaData);
$response = $handler($request);
// 检查是否需要记录操作日志(跳过匿名访问)
$allowAnonymous = AnnotationHelper::getMethodAnnotation($controllerClass, $action, AllowAnonymous::class);
if (!$allowAnonymous || $allowAnonymous->requirePermission) {
$logData = $this->buildLogData($request, $response, $firstOa);
$event = new OperationLogEvent(
$logData['name'],
$logData['oa_tags'],
$logData['oa_description'],
$logData['app'],
$logData['ip'],
$logData['ip_location'],
$logData['browser'],
$logData['os'],
$logData['url'],
$logData['class_name'],
$logData['action'],
$logData['method'],
$logData['param'],
$logData['result'],
$logData['user_name'],
$logData['user_id']
);
$event->dispatch();
}
return $response;
}
private function extractOaData(array $oaAnnotations): array
{
return array_map(function ($annotation) {
return [
'type' => $annotation::class, // 注解类型(如 OpenApi\Attributes\Get)
'summary' => $annotation->summary, // 操作摘要
'tags' => $annotation->tags, // 标签
'description' => $annotation->description ?? '', // 描述
];
}, $oaAnnotations);
}
private function buildLogData(Request $request, Response $response, ?array $firstOa): array
{
$adminInfo = $this->currentUser->admin();
return [
'name' => $firstOa['summary'] ?? '未知操作', // 用 OA summary 作为操作名称
'oa_tags' => $firstOa['tags'] ?? [], // OA 标签
'oa_description' => $firstOa['description'] ?? '', // OA 描述
'app' => $request->app,
'ip' => $request->getRealIp(),
'ip_location' => $this->getIpLocation($request->getRealIp()), // 可选:IP 归属地
'browser' => $this->getBrowser($request->header('user-agent')),
'os' => $this->getOs($request->header('user-agent')),
'url' => trim($request->path()),
'class_name' => $request->controller,
'action' => $request->action,
'method' => $request->method(),
'param' => $this->filterParams($request->all()), // 过滤后的参数(数组)
'result' => $this->formatResponse($response), // 格式化响应(数组/字符串)
'user_name' => $adminInfo['user_name'] ?? '匿名用户',
'user_id' => $adminInfo['user_id'] ?? 0,
];
}
private function getIpLocation(string $ip): string
{
return '未知';
}
private function getBrowser($user_agent): string
{
$br = 'Unknown';
if (preg_match('/MSIE/i', $user_agent)) {
$br = 'MSIE';
} elseif (preg_match('/Firefox/i', $user_agent)) {
$br = 'Firefox';
} elseif (preg_match('/Chrome/i', $user_agent)) {
$br = 'Chrome';
} elseif (preg_match('/Safari/i', $user_agent)) {
$br = 'Safari';
} elseif (preg_match('/Opera/i', $user_agent)) {
$br = 'Opera';
} else {
$br = 'Other';
}
return $br;
}
private function getOs($user_agent): string
{
$os = 'Unknown';
if (preg_match('/win/i', $user_agent)) {
$os = 'Windows';
} elseif (preg_match('/mac/i', $user_agent)) {
$os = 'Mac';
} elseif (preg_match('/linux/i', $user_agent)) {
$os = 'Linux';
} else {
$os = 'Other';
}
return $os;
}
private function filterParams($params): string
{
$blackList = ['password', 'old_password', 'new_password', 'content'];
foreach ($params as $key => $value) {
if (in_array($key, $blackList)) {
$params[$key] = '******';
}
}
return json_encode($params, JSON_UNESCAPED_UNICODE);
}
private function formatResponse(Response $response): array|string
{
$rawBody = $response->rawBody();
if (str_contains($rawBody, 'json')) {
return json_decode($rawBody, true) ?? $rawBody;
}
return $rawBody;
}
}
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\middleware;
use app\adminapi\middleware\helper\SseHelper;
use app\adminapi\CurrentUser;
use madong\swagger\attribute\Permission;
use core\exception\handler\UnauthorizedHttpException;
use core\jwt\JwtToken;
use core\tool\Json;
use madong\swagger\attribute\AllowAnonymous;
use madong\swagger\helper\AnnotationHelper;
use support\Container;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 方法级权限校验中间件
* 功能:
* 1. 支持 SkipAuth 注解跳过权限校验
* 2. 校验类/方法级的 Permission 注解
* 3. 处理 AND/OR 操作类型的权限逻辑
*/
#\[\Attribute\]
final class PermissionMiddleware implements MiddlewareInterface
{
/**
* Webman 中间件入口
*
* @throws \core\exception\handler\ForbiddenHttpException|\core\exception\handler\UnauthorizedHttpException
*/
public function process(Request $request, callable $handler): Response
{
$controllerClass = $request->controller;
$action = $request->action;
$allowAnonymous = AnnotationHelper::getMethodAnnotation($controllerClass, $action, AllowAnonymous::class);
if ($allowAnonymous && !$allowAnonymous->requirePermission) {
return $handler($request);
}
try {
$userId = (new JwtToken())->id();
if (empty($userId)) {
throw new UnauthorizedHttpException();
}
} catch (\Exception $e) {
// 检查是否为SSE请求
if (SseHelper::isSseRequest($request)) {
return SseHelper::sendSseErrorViaConnection($request, $e->getMessage());
}
return Json::fail($e->getMessage(), [], 401);
}
// 使用CurrentUser进行权限验证
$currentUser = Container::make(CurrentUser::class);
// 顶级管理员直接跳过权限验证
if ($currentUser->isSuperAdmin()) {
return $handler($request);
}
try {
//权限验证
$permissions = $this->resolvePermissions($controllerClass, $action);
$this->validatePermissions($currentUser, $permissions);
} catch (\Exception $e) {
// 检查是否为SSE请求
if (SseHelper::isSseRequest($request)) {
return SseHelper::sendSseErrorViaConnection($request, $e->getMessage());
}
throw $e;
}
return $handler($request);
}
/**
* 解析类/方法级的 Permission 注解
*
* @return array<Permission> 合并后的权限注解列表
*/
private function resolvePermissions(string $controllerClass, string $action): array
{
$permissions = [];
// a. 类级别注解(作用于整个控制器)
$classAnnotation = AnnotationHelper::getClassAnnotation($controllerClass, Permission::class);
if ($classAnnotation) {
$permissions[] = $classAnnotation;
}
// b. 方法级别注解(覆盖/补充类级别规则)
$methodAnnotation = AnnotationHelper::getMethodAnnotation($controllerClass, $action, Permission::class);
if ($methodAnnotation) {
$permissions[] = $methodAnnotation;
}
return $permissions;
}
/**
* 校验用户权限是否符合注解规则
*
* @throws \core\exception\handler\ForbiddenHttpException
*/
private function validatePermissions(CurrentUser $currentUser, array $permissions): void
{
foreach ($permissions as $permission) {
$this->handleSinglePermission($currentUser, $permission);
}
}
/**
* 处理 Permission 注解的权限校验逻辑
*
* @param CurrentUser $currentUser 当前用户
* @param Permission $permission 权限注解
*
* @throws \core\exception\handler\ForbiddenHttpException
*/
private function handleSinglePermission(
CurrentUser $currentUser,
Permission $permission
): void
{
// 权限码数组
$codes = $permission->getCodes();
// 操作类型
$operation = $permission->getOperation();
// 使用CurrentUser的checkPermission方法进行权限验证
$currentUser->checkPermission($codes, $operation);
}
}
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\middleware\helper;
use core\tool\Sse;
use Webman\Http\Request;
use Webman\Http\Response;
/**
* SSE 处理辅助类
* 提供 SSE 请求检测和错误处理功能
*/
class SseHelper
{
/**
* 检查是否为SSE请求
*
* @param Request $request
*
* @return bool
*/
public static function isSseRequest(Request $request): bool
{
// 方法1:检查请求头
$acceptHeader = $request->header('Accept', '');
if (str_contains($acceptHeader, 'text/event-stream')) {
return true;
}
// 方法2:检查路径(SSE接口通常有特定路径模式)
$path = $request->path();
$ssePaths = [
'/terminal',
'/terminal/exec',
];
foreach ($ssePaths as $ssePath) {
if (str_starts_with($path, $ssePath)) {
return true;
}
}
// 方法3:检查查询参数
$isSse = $request->input('sse', false) || $request->input('stream', false);
if ($isSse) {
return true;
}
return false;
}
/**
* 通过连接发送SSE错误消息
*
* @param Request $request
* @param string $message
*
* @return Response
* @throws \Exception
*/
public static function sendSseErrorViaConnection(Request $request, string $message): Response
{
// 检查是否有连接对象
if (!property_exists($request, 'connection') || !$request->connection) {
// 回退到普通的SSE响应
return self::createSseErrorResponse($message, $request->input('uuid'));
}
$connection = $request->connection;
$uuid = $request->input('uuid');
try {
// 发送SSE响应头
$connection->send(new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], "\r\n"));
// 发送SSE错误消息,传递uuid
$connection->send(Sse::error($message, null, $uuid));
// 发送空数据表示结束,防止前端等待更多数据
$connection->send(new Response(200, [], ""));
} catch (\Exception $e) {
// 忽略连接异常,返回普通响应
return self::createSseErrorResponse($message, $uuid);
}
// 返回空响应结束请求
return new Response(200, [], '');
}
/**
* 创建SSE格式的错误响应
*
* @param string $message
* @param string|null $uuid
*
* @return Response
* @throws \Exception
*/
public static function createSseErrorResponse(string $message, ?string $uuid = null): Response
{
$errorMessage = Sse::error($message, [], $uuid);
// 创建完整的SSE响应
$responseBody = $errorMessage;
return new Response(200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no', // 禁用Nginx缓冲
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Expose-Headers' => 'Content-Type',
], $responseBody);
}
}
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request;
use madong\swagger\schema\BaseRequestDTO;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '第三方登录请求',
description: '第三方登录请求参数'
)]
class ThirdPartyLoginRequest extends BaseRequestDTO
{
#[OA\Property(
description: '账套',
type: 'string',
example: '669e3705b3d7de'
)]
#[ValidationRules(rules: 'required|string|max:50')]
public string $acct_id;
#[OA\Property(
description: '授权应用ID',
type: 'string',
example: '285191_1fbo28uL6prUQ90J5YTP2dSL6vx71tqv'
)]
#[ValidationRules(rules: 'required|string|max:100')]
public string $app_id;
#[OA\Property(
description: '应用秘钥',
type: 'string',
example: '8439b36501df4f6185f521781d6b7328'
)]
#[ValidationRules(rules: 'required|string|max:100')]
public string $app_secret;
#[OA\Property(
description: '授权登录用户名',
type: 'string',
example: 'admin'
)]
#[ValidationRules(rules: 'required|string|max:50')]
public string $user_name;
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\crontab;
use madong\swagger\schema\BaseResponseDTO;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '定时任务执行请求',
description: '定时任务手动执行接口参数'
)]
class CrontabExecuteRequest extends BaseResponseDTO
{
#[OA\Property(
description: '任务ID',
type: 'string',
example: 1
)]
#[ValidationRules(rules: 'required|integer|min:1')]
public int|string $data;
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\crontab;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '定时任务表单',
description: '定时任务创建和编辑接口共用的表单请求参数'
)]
class CrontabFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'type',
description: '任务类型',
type: 'integer',
example: 1
)]
public int $type;
#[OA\Property(
property: 'task_cycle',
description: '任务周期',
type: 'integer',
example: 1
)]
public int $task_cycle;
#[OA\Property(
property: 'singleton',
description: '是否单例执行',
type: 'integer',
enum: [0, 1],
example: 1
)]
public int $singleton;
#[OA\Property(
property: 'enabled',
description: '是否启用',
type: 'integer',
enum: [0, 1],
example: 0
)]
public int $enabled;
#[OA\Property(
property: 'title',
description: '任务标题',
type: 'string',
example: '数据备份任务'
)]
public string $title;
#[OA\Property(
property: 'hour',
description: '小时',
type: 'integer',
example: 1
)]
public int $hour;
#[OA\Property(
property: 'minute',
description: '分钟',
type: 'integer',
example: 1
)]
public int $minute;
#[OA\Property(
property: 'target',
description: '执行目标',
type: 'string',
example: 'app\command\Backup::run'
)]
public string $target;
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\crontab;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '定时任务日志查询请求',
description: '定时任务日志列表查询过滤参数'
)]
class CrontabLogQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'id',
description: '日志ID',
type: 'string',
example: '123456789012345678'
)]
public ?string $id = null;
#[OA\Property(
property: 'crontab_id',
description: '定时任务ID',
type: 'string',
example: '987654321098765432'
)]
public ?string $crontab_id = null;
#[OA\Property(
property: 'target',
description: '执行目标',
type: 'string',
example: 'app\command\Backup::run'
)]
public ?string $target = null;
#[OA\Property(
property: 'return_code',
description: '返回码',
type: 'integer',
example: 0
)]
public ?int $return_code = null;
#[OA\Property(
property: 'running_time',
description: '运行时间(秒)',
type: 'integer',
example: 10
)]
public ?int $running_time = null;
#[OA\Property(
property: 'created_at',
description: '创建时间',
type: 'string',
format: 'date-time',
example: '2024-05-12T10:30:00Z'
)]
public ?string $created_at = null;
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\crontab;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '定时任务列表查询请求',
description: '定时任务列表查询过滤参数'
)]
class CrontabQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'type',
description: '任务类型',
type: 'integer',
example: 1
)]
public ?int $type = null;
#[OA\Property(
property: 'task_cycle',
description: '任务周期',
type: 'integer',
example: 1
)]
public ?int $task_cycle = null;
#[OA\Property(
property: 'singleton',
description: '是否单例执行',
type: 'integer',
enum: [0, 1],
example: 1
)]
public ?int $singleton = null;
#[OA\Property(
property: 'enabled',
description: '是否启用',
type: 'integer',
enum: [0, 1],
example: 0
)]
public ?int $enabled = null;
#[OA\Property(
property: 'title',
description: '任务标题',
type: 'string',
example: '数据备份任务'
)]
public ?string $title = null;
#[OA\Property(
property: 'target',
description: '执行目标',
type: 'string',
example: 'app\command\Backup::run'
)]
public ?string $target = null;
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\crontab;
use madong\swagger\schema\BaseRequestDTO;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '定时任务状态变更请求',
description: '暂停/恢复定时任务'
)]
class CrontabStatusRequest extends BaseRequestDTO
{
#[OA\Property(
description: '任务ID',
type: 'string',
example: 1
)]
#[ValidationRules(rules: 'required|integer|min:1')]
public int|string $data;
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\dict;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '字典表单',
description: '字典创建和编辑接口共用的表单请求参数'
)]
class DictFormRequest extends BaseFormRequest
{
#[OA\Property(
description: '字典编码',
type: 'string',
example: 'status',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50|unique:sys_dict,code')]
public string $code;
#[OA\Property(
description: '字典名称',
type: 'string',
example: '状态字典',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50|unique:sys_dict,name')]
public string $name;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|in:0,1')]
public int $status;
#[OA\Property(
description: '排序号',
type: 'integer',
example: 100,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|min:0|max:999')]
public int $sort;
#[OA\Property(
description: '备注',
type: 'string',
example: '系统状态字典',
nullable: true
)]
#[ValidationRules(rules: 'string|max:255|nullable')]
public ?string $remark = null;
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\dict;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '字典项表单',
description: '字典项创建和编辑接口共用的表单请求参数'
)]
class DictItemFormRequest extends BaseFormRequest
{
#[OA\Property(
description: '字典ID',
type: 'string',
example: '242218273104986112',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $dict_id;
#[OA\Property(
description: '选项标签',
type: 'string',
example: '所属分组',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50')]
public string $label;
#[OA\Property(
description: '选项值',
type: 'string',
example: 'default',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50|unique:sys_dict_item,value,dict_id,' . 'id' . ',id')]
public string $value;
#[OA\Property(
description: '排序号',
type: 'integer',
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|min:0|max:999')]
public int $sort;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|in:0,1')]
public int $enabled;
#[OA\Property(
description: '颜色值',
type: 'string',
example: '#4CAF50',
nullable: true
)]
#[ValidationRules(rules: 'string|max:20|nullable')]
public ?string $color = null;
#[OA\Property(
description: '扩展信息',
type: 'object',
nullable: true,
example: '{"icon": "el-icon-setting"}'
)]
#[ValidationRules(rules: 'array|nullable')]
public ?array $ext = null;
}
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\dict;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '字典项列表查询请求',
description: '字典项列表接口的查询过滤参数'
)]
class DictItemQueryRequest extends BaseQueryRequest
{
#[OA\Property(
description: '字典ID',
type: 'string',
example: '242218273104986112',
nullable: true
)]
#[ValidationRules(rules: 'string|nullable')]
public ?string $dict_id = null;
#[OA\Property(
description: '选项标签',
type: 'string',
example: '所属分组',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $label = null;
#[OA\Property(
description: '选项值',
type: 'string',
example: 'default',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $value = null;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'in:0,1|nullable')]
public ?int $enabled = null;
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\dict;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '字典列表查询请求',
description: '字典列表接口的查询过滤参数'
)]
class DictQueryRequest extends BaseQueryRequest
{
#[OA\Property(
description: '字典编码',
type: 'string',
example: 'status',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $code = null;
#[OA\Property(
description: '字典名称',
type: 'string',
example: '状态字典',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $name = null;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'in:0,1|nullable')]
public ?int $status = null;
#[OA\Property(
description: '排序字段',
type: 'string',
default: 'sort asc',
example: 'sort asc'
)]
#[ValidationRules(rules: 'string|nullable')]
public ?string $order = null;
#[OA\Property(
description: '返回数据格式(normal|tree|select)',
type: 'string',
default: 'normal',
enum: ['normal', 'tree', 'select'],
example: 'normal'
)]
#[ValidationRules(rules: 'in:normal,tree,select|nullable')]
public ?string $format = null;
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\gateway;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '限访规则表单',
description: '限访规则创建和编辑接口共用的表单请求参数'
)]
class RateLimiterFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'name',
description: '规则名称',
type: 'string',
example: 'customized'
)]
public string $name;
#[OA\Property(
property: 'match_type',
description: '匹配类型',
type: 'string',
example: 'allow'
)]
public string $match_type;
#[OA\Property(
property: 'methods',
description: '请求方法(多个用逗号分隔,如:GET,POST)',
type: 'string',
example: 'GET'
)]
public string $methods;
#[OA\Property(
property: 'path',
description: '请求路径',
type: 'string',
example: '/system/menu'
)]
public string $path;
#[OA\Property(
property: 'limit_value',
description: '限制次数',
type: 'integer',
example: 60
)]
public int $limit_value;
#[OA\Property(
property: 'period',
description: '限制周期(秒)',
type: 'integer',
example: 1
)]
public int $period;
#[OA\Property(
property: 'enabled',
description: '是否启用(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1
)]
public int $enabled;
#[OA\Property(
property: 'message',
description: '触发限制时提示消息',
type: 'string',
example: '请求频繁'
)]
public string $message;
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\gateway;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '限访规则列表查询请求',
description: '限访规则接口查询过滤参数'
)]
class RateLimiterQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'id',
description: '规则ID',
type: 'string',
example: '247298887944531968',
nullable: true
)]
public ?string $id = null;
#[OA\Property(
property: 'name',
description: '规则名称(模糊匹配)',
type: 'string',
example: 'customized',
nullable: true
)]
public ?string $name = null;
#[OA\Property(
property: 'match_type',
description: '匹配类型',
type: 'string',
example: 'allow',
nullable: true
)]
public ?string $match_type = null;
#[OA\Property(
property: 'methods',
description: '请求方法',
type: 'string',
example: 'GET',
nullable: true
)]
public ?string $methods = null;
#[OA\Property(
property: 'path',
description: '请求路径(模糊匹配)',
type: 'string',
example: '/system/menu',
nullable: true
)]
public ?string $path = null;
#[OA\Property(
property: 'enabled',
description: '是否启用(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
public ?int $enabled = null;
#[OA\Property(
property: 'limit_value',
description: '限制次数',
type: 'integer',
example: 60,
nullable: true
)]
public ?int $limit_value = null;
#[OA\Property(
property: 'period',
description: '限制周期(秒)',
type: 'integer',
example: 1,
nullable: true
)]
public ?int $period = null;
}
@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\gateway;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '限访名单表单',
description: '限访名单创建和编辑接口共用的表单请求参数'
)]
class RateRestrictionsFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'name',
description: '名单名称',
type: 'string',
example: ""
)]
public string $name;
#[OA\Property(
property: 'ip',
description: 'IP地址(留空表示不限IP)',
type: 'string',
example: null,
nullable: true
)]
public ?string $ip = null;
#[OA\Property(
property: 'enabled',
description: '是否启用(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1
)]
public int $enabled;
#[OA\Property(
property: 'priority',
description: '优先级(数值越大优先级越高)',
type: 'integer',
example: 100
)]
public int $priority;
#[OA\Property(
property: 'methods',
description: '请求方法(多个用逗号分隔,如:GET,POST)',
type: 'string',
example: "GET"
)]
public string $methods;
#[OA\Property(
property: 'path',
description: '请求路径',
type: 'string',
example: "/system/menu"
)]
public string $path;
#[OA\Property(
property: 'message',
description: '限制提示消息',
type: 'string',
example: "限制访问"
)]
public string $message;
#[OA\Property(
property: 'start_time',
description: '生效开始时间(UTC格式,如:2025-11-13T00:00:00Z)',
type: 'string',
format: 'date-time',
example: null,
nullable: true
)]
public ?string $start_time = null;
#[OA\Property(
property: 'end_time',
description: '生效结束时间(UTC格式,如:2025-11-14T00:00:00Z)',
type: 'string',
format: 'date-time',
example: null,
nullable: true
)]
public ?string $end_time = null;
#[OA\Property(
property: 'remark',
description: '备注信息',
type: 'string',
example: null,
nullable: true
)]
public ?string $remark = null;
}
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\gateway;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
#[OA\Schema(
title: '限访名单列表查询请求',
description: '限访名单列表接口查询过滤参数'
)]
class RateRestrictionsQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'id',
description: '名单ID',
type: 'string',
example: "247306532294254592",
nullable: true
)]
public ?string $id = null;
#[OA\Property(
property: 'name',
description: '名单名称(模糊匹配)',
type: 'string',
example: "sss",
nullable: true
)]
public ?string $name = null;
#[OA\Property(
property: 'ip',
description: 'IP地址(模糊匹配)',
type: 'string',
example: null,
nullable: true
)]
public ?string $ip = null;
#[OA\Property(
property: 'enabled',
description: '是否启用(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
public ?int $enabled = null;
#[OA\Property(
property: 'methods',
description: '请求方法',
type: 'string',
example: "GET",
nullable: true
)]
public ?string $methods = null;
#[OA\Property(
property: 'path',
description: '请求路径(模糊匹配)',
type: 'string',
example: "/system/menu",
nullable: true
)]
public ?string $path = null;
#[OA\Property(
property: 'priority',
description: '优先级',
type: 'integer',
example: 100,
nullable: true
)]
public ?int $priority = null;
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\login;
use madong\swagger\schema\BaseResponseDTO;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '登录请求',
description: '登录请求参数'
)]
class LoginRequest extends BaseResponseDTO
{
#[OA\Property(
description: '用户名',
type: 'string',
example: 'admin',
nullable: true,
)]
#[ValidationRules(rules: 'sometimes|string|max:50')]
public ?string $user_name = null;
#[OA\Property(
description: '密码',
type: 'string',
example: '123456',
nullable: true,
)]
#[ValidationRules(rules: 'sometimes|string')]
public ?string $password = null;
#[OA\Property(
description: '手机号码',
type: 'string',
example: '18888888888',
nullable: true
)]
#[ValidationRules(rules: 'sometimes|mobile')]
public ?string $mobile_phone = null;
#[OA\Property(
description: '验证码',
type: 'string',
example: '1234',
nullable: true
)]
#[ValidationRules(rules: 'sometimes|string|length:4,6')]
public ?string $code = null;
#[OA\Property(
description: '验证码UUID',
type: 'string',
example: 'a3bb6c8c-9b12-467b-acbf-3a3c2ecd2d2f',
nullable: true
)]
#[ValidationRules(rules: 'sometimes|uuid')]
public ?string $uuid = null;
#[OA\Property(
description: '登录类型',
type: 'string',
default: 'admin',
example: 'admin'
)]
#[ValidationRules(rules: 'default:admin|string|max:20')]
public string $type = 'admin';
#[OA\Property(
description: '授权类型',
type: 'string',
default: 'default',
enum: ['default', 'sms', 'refresh_token'],
example: 'default'
)]
#[ValidationRules(rules: 'default:default|in:default,sms,refresh_token')]
public string $grant_type = 'default';
#[OA\Property(
description: '公钥ID',
type: 'string',
example: '8119456a6fa4dfe32d01d84fc195ad23',
nullable: true,
)]
#[ValidationRules(rules: 'sometimes|string|max:100')]
public ?string $key_id = null;
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\logs;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '登录日志列表查询请求',
description: '登录日志列表接口的查询过滤参数'
)]
class LoginLogQueryRequest extends BaseQueryRequest
{
#[OA\Property(
description: '用户名',
type: 'string',
example: 'admin',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $username = null;
#[OA\Property(
description: '登录IP',
type: 'string',
example: '192.168.1.1',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $ipaddr = null;
#[OA\Property(
description: '登录状态(0失败 1成功)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'in:0,1|nullable')]
public ?int $status = null;
#[OA\Property(
description: '开始时间',
type: 'string',
format: 'date-time',
example: '2025-01-01 00:00:00',
nullable: true
)]
#[ValidationRules(rules: 'date_format:Y-m-d H:i:s|nullable')]
public ?string $start_time = null;
#[OA\Property(
description: '结束时间',
type: 'string',
format: 'date-time',
example: '2025-01-31 23:59:59',
nullable: true
)]
#[ValidationRules(rules: 'date_format:Y-m-d H:i:s|nullable|after:start_time')]
public ?string $end_time = null;
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\logs;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '操作日志列表查询请求',
description: '操作日志列表接口的查询过滤参数'
)]
class OperateLogQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'user_name',
description: '操作用户',
type: 'string',
example: 'admin',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $user_name = null;
#[OA\Property(
property: 'name',
description: '操作名称',
type: 'string',
example: '数据字典',
nullable: true
)]
#[ValidationRules(rules: 'string|max:100|nullable')]
public ?string $name = null;
#[OA\Property(
property: 'method',
description: '请求方法',
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
example: 'GET',
nullable: true
)]
#[ValidationRules(rules: 'string|in:GET,POST,PUT,DELETE,PATCH|nullable')]
public ?string $method = null;
#[OA\Property(
property: 'ip',
description: '操作IP',
type: 'string',
example: '220.165.172.45',
nullable: true
)]
#[ValidationRules(rules: 'string|ip|nullable')]
public ?string $ip = null;
#[OA\Property(
property: 'start_date',
description: '操作开始日期',
type: 'string',
format: 'date',
example: '2025-10-01',
nullable: true
)]
#[ValidationRules(rules: 'date|nullable')]
public ?string $start_date = null;
#[OA\Property(
property: 'end_date',
description: '操作结束日期',
type: 'string',
format: 'date',
example: '2025-10-31',
nullable: true
)]
#[ValidationRules(rules: 'date|after_or_equal:start_date|nullable')]
public ?string $end_date = null;
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace app\adminapi\schema\request\member;
use app\schema\request\BaseFormRequest;
/**
* 会员表单请求DTO
*/
class MemberFormRequest extends BaseFormRequest
{
/**
* 用户名
*/
public string $username;
/**
* 邮箱
*/
public ?string $email = null;
/**
* 手机号
*/
public ?string $phone = null;
/**
* 密码
*/
public ?string $password = null;
/**
* 昵称
*/
public ?string $nickname = null;
/**
* 头像
*/
public ?string $avatar = null;
/**
* 等级ID
*/
public int $level_id = 1;
/**
* 积分
*/
public int $points = 0;
/**
* 余额
*/
public float $balance = 0;
/**
* 性别
*/
public int $gender = 0;
/**
* 生日
*/
public ?string $birthday = null;
/**
* 状态
*/
public int $status = 1;
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace app\adminapi\schema\request\member;
use app\schema\request\BaseQueryRequest;
/**
* 会员查询请求DTO
*/
class MemberQueryRequest extends BaseQueryRequest
{
/**
* 用户名
*/
public ?string $username = null;
/**
* 邮箱
*/
public ?string $email = null;
/**
* 手机号
*/
public ?string $phone = null;
/**
* 状态
*/
public ?int $status = null;
/**
* 等级ID
*/
public ?int $level_id = null;
/**
* 排序字段
*/
public ?string $order_by = null;
/**
* 排序方向
*/
public ?string $order_dir = null;
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\message;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '消息表单',
description: '消息创建和编辑接口共用的表单请求参数'
)]
class MessageFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'type',
description: '消息类型(如公告、通知等)',
type: 'string',
example: 'announcement',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50')]
public string $type;
#[OA\Property(
property: 'title',
description: '消息标题',
type: 'string',
example: '系统升级维护通知',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:100')]
public string $title;
#[OA\Property(
property: 'content',
description: '消息内容',
type: 'string',
example: '系统将于2023-12-31 23:00进行维护升级,预计持续2小时',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $content;
#[OA\Property(
property: 'sender_id',
description: '发送者ID(关联管理员表)',
type: 'string',
example: '987654321098765432',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $sender_id;
#[OA\Property(
property: 'receiver_id',
description: '接收者ID(关联管理员表,0表示全员)',
type: 'string',
example: '567890123456789012',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $receiver_id;
#[OA\Property(
property: 'status',
description: '消息状态(0=未读,1=已读)',
type: 'integer',
enum: [0, 1],
example: 0,
nullable: false
)]
#[ValidationRules(rules: 'required|in:0,1')]
public int $status;
#[OA\Property(
property: 'priority',
description: '优先级(1=低,2=中,3=高)',
type: 'integer',
example: 3,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|between:1,3')]
public int $priority;
#[OA\Property(
property: 'channel',
description: '发送渠道(如system=系统内,email=邮件)',
type: 'string',
example: 'system',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:20')]
public string $channel;
#[OA\Property(
property: 'related_id',
description: '关联业务ID(如订单ID、任务ID)',
type: 'string',
example: '456789012345678901',
nullable: true
)]
#[ValidationRules(rules: 'string|max:32|nullable')]
public ?string $related_id = null;
#[OA\Property(
property: 'related_type',
description: '关联业务类型(如order=订单,task=任务)',
type: 'string',
example: 'order',
nullable: true
)]
#[ValidationRules(rules: 'string|max:20|nullable')]
public ?string $related_type = null;
#[OA\Property(
property: 'action_url',
description: '操作跳转URL',
type: 'string',
example: '/system/notice/1',
nullable: true
)]
#[ValidationRules(rules: 'string|max:255|nullable')]
public ?string $action_url = null;
#[OA\Property(
property: 'action_params',
description: '操作参数(JSON字符串)',
type: 'string',
example: '{"id":1,"type":"detail"}',
nullable: true
)]
#[ValidationRules(rules: 'string|nullable')]
public ?string $action_params = null;
#[OA\Property(
property: 'expired_at',
description: '过期时间',
type: 'string',
format: 'date-time',
example: '2023-12-31T23:59:59Z',
nullable: true
)]
#[ValidationRules(rules: 'date_format:Y-m-d H:i:s|nullable')]
public ?string $expired_at = null;
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\message;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '消息列表查询请求',
description: '消息列表接口的查询过滤参数'
)]
class MessageQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'title',
description: '消息标题',
type: 'string',
example: '系统通知',
nullable: true
)]
#[ValidationRules(rules: 'string|max:100|nullable')]
public ?string $title = null;
#[OA\Property(
property: 'content',
description: '消息内容',
type: 'string',
example: '系统升级',
nullable: true
)]
#[ValidationRules(rules: 'string|max:255|nullable')]
public ?string $content = null;
#[OA\Property(
property: 'status',
description: '消息状态(0=未读,1=已读)',
type: 'integer',
enum: [0, 1],
example: 0,
nullable: true
)]
#[ValidationRules(rules: 'in:0,1|nullable')]
public ?int $status = null;
#[OA\Property(
property: 'priority',
description: '优先级(1=低,2=中,3=高)',
type: 'integer',
enum: [1, 2, 3],
example: 3,
nullable: true
)]
#[ValidationRules(rules: 'in:1,2,3|nullable')]
public ?int $priority = null;
}
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\notice;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '公告表单',
description: '公告创建和编辑接口共用的表单请求参数'
)]
class NoticeFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'type',
description: '公告类型',
type: 'string',
enum: ['notice', 'announcement', 'alert'],
example: 'notice',
nullable: false
)]
#[ValidationRules(rules: 'required|string|in:notice,announcement,alert')]
public string $type;
#[OA\Property(
property: 'title',
description: '公告标题',
type: 'string',
example: '系统升级通知',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:100')]
public string $title;
#[OA\Property(
property: 'content',
description: '公告内容',
type: 'string',
example: '系统将于2025-10-20进行升级维护',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $content;
#[OA\Property(
property: 'sort',
description: '排序号',
type: 'integer',
example: 10,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|min:0|max:1000')]
public int $sort;
#[OA\Property(
property: 'enabled',
description: '是否启用(1启用 0禁用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|in:0,1')]
public int $enabled;
#[OA\Property(
property: 'remark',
description: '备注',
type: 'string',
example: '重要公告',
nullable: true
)]
#[ValidationRules(rules: 'string|max:255|nullable')]
public ?string $remark = null;
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\notice;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '公告列表查询请求',
description: '公告列表接口的查询过滤参数'
)]
class NoticeQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'title',
description: '公告标题',
type: 'string',
example: '系统公告',
nullable: true
)]
#[ValidationRules(rules: 'string|max:100|nullable')]
public ?string $title = null;
#[OA\Property(
property: 'type',
description: '公告类型',
type: 'string',
enum: ['notice', 'announcement', 'alert'],
example: 'notice',
nullable: true
)]
#[ValidationRules(rules: 'string|in:notice,announcement,alert|nullable')]
public ?string $type = null;
#[OA\Property(
property: 'enabled',
description: '是否启用(1启用 0禁用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'integer|in:0,1|nullable')]
public ?int $enabled = null;
#[OA\Property(
property: 'start_date',
description: '创建开始日期',
type: 'string',
format: 'date',
example: '2025-10-01',
nullable: true
)]
#[ValidationRules(rules: 'date|nullable')]
public ?string $start_date = null;
#[OA\Property(
property: 'end_date',
description: '创建结束日期',
type: 'string',
format: 'date',
example: '2025-10-31',
nullable: true
)]
#[ValidationRules(rules: 'date|after_or_equal:start_date|nullable')]
public ?string $end_date = null;
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\org;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '部门表单',
description: '部门创建和编辑接口共用的表单请求参数'
)]
class DeptFormRequest extends BaseFormRequest
{
#[OA\Property(
description: '部门ID',
type: 'string',
example: '1',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string|int|null $id;
#[OA\Property(
description: '父部门ID',
type: 'string',
example: '0',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $parent_id;
#[OA\Property(
description: '部门名称',
type: 'string',
example: '开发部',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50|unique:sys_dept,name,' . 'id' . ',id')]
public string $name;
#[OA\Property(
description: '部门编码',
type: 'string',
example: 'dev',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50|alpha_dash|unique:sys_dept,code,' . 'id' . ',id')]
public string $code;
#[OA\Property(
description: '部门领导ID列表',
type: 'array',
items: new OA\Items(type: 'integer'),
example: [1, 2],
nullable: true
)]
#[ValidationRules(rules: 'array|nullable')]
public ?array $leader_id_list = null;
#[OA\Property(
description: '联系电话',
type: 'string',
example: '13800138000',
nullable: true
)]
#[ValidationRules(rules: 'phone|nullable')]
public ?string $phone = null;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|in:0,1')]
public int $enabled;
#[OA\Property(
description: '排序号',
type: 'integer',
example: 100,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|min:0|max:999')]
public int $sort;
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: https://madong.tech
*/
namespace app\adminapi\schema\request\org;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '部门列表查询请求',
description: '部门列表接口的查询过滤参数'
)]
class DeptQueryRequest extends BaseQueryRequest
{
#[OA\Property(
description: '部门编码',
type: 'string',
example: 'dev',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $code = null;
#[OA\Property(
description: '部门名称',
type: 'string',
example: '开发部',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $name = null;
#[OA\Property(
description: '状态(0:禁用,1:启用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'in:0,1|nullable')]
public ?int $enabled = null;
#[OA\Property(
description: '排序字段',
type: 'string',
default: 'sort asc',
example: 'sort asc'
)]
#[ValidationRules(rules: 'string|nullable')]
public ?string $order = null;
#[OA\Property(
description: '返回数据格式(normal|tree|select)',
type: 'string',
default: 'normal',
enum: ['normal', 'tree', 'select'],
example: 'normal'
)]
#[ValidationRules(rules: 'in:normal,tree,select|nullable')]
public ?string $format = null;
}
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\org;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '岗位表单',
description: '岗位创建和编辑接口共用的表单请求参数'
)]
class PostFormRequest extends BaseFormRequest
{
#[OA\Property(
property: 'dept_id',
description: '部门ID',
type: 'string',
example: '246996721795072000',
nullable: false
)]
#[ValidationRules(rules: 'required|string')]
public string $dept_id;
#[OA\Property(
property: 'code',
description: '岗位编码',
type: 'string',
example: 'SDF',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:30|unique:sys_post,code')]
public string $code;
#[OA\Property(
property: 'name',
description: '岗位名称',
type: 'string',
example: 'AD',
nullable: false
)]
#[ValidationRules(rules: 'required|string|max:50')]
public string $name;
#[OA\Property(
property: 'sort',
description: '排序号',
type: 'integer',
example: 0,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|min:0|max:1000')]
public int $sort;
#[OA\Property(
property: 'enabled',
description: '状态(1启用 0禁用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: false
)]
#[ValidationRules(rules: 'required|integer|in:0,1')]
public int $enabled;
#[OA\Property(
property: 'remark',
description: '备注',
type: 'string',
example: 'ASDF',
nullable: true
)]
#[ValidationRules(rules: 'string|max:255|nullable')]
public ?string $remark = null;
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace app\adminapi\schema\request\org;
use app\schema\request\BaseQueryRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '岗位列表查询请求',
description: '岗位列表接口的查询过滤参数'
)]
class PostQueryRequest extends BaseQueryRequest
{
#[OA\Property(
property: 'name',
description: '岗位名称',
type: 'string',
example: '技术主管',
nullable: true
)]
#[ValidationRules(rules: 'string|max:50|nullable')]
public ?string $name = null;
#[OA\Property(
property: 'code',
description: '岗位编码',
type: 'string',
example: 'TECH_LEAD',
nullable: true
)]
#[ValidationRules(rules: 'string|max:30|nullable')]
public ?string $code = null;
#[OA\Property(
property: 'dept_id',
description: '部门ID',
type: 'string',
example: '246996721795072000',
nullable: true
)]
#[ValidationRules(rules: 'string|nullable')]
public ?string $dept_id = null;
#[OA\Property(
property: 'enabled',
description: '状态(1启用 0禁用)',
type: 'integer',
enum: [0, 1],
example: 1,
nullable: true
)]
#[ValidationRules(rules: 'integer|in:0,1|nullable')]
public ?int $enabled = null;
#[OA\Property(
property: 'start_date',
description: '创建开始日期',
type: 'string',
format: 'date',
example: '2025-11-01',
nullable: true
)]
#[ValidationRules(rules: 'date|nullable')]
public ?string $start_date = null;
#[OA\Property(
property: 'end_date',
description: '创建结束日期',
type: 'string',
format: 'date',
example: '2025-11-30',
nullable: true
)]
#[ValidationRules(rules: 'date|after_or_equal:start_date|nullable')]
public ?string $end_date = null;
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace app\adminapi\schema\request\plugin;
use app\schema\request\BaseFormRequest;
use OpenApi\Attributes as OA;
use WebmanTech\DTO\Attributes\ValidationRules;
#[OA\Schema(
title: '应用授权表单',
description: '应用授权创建和编辑接口共用的表单请求参数'
)]
class ApplicationAuthFormRequest extends BaseFormRequest
{
#[OA\Property(property: 'name', description: '应用名称', type: 'string', example: '开放平台应用', nullable: false)]
#[ValidationRules(rules: 'required|string|max:100')]
public string $name;
#[OA\Property(property: 'expired_at', description: '失效时间戳', type: 'integer', example: 1795104000, nullable: true)]
#[ValidationRules(rules: 'nullable|integer')]
public ?int $expired_at = null;
#[OA\Property(property: 'enabled', description: '状态(1正常 0停用)', type: 'integer', enum: [0, 1], example: 1, nullable: false)]
#[ValidationRules(rules: 'required|integer|in:0,1')]
public int $enabled;
#[OA\Property(property: 'description', description: '描述', type: 'string', example: '用于第三方应用接口调用', nullable: true)]
#[ValidationRules(rules: 'nullable|string|max:500')]
public ?string $description = null;
}

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