From 6fa41e3f4a43a347905ef0ac266ab3b5f01bad55 Mon Sep 17 00:00:00 2001 From: xavier Date: Wed, 7 May 2025 17:43:39 +0800 Subject: [PATCH] submit cursor rule --- Server/.cursor/rules/01-project-overview.mdc | 31 + .../.cursor/rules/02-directory-structure.mdc | 50 + Server/.cursor/rules/03-main-modules.mdc | 66 + .../.cursor/rules/04-development-workflow.mdc | 71 + Server/.cursor/rules/05-api-reference.mdc | 85 + Server/.cursor/rules/06-data-models.mdc | 96 + .../rules/07-websocket-communication.mdc | 104 + Server/composer.json | 2 +- Server/config/wechat_device_api.php | 19 + .../Adapters/ChuKeBao/Adapter.php | 98 + .../Contracts/WeChatServiceInterface.php | 54 + .../Exceptions/ApiException.php | 7 + Server/extend/WeChatDeviceApi/Manager.php | 94 + Server/thinkphp/library/think/App.php | 1958 ++++++++--------- 14 files changed, 1755 insertions(+), 980 deletions(-) create mode 100644 Server/.cursor/rules/01-project-overview.mdc create mode 100644 Server/.cursor/rules/02-directory-structure.mdc create mode 100644 Server/.cursor/rules/03-main-modules.mdc create mode 100644 Server/.cursor/rules/04-development-workflow.mdc create mode 100644 Server/.cursor/rules/05-api-reference.mdc create mode 100644 Server/.cursor/rules/06-data-models.mdc create mode 100644 Server/.cursor/rules/07-websocket-communication.mdc create mode 100644 Server/config/wechat_device_api.php create mode 100644 Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php create mode 100644 Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php create mode 100644 Server/extend/WeChatDeviceApi/Exceptions/ApiException.php create mode 100644 Server/extend/WeChatDeviceApi/Manager.php diff --git a/Server/.cursor/rules/01-project-overview.mdc b/Server/.cursor/rules/01-project-overview.mdc new file mode 100644 index 00000000..76fb1db1 --- /dev/null +++ b/Server/.cursor/rules/01-project-overview.mdc @@ -0,0 +1,31 @@ +--- +description: +globs: +alwaysApply: false +--- +# Project Overview + +存客宝微信管理系统是基于ThinkPHP 5.1和PHP 7.4的微信账号管理和营销自动化平台。 + +## 主要功能 + +- **设备管理**: 设备添加、删除、状态监控和任务配置 +- **微信账号管理**: 微信账号列表、微信好友管理和好友转移功能 +- **内容库**: 内容创建与管理、朋友圈内容采集和内容分类管理 +- **工作台功能**: 自动点赞、朋友圈同步、群消息推送和自动建群 +- **流量管理**: 流量标签、流量池管理和订单导入 + +## 核心文件 + +- 入口文件: [public/index.php](mdc:public/index.php) +- 应用配置: [config/app.php](mdc:config/app.php) +- 数据库配置: [config/database.php](mdc:config/database.php) +- 路由配置: [route/route.php](mdc:route/route.php) +- 公共函数: [application/common.php](mdc:application/common.php) + +## 环境要求 + +- PHP >= 7.0 (推荐 PHP 7.4) +- MySQL 5.6+ +- Composer +- ThinkPHP 5.1所需的PHP扩展 diff --git a/Server/.cursor/rules/02-directory-structure.mdc b/Server/.cursor/rules/02-directory-structure.mdc new file mode 100644 index 00000000..2ab8a4e7 --- /dev/null +++ b/Server/.cursor/rules/02-directory-structure.mdc @@ -0,0 +1,50 @@ +--- +description: +globs: +alwaysApply: false +--- +# 目录结构 + +整个项目遵循ThinkPHP 5.1的目录结构规范,主要包含以下目录: + +## 核心目录 + +- **application/** - 应用目录,包含所有模块和业务逻辑 + - [api/](mdc:application/api) - API模块,提供RESTful API接口 + - [command/](mdc:application/command) - 命令行脚本 + - [common/](mdc:application/common) - 公共模块,包含共享功能 + - [cunkebao/](mdc:application/cunkebao) - 村客宝主模块 + - [store/](mdc:application/store) - 商店模块 + - [superadmin/](mdc:application/superadmin) - 超级管理员模块 + - [cozeai/](mdc:application/cozeai) - 智能AI模块 + - [job/](mdc:application/job) - 后台任务模块 + +- **config/** - 配置目录 + - [app.php](mdc:config/app.php) - 应用配置 + - [database.php](mdc:config/database.php) - 数据库配置 + - [queue.php](mdc:config/queue.php) - 队列配置 + - [worker.php](mdc:config/worker.php) - Worker配置 + +- **extend/** - 扩展目录,存放第三方扩展类库 + +- **public/** - 公共资源目录,Web访问入口 + - [index.php](mdc:public/index.php) - 入口文件 + +- **route/** - 路由目录 + - [route.php](mdc:route/route.php) - 路由配置 + +- **runtime/** - 运行时目录,存放日志和缓存文件 + +- **thinkphp/** - ThinkPHP框架核心目录 + +- **vendor/** - Composer依赖目录 + +## 模块结构 + +每个模块通常包含以下目录结构: + +- **controller/** - 控制器 +- **model/** - 数据模型 +- **service/** - 服务层 +- **validate/** - 验证器 +- **config/** - 模块配置,包括独立的路由配置 diff --git a/Server/.cursor/rules/03-main-modules.mdc b/Server/.cursor/rules/03-main-modules.mdc new file mode 100644 index 00000000..107c6a76 --- /dev/null +++ b/Server/.cursor/rules/03-main-modules.mdc @@ -0,0 +1,66 @@ +--- +description: +globs: +alwaysApply: false +--- +# 主要模块 + +村客宝系统由多个功能模块组成,各模块负责不同的业务功能: + +## API模块 + +[application/api/](mdc:application/api) 提供RESTful API服务,主要包括: + +- [DeviceController.php](mdc:application/api/controller/DeviceController.php) - 设备相关API +- [WechatController.php](mdc:application/api/controller/WechatController.php) - 微信账号相关API +- [WechatFriendController.php](mdc:application/api/controller/WechatFriendController.php) - 微信好友相关API +- [WechatChatroomController.php](mdc:application/api/controller/WechatChatroomController.php) - 微信群相关API +- [MessageController.php](mdc:application/api/controller/MessageController.php) - 消息相关API +- [MomentsController.php](mdc:application/api/controller/MomentsController.php) - 朋友圈相关API +- [WebSocketController.php](mdc:application/api/controller/WebSocketController.php) - WebSocket通信API + +## 村客宝主模块 + +[application/cunkebao/](mdc:application/cunkebao) 是系统的核心模块,包含主要业务逻辑: + +- [Device.php](mdc:application/cunkebao/controller/Device.php) - 设备管理控制器 +- [DeviceWechat.php](mdc:application/cunkebao/controller/DeviceWechat.php) - 设备微信管理 +- [ContentLibraryController.php](mdc:application/cunkebao/controller/ContentLibraryController.php) - 内容库管理 +- [WorkbenchController.php](mdc:application/cunkebao/controller/WorkbenchController.php) - 工作台功能 +- [TrafficPool.php](mdc:application/cunkebao/controller/TrafficPool.php) - 流量池管理 +- [TrafficTag.php](mdc:application/cunkebao/controller/TrafficTag.php) - 流量标签管理 + +## 公共模块 + +[application/common/](mdc:application/common) 包含系统共享的功能和工具: + +- [service/](mdc:application/common/service) - 公共服务 +- [model/](mdc:application/common/model) - 核心数据模型 +- [util/](mdc:application/common/util) - 工具类 +- [socket/](mdc:application/common/socket) - WebSocket通信 +- [middleware/](mdc:application/common/middleware) - 中间件 + +## 命令行模块 + +[application/command/](mdc:application/command) 包含系统的命令行工具: + +- 定时任务 +- 队列处理 +- 数据迁移 +- 系统维护 + +## 存储模块 + +[application/store/](mdc:application/store) 提供资源存储相关的功能: + +- 图片存储 +- 文件上传 +- 资源管理 + +## 超级管理员模块 + +[application/superadmin/](mdc:application/superadmin) 提供系统管理功能: + +- 用户管理 +- 权限管理 +- 系统配置 diff --git a/Server/.cursor/rules/04-development-workflow.mdc b/Server/.cursor/rules/04-development-workflow.mdc new file mode 100644 index 00000000..8e177beb --- /dev/null +++ b/Server/.cursor/rules/04-development-workflow.mdc @@ -0,0 +1,71 @@ +--- +description: +globs: +alwaysApply: false +--- +# 开发工作流程 + +本项目基于ThinkPHP 5.1框架开发,遵循MVC设计模式。开发时请参考以下工作流程和规范: + +## 框架特性 + +ThinkPHP 5.1使用以下特性: +- 命名空间和自动加载 +- 依赖注入和容器 +- 门面(Facade)模式 +- 中间件机制 +- 路由定义 + +## 开发流程 + +1. **理解需求** - 明确功能需求和业务逻辑 +2. **设计数据库** - 设计相关数据表和字段 +3. **创建模型** - 在对应模块的`model`目录下创建数据模型 +4. **编写服务层** - 在`service`目录下实现业务逻辑 +5. **创建控制器** - 在`controller`目录下创建控制器处理请求 +6. **定义路由** - 在模块的`config/route.php`中定义路由规则 +7. **编写前端代码** - 实现页面交互和UI + +## 关键文件和位置 + +- **模型定义**: `application/模块名/model/` +- **控制器**: `application/模块名/controller/` +- **服务层**: `application/模块名/service/` +- **路由配置**: `application/模块名/config/route.php` +- **公共函数**: `application/common.php` +- **配置文件**: `config/` 和 `application/模块名/config/` + +## 命名规范 + +- **类名**: 使用Pascal命名法(如`DeviceController`) +- **方法名**: 使用camel命名法(如`getUserInfo`) +- **变量名**: 使用camel命名法(如`$deviceInfo`) +- **常量**: 使用大写下划线(如`APP_DEBUG`) +- **配置参数**: 使用小写下划线(如`app_debug`) + +## 使用命令行 + +ThinkPHP提供了命令行工具,可用于执行各种任务: + +```bash +# 查看可用命令 +php think + +# 启动内置服务器 +php think run + +# 清除缓存 +php think clear + +# 执行数据库迁移 +php think migrate:run +``` + +## 扩展包依赖 + +项目使用Composer管理依赖,主要依赖见[composer.json](mdc:composer.json): + +- topthink/framework: 5.1.* +- phpoffice/phpexcel +- guzzlehttp/guzzle +- 等其他扩展包 diff --git a/Server/.cursor/rules/05-api-reference.mdc b/Server/.cursor/rules/05-api-reference.mdc new file mode 100644 index 00000000..9e27a73d --- /dev/null +++ b/Server/.cursor/rules/05-api-reference.mdc @@ -0,0 +1,85 @@ +--- +description: +globs: +alwaysApply: false +--- +# API 参考 + +村客宝系统提供RESTful风格的API,主要通过`application/api`模块实现。 + +## API路由结构 + +系统API遵循RESTful设计,主要路由前缀为`/v1`,详细路由定义见[application/api/config/route.php](mdc:application/api/config/route.php) + +## 主要API控制器 + +- [DeviceController.php](mdc:application/api/controller/DeviceController.php) - 设备管理API +- [WechatController.php](mdc:application/api/controller/WechatController.php) - 微信账号API +- [WechatFriendController.php](mdc:application/api/controller/WechatFriendController.php) - 微信好友API +- [WechatChatroomController.php](mdc:application/api/controller/WechatChatroomController.php) - 微信群API +- [MessageController.php](mdc:application/api/controller/MessageController.php) - 消息API +- [MomentsController.php](mdc:application/api/controller/MomentsController.php) - 朋友圈API +- [UserController.php](mdc:application/api/controller/UserController.php) - 用户API + +## 常用API端点 + +### 设备管理 + +- `GET /v1/devices` - 获取设备列表 +- `GET /v1/devices/{id}` - 获取设备详情 +- `POST /v1/devices` - 添加设备 +- `PUT /v1/devices/{id}` - 更新设备信息 +- `DELETE /v1/devices/{id}` - 删除设备 + +### 微信账号管理 + +- `GET /v1/wechats` - 获取微信账号列表 +- `GET /v1/wechats/{id}` - 获取微信账号详情 +- `PUT /v1/wechats/{id}` - 更新微信账号信息 +- `DELETE /v1/wechats/{id}` - 删除微信账号 + +### 微信好友管理 + +- `GET /v1/wechats/{id}/friends` - 获取好友列表 +- `POST /v1/wechats/{id}/friends` - 添加好友 +- `PUT /v1/wechats/{id}/friends/{friendId}` - 更新好友信息 + +### 工作台功能 + +- `POST /v1/workbench/auto-like` - 自动点赞 +- `POST /v1/workbench/sync-moments` - 同步朋友圈 +- `POST /v1/workbench/send-message` - 发送消息 + +## API鉴权 + +API使用Token认证机制,每个请求需要在Header中包含授权信息: + +``` +Authorization: Bearer {token} +``` + +获取Token的方法: +- `POST /v1/auth/login` - 登录获取Token +- `POST /v1/auth/refresh` - 刷新Token + +## 响应格式 + +API统一返回JSON格式数据,基本结构: + +```json +{ + "code": 200, // 状态码 + "message": "success", // 消息 + "data": {}, // 数据(可选) + "time": 1628160000 // 时间戳 +} +``` + +## WebSocket通信 + +实时通信通过WebSocket实现,在[WebSocketController.php](mdc:application/api/controller/WebSocketController.php)中定义: + +- 连接地址:`ws://{host}/ws` +- 支持设备状态实时推送 +- 支持消息实时通知 +- 支持任务执行状态更新 diff --git a/Server/.cursor/rules/06-data-models.mdc b/Server/.cursor/rules/06-data-models.mdc new file mode 100644 index 00000000..e41ff5f8 --- /dev/null +++ b/Server/.cursor/rules/06-data-models.mdc @@ -0,0 +1,96 @@ +--- +description: +globs: +alwaysApply: false +--- +# 数据模型 + +系统使用ThinkPHP的ORM框架进行数据库操作,主要模型如下: + +## 核心模型 + +### 设备相关 + +- **Device模型** - 设备信息 + - 位置:[application/common/model/Device.php](mdc:application/common/model/Device.php) + - 主要字段:id, name, device_id, status, online_status, created_at, updated_at + +- **DeviceWechat模型** - 设备上的微信账号 + - 位置:[application/common/model/DeviceWechat.php](mdc:application/common/model/DeviceWechat.php) + - 主要字段:id, device_id, wechat_id, status, login_status, created_at, updated_at + +### 微信相关 + +- **Wechat模型** - 微信账号信息 + - 位置:[application/common/model/Wechat.php](mdc:application/common/model/Wechat.php) + - 主要字段:id, wxid, nickname, avatar, gender, country, province, city, created_at, updated_at + +- **WechatFriend模型** - 微信好友 + - 位置:[application/common/model/WechatFriend.php](mdc:application/common/model/WechatFriend.php) + - 主要字段:id, wechat_id, wxid, nickname, remark, avatar, gender, created_at, updated_at + +- **WechatChatroom模型** - 微信群 + - 位置:[application/common/model/WechatChatroom.php](mdc:application/common/model/WechatChatroom.php) + - 主要字段:id, chatroom_id, name, owner_wxid, member_count, created_at, updated_at + +### 内容相关 + +- **ContentLibrary模型** - 内容库 + - 位置:[application/common/model/ContentLibrary.php](mdc:application/common/model/ContentLibrary.php) + - 主要字段:id, title, content, type, category_id, tags, created_at, updated_at + +- **ContentCategory模型** - 内容分类 + - 位置:[application/common/model/ContentCategory.php](mdc:application/common/model/ContentCategory.php) + - 主要字段:id, name, parent_id, sort, created_at, updated_at + +### 工作台相关 + +- **Task模型** - 任务 + - 位置:[application/common/model/Task.php](mdc:application/common/model/Task.php) + - 主要字段:id, name, type, status, config, created_at, updated_at + +- **Plan模型** - 计划 + - 位置:[application/common/model/Plan.php](mdc:application/common/model/Plan.php) + - 主要字段:id, name, type, status, config, schedule, created_at, updated_at + +### 流量相关 + +- **TrafficPool模型** - 流量池 + - 位置:[application/common/model/TrafficPool.php](mdc:application/common/model/TrafficPool.php) + - 主要字段:id, name, description, created_at, updated_at + +- **TrafficTag模型** - 流量标签 + - 位置:[application/common/model/TrafficTag.php](mdc:application/common/model/TrafficTag.php) + - 主要字段:id, name, color, created_at, updated_at + +## 模型关联 + +系统中的模型通过ThinkPHP的关联关系进行关联: + +- Device模型 hasMany DeviceWechat模型 +- DeviceWechat模型 belongsTo Device模型 +- DeviceWechat模型 belongsTo Wechat模型 +- Wechat模型 hasMany WechatFriend模型 +- Wechat模型 hasMany WechatChatroom模型 +- ContentLibrary模型 belongsTo ContentCategory模型 + +## 模型使用示例 + +```php +// 查询设备列表 +$devices = Device::where('status', 1)->order('id', 'desc')->paginate(10); + +// 关联查询设备上的微信账号 +$device = Device::with('wechats')->find($deviceId); + +// 创建新设备 +$device = new Device; +$device->name = '设备名称'; +$device->device_id = 'DEVICE_123456'; +$device->status = 1; +$device->save(); +``` + +## 数据验证 + +模型验证规则定义在对应的验证器类中,位于`application/模块名/validate/`目录下。 diff --git a/Server/.cursor/rules/07-websocket-communication.mdc b/Server/.cursor/rules/07-websocket-communication.mdc new file mode 100644 index 00000000..0f7914ac --- /dev/null +++ b/Server/.cursor/rules/07-websocket-communication.mdc @@ -0,0 +1,104 @@ +--- +description: +globs: +alwaysApply: false +--- +# WebSocket通信 + +村客宝系统使用WebSocket实现实时通信功能,主要用于设备状态监控和消息实时推送。 + +## WebSocket服务 + +系统使用ThinkPHP的think-worker扩展实现WebSocket服务: + +- 服务配置:[config/worker.php](mdc:config/worker.php)和[config/worker_server.php](mdc:config/worker_server.php) +- Gateway配置:[config/gateway_worker.php](mdc:config/gateway_worker.php) + +## WebSocket控制器 + +WebSocket服务主要通过以下文件实现: + +- [application/api/controller/WebSocketController.php](mdc:application/api/controller/WebSocketController.php) - WebSocket控制器 +- [application/common/socket/](mdc:application/common/socket) - WebSocket核心实现 + +## 消息格式 + +WebSocket消息使用JSON格式,基本结构如下: + +```json +{ + "type": "message_type", // 消息类型 + "data": {}, // 消息数据 + "time": 1628160000 // 时间戳 +} +``` + +## 常用消息类型 + +- `device_status` - 设备状态更新 +- `wechat_login` - 微信登录状态更新 +- `new_message` - 新消息通知 +- `task_status` - 任务状态更新 +- `error` - 错误消息 + +## 客户端连接 + +客户端可以通过以下方式连接WebSocket服务: + +```javascript +const ws = new WebSocket('ws://{host}/ws'); + +ws.onopen = function() { + console.log('Connected to WebSocket server'); + // 发送认证消息 + ws.send(JSON.stringify({ + type: 'auth', + data: { + token: 'YOUR_AUTH_TOKEN' + } + })); +}; + +ws.onmessage = function(event) { + const message = JSON.parse(event.data); + console.log('Received message:', message); + + // 根据消息类型处理 + switch(message.type) { + case 'device_status': + // 处理设备状态更新 + break; + case 'new_message': + // 处理新消息 + break; + // ...其他消息类型 + } +}; +``` + +## WebSocket命令 + +系统提供命令行工具管理WebSocket服务: + +```bash +# 启动WebSocket服务 +php think worker:server start + +# 停止WebSocket服务 +php think worker:server stop + +# 重启WebSocket服务 +php think worker:server restart + +# 查看WebSocket服务状态 +php think worker:server status +``` + +## 消息推送服务 + +系统使用队列实现消息的异步推送: + +- 队列配置:[config/queue.php](mdc:config/queue.php) +- 消息推送任务:[application/job/](mdc:application/job) + +通过队列可以实现高效的消息推送,避免阻塞主进程。 diff --git a/Server/composer.json b/Server/composer.json index 093debe1..5f38cd4a 100644 --- a/Server/composer.json +++ b/Server/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=5.6.0", + "php": ">=7.0", "topthink/framework": "5.1.*", "phpoffice/phpexcel": "^1.8", "endroid/qr-code": "^2.5", diff --git a/Server/config/wechat_device_api.php b/Server/config/wechat_device_api.php new file mode 100644 index 00000000..24c57cac --- /dev/null +++ b/Server/config/wechat_device_api.php @@ -0,0 +1,19 @@ + 'ChuKeBao', + + // 各个供应商适配器的配置 + 'adapters' => [ + 'ChuKeBao' => [ + 'driver' => \WeChatDeviceApi\Adapters\ChuKebao\Adapter::class, +// 'api_key' => env('ChuKebao_API_KEY', ''), +// 'api_secret' => env('ChuKebao_API_SECRET', ''), + 'base_url' => env('api.wechat_url'), + 'username' => env('api.username', ''), + 'password' => env('api.password', ''), + ], + // ... 更多供应商 + ], +]; \ No newline at end of file diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php new file mode 100644 index 00000000..5720ce68 --- /dev/null +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -0,0 +1,98 @@ +config = $config; + // $this->apiClient = new VendorAApiClient($config['api_key'], $config['api_secret'], $config['base_url']); + // 校验配置等... + if (empty($config['api_key']) || empty($config['base_url'])) { + throw new \InvalidArgumentException("VendorA API key and base_url are required."); + } + } + + public function addFriend(string $deviceId, string $targetWxId): bool + { + // 1. 构建请求参数 (VendorA 特定的格式) + $params = [ + 'device_identifier' => $deviceId, + 'wechat_user_to_add' => $targetWxId, + 'apiKey' => $this->config['api_key'], + // ... 其他 VendorA 特定参数 + ]; + + // 2. 调用 VendorA 的 API (例如使用 GuzzleHttp 或 cURL) + // $response = $this->apiClient->post('/friend/add', $params); + // 伪代码: + $url = $this->config['base_url'] . '/friend/add'; + // $httpClient = new \GuzzleHttp\Client(); + // $response = $httpClient->request('POST', $url, ['form_params' => $params]); + // $responseData = json_decode($response->getBody()->getContents(), true); + + // 模拟API调用 + echo "VendorA: Adding friend {$targetWxId} using device {$deviceId}\n"; + $responseData = ['code' => 0, 'message' => 'Success']; // 假设的响应 + + // 3. 处理响应,转换为标准结果 + if (!isset($responseData['code'])) { + throw new ApiException("VendorA: Invalid API response for addFriend."); + } +// if ($responseData['code'] === 1001) { // 假设1001是设备离线 +// throw new DeviceOfflineException("VendorA: Device {$deviceId} is offline."); +// } + if ($responseData['code'] !== 0) { + throw new ApiException("VendorA: Failed to add friend - " . ($responseData['message'] ?? 'Unknown error')); + } + + return true; + } + + public function likeMoment(string $deviceId, string $momentId): bool + { + echo "VendorA: Liking moment {$momentId} using device {$deviceId}\n"; + // 实现 VendorA 的点赞逻辑 + return true; + } + + public function getGroupList(string $deviceId): array + { + echo "VendorA: Getting group list for device {$deviceId}\n"; + // 实现 VendorA 的获取群列表逻辑,并转换数据格式 + return [ + ['id' => 'group1_va', 'name' => 'VendorA Group 1', 'member_count' => 10], + ]; + } + + public function getFriendList(string $deviceId): array + { + echo "VendorA: Getting friend list for device {$deviceId}\n"; + return [ + ['id' => 'friend1_va', 'nickname' => 'VendorA Friend 1', 'remark' => 'VA-F1'], + ]; + } + + public function getDeviceInfo(string $deviceId): array + { + echo "VendorA: Getting device info for device {$deviceId}\n"; + return ['id' => $deviceId, 'status' => 'online_va', 'battery' => '80%']; + } + + public function bindDeviceToCompany(string $deviceId, string $companyId): bool + { + echo "VendorA: Binding device {$deviceId} to company {$companyId}\n"; + return true; + } + + // ... 实现接口中的其他方法 +} \ No newline at end of file diff --git a/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php b/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php new file mode 100644 index 00000000..e35cc496 --- /dev/null +++ b/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php @@ -0,0 +1,54 @@ +config = $config ?: Config::get('wechat_device_api.'); + if (empty($this->config)) { + throw new \InvalidArgumentException("WeChat Device API configuration not found."); + } + } + + /** + * 获取指定的适配器实例 + * + * @param string|null $name 适配器名称 (例如 'vendor_a', 'vendor_b'),null 则使用默认 + * @return WeChatServiceInterface + * @throws \InvalidArgumentException + */ + public function adapter(string $name = null): WeChatServiceInterface + { + $name = $name ?: $this->getDefaultAdapterName(); + + if (!isset($this->config['adapters'][$name])) { + throw new \InvalidArgumentException("Adapter [{$name}] configuration not found."); + } + + if (!isset($this->adapters[$name])) { + $this->adapters[$name] = $this->createAdapter($name); + } + + return $this->adapters[$name]; + } + + /** + * 创建适配器实例 + * + * @param string $name + * @return WeChatServiceInterface + */ + protected function createAdapter(string $name): WeChatServiceInterface + { + $adapterConfig = $this->config['adapters'][$name]; + $driverClass = $adapterConfig['driver'] ?? null; + + if (!$driverClass || !class_exists($driverClass)) { + throw new \InvalidArgumentException("Driver class for adapter [{$name}] not found or not specified."); + } + + $adapterInstance = new $driverClass($adapterConfig); + + if (!$adapterInstance instanceof WeChatServiceInterface) { + throw new \LogicException("Driver class [{$driverClass}] must implement WeChatServiceInterface."); + } + + return $adapterInstance; + } + + /** + * 获取默认适配器名称 + * + * @return string + */ + public function getDefaultAdapterName(): string + { + return $this->config['default_adapter'] ?? ''; + } + + /** + * 动态调用默认适配器的方法 + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call(string $method, array $parameters) + { + return $this->adapter()->{$method}(...$parameters); + } +} \ No newline at end of file diff --git a/Server/thinkphp/library/think/App.php b/Server/thinkphp/library/think/App.php index 35924a5b..692b4227 100644 --- a/Server/thinkphp/library/think/App.php +++ b/Server/thinkphp/library/think/App.php @@ -1,979 +1,979 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; -use think\exception\HttpResponseException; -use think\route\Dispatch; - -/** - * App 应用管理 - */ -class App extends Container -{ - const VERSION = '5.1.41 LTS'; - - /** - * 当前模块路径 - * @var string - */ - protected $modulePath; - - /** - * 应用调试模式 - * @var bool - */ - protected $appDebug = true; - - /** - * 应用开始时间 - * @var float - */ - protected $beginTime; - - /** - * 应用内存初始占用 - * @var integer - */ - protected $beginMem; - - /** - * 应用类库命名空间 - * @var string - */ - protected $namespace = 'app'; - - /** - * 应用类库后缀 - * @var bool - */ - protected $suffix = false; - - /** - * 严格路由检测 - * @var bool - */ - protected $routeMust; - - /** - * 应用类库目录 - * @var string - */ - protected $appPath; - - /** - * 框架目录 - * @var string - */ - protected $thinkPath; - - /** - * 应用根目录 - * @var string - */ - protected $rootPath; - - /** - * 运行时目录 - * @var string - */ - protected $runtimePath; - - /** - * 配置目录 - * @var string - */ - protected $configPath; - - /** - * 路由目录 - * @var string - */ - protected $routePath; - - /** - * 配置后缀 - * @var string - */ - protected $configExt; - - /** - * 应用调度实例 - * @var Dispatch - */ - protected $dispatch; - - /** - * 绑定模块(控制器) - * @var string - */ - protected $bindModule; - - /** - * 初始化 - * @var bool - */ - protected $initialized = false; - - public function __construct($appPath = '') - { - $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; - $this->path($appPath); - } - - /** - * 绑定模块或者控制器 - * @access public - * @param string $bind - * @return $this - */ - public function bind($bind) - { - $this->bindModule = $bind; - return $this; - } - - /** - * 设置应用类库目录 - * @access public - * @param string $path 路径 - * @return $this - */ - public function path($path) - { - $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); - - return $this; - } - - /** - * 初始化应用 - * @access public - * @return void - */ - public function initialize() - { - if ($this->initialized) { - return; - } - - $this->initialized = true; - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); - - $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; - $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; - $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; - $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; - - static::setInstance($this); - - $this->instance('app', $this); - - // 加载环境变量配置文件 - if (is_file($this->rootPath . '.env')) { - $this->env->load($this->rootPath . '.env'); - } - - $this->configExt = $this->env->get('config_ext', '.php'); - - // 加载惯例配置文件 - $this->config->set(include $this->thinkPath . 'convention.php'); - - // 设置路径环境变量 - $this->env->set([ - 'think_path' => $this->thinkPath, - 'root_path' => $this->rootPath, - 'app_path' => $this->appPath, - 'config_path' => $this->configPath, - 'route_path' => $this->routePath, - 'runtime_path' => $this->runtimePath, - 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, - 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, - ]); - - $this->namespace = $this->env->get('app_namespace', $this->namespace); - $this->env->set('app_namespace', $this->namespace); - - // 注册应用命名空间 - Loader::addNamespace($this->namespace, $this->appPath); - - // 初始化应用 - $this->init(); - - // 开启类名后缀 - $this->suffix = $this->config('app.class_suffix'); - - // 应用调试模式 - $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); - $this->env->set('app_debug', $this->appDebug); - - if (!$this->appDebug) { - ini_set('display_errors', 'Off'); - } elseif (PHP_SAPI != 'cli') { - //重新申请一块比较大的buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - ob_start(); - if (!empty($output)) { - echo $output; - } - } - - // 注册异常处理类 - if ($this->config('app.exception_handle')) { - Error::setExceptionHandler($this->config('app.exception_handle')); - } - - // 注册根命名空间 - if (!empty($this->config('app.root_namespace'))) { - Loader::addNamespace($this->config('app.root_namespace')); - } - - // 加载composer autofile文件 - Loader::loadComposerAutoloadFiles(); - - // 注册类库别名 - Loader::addClassAlias($this->config->pull('alias')); - - // 数据库配置初始化 - Db::init($this->config->pull('database')); - - // 设置系统时区 - date_default_timezone_set($this->config('app.default_timezone')); - - // 读取语言包 - $this->loadLangPack(); - - // 路由初始化 - $this->routeInit(); - } - - /** - * 初始化应用或模块 - * @access public - * @param string $module 模块名 - * @return void - */ - public function init($module = '') - { - // 定位模块目录 - $module = $module ? $module . DIRECTORY_SEPARATOR : ''; - $path = $this->appPath . $module; - - // 加载初始化文件 - if (is_file($path . 'init.php')) { - include $path . 'init.php'; - } elseif (is_file($this->runtimePath . $module . 'init.php')) { - include $this->runtimePath . $module . 'init.php'; - } else { - // 加载行为扩展文件 - if (is_file($path . 'tags.php')) { - $tags = include $path . 'tags.php'; - if (is_array($tags)) { - $this->hook->import($tags); - } - } - - // 加载公共文件 - if (is_file($path . 'common.php')) { - include_once $path . 'common.php'; - } - - if ('' == $module) { - // 加载系统助手函数 - include $this->thinkPath . 'helper.php'; - } - - // 加载中间件 - if (is_file($path . 'middleware.php')) { - $middleware = include $path . 'middleware.php'; - if (is_array($middleware)) { - $this->middleware->import($middleware); - } - } - - // 注册服务的容器对象实例 - if (is_file($path . 'provider.php')) { - $provider = include $path . 'provider.php'; - if (is_array($provider)) { - $this->bindTo($provider); - } - } - - // 自动读取配置文件 - if (is_dir($path . 'config')) { - $dir = $path . 'config' . DIRECTORY_SEPARATOR; - } elseif (is_dir($this->configPath . $module)) { - $dir = $this->configPath . $module; - } - - $files = isset($dir) ? scandir($dir) : []; - - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { - $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - $this->setModulePath($path); - - if ($module) { - // 对容器中的对象实例进行配置更新 - $this->containerConfigUpdate($module); - } - } - - protected function containerConfigUpdate($module) - { - $config = $this->config->get(); - - // 注册异常处理类 - if ($config['app']['exception_handle']) { - Error::setExceptionHandler($config['app']['exception_handle']); - } - - Db::init($config['database']); - $this->middleware->setConfig($config['middleware']); - $this->route->setConfig($config['app']); - $this->request->init($config['app']); - $this->cookie->init($config['cookie']); - $this->view->init($config['template']); - $this->log->init($config['log']); - $this->session->setConfig($config['session']); - $this->debug->setConfig($config['trace']); - $this->cache->init($config['cache'], true); - - // 加载当前模块语言包 - $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); - - // 模块请求缓存检查 - $this->checkRequestCache( - $config['app']['request_cache'], - $config['app']['request_cache_expire'], - $config['app']['request_cache_except'] - ); - } - - /** - * 执行应用程序 - * @access public - * @return Response - * @throws Exception - */ - public function run() - { - try { - // 初始化应用 - $this->initialize(); - - // 监听app_init - $this->hook->listen('app_init'); - - if ($this->bindModule) { - // 模块/控制器绑定 - $this->route->bind($this->bindModule); - } elseif ($this->config('app.auto_bind_module')) { - // 入口自动绑定 - $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir($this->appPath . $name)) { - $this->route->bind($name); - } - } - - // 监听app_dispatch - $this->hook->listen('app_dispatch'); - - $dispatch = $this->dispatch; - - if (empty($dispatch)) { - // 路由检测 - $dispatch = $this->routeCheck()->init(); - } - - // 记录当前调度信息 - $this->request->dispatch($dispatch); - - // 记录路由和请求信息 - if ($this->appDebug) { - $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); - $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); - $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); - } - - // 监听app_begin - $this->hook->listen('app_begin'); - - // 请求缓存检查 - $this->checkRequestCache( - $this->config('request_cache'), - $this->config('request_cache_expire'), - $this->config('request_cache_except') - ); - - $data = null; - } catch (HttpResponseException $exception) { - $dispatch = null; - $data = $exception->getResponse(); - } - - $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { - return is_null($data) ? $dispatch->run() : $data; - }); - - $response = $this->middleware->dispatch($this->request); - - // 监听app_end - $this->hook->listen('app_end', $response); - - return $response; - } - - protected function getRouteCacheKey() - { - if ($this->config->get('route_check_cache_key')) { - $closure = $this->config->get('route_check_cache_key'); - $routeKey = $closure($this->request); - } else { - $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); - } - - return $routeKey; - } - - protected function loadLangPack() - { - // 读取默认语言 - $this->lang->range($this->config('app.default_lang')); - - if ($this->config('app.lang_switch_on')) { - // 开启多语言机制 检测当前语言 - $this->lang->detect(); - } - - $this->request->setLangset($this->lang->range()); - - // 加载系统语言包 - $this->lang->load([ - $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', - $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', - ]); - } - - /** - * 设置当前地址的请求缓存 - * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @param string $tag 缓存标签 - * @return void - */ - public function checkRequestCache($key, $expire = null, $except = [], $tag = null) - { - $cache = $this->request->cache($key, $expire, $except, $tag); - - if ($cache) { - $this->setResponseCache($cache); - } - } - - public function setResponseCache($cache) - { - list($key, $expire, $tag) = $cache; - - if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { - // 读取缓存 - $response = Response::create()->code(304); - throw new HttpResponseException($response); - } elseif ($this->cache->has($key)) { - list($content, $header) = $this->cache->get($key); - - $response = Response::create($content)->header($header); - throw new HttpResponseException($response); - } - } - - /** - * 设置当前请求的调度信息 - * @access public - * @param Dispatch $dispatch 调度信息 - * @return $this - */ - public function dispatch(Dispatch $dispatch) - { - $this->dispatch = $dispatch; - return $this; - } - - /** - * 记录调试信息 - * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @return void - */ - public function log($msg, $type = 'info') - { - $this->appDebug && $this->log->record($msg, $type); - } - - /** - * 获取配置参数 为空则获取所有配置 - * @access public - * @param string $name 配置参数名(支持二级配置 .号分割) - * @return mixed - */ - public function config($name = '') - { - return $this->config->get($name); - } - - /** - * 路由初始化 导入路由定义规则 - * @access public - * @return void - */ - public function routeInit() - { - // 路由检测 - if (is_dir($this->routePath)) { - $files = glob($this->routePath . '*.php'); - foreach ($files as $file) { - $rules = include $file; - if (is_array($rules)) { - $this->route->import($rules); - } - } - } - - if ($this->route->config('route_annotation')) { - // 自动生成路由定义 - if ($this->appDebug) { - $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); - $this->build->buildRoute($suffix); - } - - $filename = $this->runtimePath . 'build_route.php'; - - if (is_file($filename)) { - include $filename; - } - } - } - - /** - * URL路由检测(根据PATH_INFO) - * @access public - * @return Dispatch - */ - public function routeCheck() - { - // 检测路由缓存 - if (!$this->appDebug && $this->config->get('route_check_cache')) { - $routeKey = $this->getRouteCacheKey(); - $option = $this->config->get('route_cache_option'); - - if ($option && $this->cache->connect($option)->has($routeKey)) { - return $this->cache->connect($option)->get($routeKey); - } elseif ($this->cache->has($routeKey)) { - return $this->cache->get($routeKey); - } - } - - // 获取应用调度信息 - $path = $this->request->path(); - - // 是否强制路由模式 - $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); - - // 路由检测 返回一个Dispatch对象 - $dispatch = $this->route->check($path, $must); - - if (!empty($routeKey)) { - try { - if ($option) { - $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); - } else { - $this->cache->tag('route_cache')->set($routeKey, $dispatch); - } - } catch (\Exception $e) { - // 存在闭包的时候缓存无效 - } - } - - return $dispatch; - } - - /** - * 设置应用的路由检测机制 - * @access public - * @param bool $must 是否强制检测路由 - * @return $this - */ - public function routeMust($must = false) - { - $this->routeMust = $must; - return $this; - } - - /** - * 解析模块和类名 - * @access protected - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return array - */ - protected function parseModuleAndClass($name, $layer, $appendSuffix) - { - if (false !== strpos($name, '\\')) { - $class = $name; - $module = $this->request->module(); - } else { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = $this->request->module(); - } - - $class = $this->parseClass($module, $layer, $name, $appendSuffix); - } - - return [$module, $class]; - } - - /** - * 实例化应用类库 - * @access public - * @param string $name 类名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object - * @throws ClassNotFoundException - */ - public function create($name, $layer, $appendSuffix = false, $common = 'common') - { - $guid = $name . $layer; - - if ($this->__isset($guid)) { - return $this->__get($guid); - } - - list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $object = $this->__get($class); - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - if (class_exists($class)) { - $object = $this->__get($class); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - $this->__set($guid, $class); - - return $object; - } - - /** - * 实例化(分层)模型 - * @access public - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return Model - * @throws ClassNotFoundException - */ - public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') - { - return $this->create($name, $layer, $appendSuffix, $common); - } - - /** - * 实例化(分层)控制器 格式:[模块名/]控制器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $empty 空控制器名称 - * @return object - * @throws ClassNotFoundException - */ - public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') - { - list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - return $this->make($class, true); - } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { - return $this->make($emptyClass, true); - } - - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** - * 实例化验证类 格式:[模块名/]验证器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return Validate - * @throws ClassNotFoundException - */ - public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') - { - $name = $name ?: $this->config('default_validate'); - - if (empty($name)) { - return new Validate; - } - - return $this->create($name, $layer, $appendSuffix, $common); - } - - /** - * 数据库初始化 - * @access public - * @param mixed $config 数据库配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Query - */ - public function db($config = [], $name = false) - { - return Db::connect($config, $name); - } - - /** - * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @access public - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - * @throws ClassNotFoundException - */ - public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - $info = pathinfo($url); - $action = $info['basename']; - $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); - $class = $this->controller($module, $layer, $appendSuffix); - - if (is_scalar($vars)) { - if (strpos($vars, '=')) { - parse_str($vars, $vars); - } else { - $vars = [$vars]; - } - } - - return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); - } - - /** - * 解析应用类的类名 - * @access public - * @param string $module 模块名 - * @param string $layer 层名 controller model ... - * @param string $name 类名 - * @param bool $appendSuffix - * @return string - */ - public function parseClass($module, $layer, $name, $appendSuffix = false) - { - $name = str_replace(['/', '.'], '\\', $name); - $array = explode('\\', $name); - $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); - $path = $array ? implode('\\', $array) . '\\' : ''; - - return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; - } - - /** - * 获取框架版本 - * @access public - * @return string - */ - public function version() - { - return static::VERSION; - } - - /** - * 是否为调试模式 - * @access public - * @return bool - */ - public function isDebug() - { - return $this->appDebug; - } - - /** - * 获取模块路径 - * @access public - * @return string - */ - public function getModulePath() - { - return $this->modulePath; - } - - /** - * 设置模块路径 - * @access public - * @param string $path 路径 - * @return void - */ - public function setModulePath($path) - { - $this->modulePath = $path; - $this->env->set('module_path', $path); - } - - /** - * 获取应用根目录 - * @access public - * @return string - */ - public function getRootPath() - { - return $this->rootPath; - } - - /** - * 获取应用类库目录 - * @access public - * @return string - */ - public function getAppPath() - { - if (is_null($this->appPath)) { - $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; - } - - return $this->appPath; - } - - /** - * 获取应用运行时目录 - * @access public - * @return string - */ - public function getRuntimePath() - { - return $this->runtimePath; - } - - /** - * 获取核心框架目录 - * @access public - * @return string - */ - public function getThinkPath() - { - return $this->thinkPath; - } - - /** - * 获取路由目录 - * @access public - * @return string - */ - public function getRoutePath() - { - return $this->routePath; - } - - /** - * 获取应用配置目录 - * @access public - * @return string - */ - public function getConfigPath() - { - return $this->configPath; - } - - /** - * 获取配置后缀 - * @access public - * @return string - */ - public function getConfigExt() - { - return $this->configExt; - } - - /** - * 获取应用类库命名空间 - * @access public - * @return string - */ - public function getNamespace() - { - return $this->namespace; - } - - /** - * 设置应用类库命名空间 - * @access public - * @param string $namespace 命名空间名称 - * @return $this - */ - public function setNamespace($namespace) - { - $this->namespace = $namespace; - return $this; - } - - /** - * 是否启用类库后缀 - * @access public - * @return bool - */ - public function getSuffix() - { - return $this->suffix; - } - - /** - * 获取应用开启时间 - * @access public - * @return float - */ - public function getBeginTime() - { - return $this->beginTime; - } - - /** - * 获取应用初始内存占用 - * @access public - * @return integer - */ - public function getBeginMem() - { - return $this->beginMem; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpResponseException; +use think\route\Dispatch; + +/** + * App 应用管理 + */ +class App extends Container +{ + const VERSION = '5.1.41 LTS'; + + /** + * 当前模块路径 + * @var string + */ + protected $modulePath; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = true; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用类库后缀 + * @var bool + */ + protected $suffix = false; + + /** + * 严格路由检测 + * @var bool + */ + protected $routeMust; + + /** + * 应用类库目录 + * @var string + */ + protected $appPath; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bindModule; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + public function __construct($appPath = '') + { + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->path($appPath); + } + + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bindModule = $bind; + return $this; + } + + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); + + return $this; + } + + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + + static::setInstance($this); + + $this->instance('app', $this); + + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, + ]); + + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); + + if (!$this->appDebug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 数据库配置初始化 + Db::init($this->config->pull('database')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 读取语言包 + $this->loadLangPack(); + + // 路由初始化 + $this->routeInit(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return void + */ + public function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; + + // 加载初始化文件 + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; + } else { + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + include_once $path . 'common.php'; + } + + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->bindTo($provider); + } + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config' . DIRECTORY_SEPARATOR; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + $this->setModulePath($path); + + if ($module) { + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); + } + } + + protected function containerConfigUpdate($module) + { + $config = $this->config->get(); + + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + + Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); + $this->route->setConfig($config['app']); + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->checkRequestCache( + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] + ); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + try { + // 初始化应用 + $this->initialize(); + + // 监听app_init + $this->hook->listen('app_init'); + + if ($this->bindModule) { + // 模块/控制器绑定 + $this->route->bind($this->bindModule); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + $dispatch = $this->dispatch; + + if (empty($dispatch)) { + // 路由检测 + $dispatch = $this->routeCheck()->init(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->appDebug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->checkRequestCache( + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') + ); + + $data = null; + } catch (HttpResponseException $exception) { + $dispatch = null; + $data = $exception->getResponse(); + } + + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { + return is_null($data) ? $dispatch->run() : $data; + }); + + $response = $this->middleware->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; + } + + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->setLangset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + $this->setResponseCache($cache); + } + } + + public function setResponseCache($cache) + { + list($key, $expire, $tag) = $cache; + + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public function log($msg, $type = 'info') + { + $this->appDebug && $this->log->record($msg, $type); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 .号分割) + * @return mixed + */ + public function config($name = '') + { + return $this->config->get($name); + } + + /** + * 路由初始化 导入路由定义规则 + * @access public + * @return void + */ + public function routeInit() + { + // 路由检测 + if (is_dir($this->routePath)) { + $files = glob($this->routePath . '*.php'); + foreach ($files as $file) { + $rules = include $file; + if (is_array($rules)) { + $this->route->import($rules); + } + } + } + + if ($this->route->config('route_annotation')) { + // 自动生成路由定义 + if ($this->appDebug) { + $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); + $this->build->buildRoute($suffix); + } + + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; + } + } + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @return Dispatch + */ + public function routeCheck() + { + // 检测路由缓存 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option'); + + if ($option && $this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); + } elseif ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + + // 获取应用调度信息 + $path = $this->request->path(); + + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); + + // 路由检测 返回一个Dispatch对象 + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + if ($option) { + $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); + } else { + $this->cache->tag('route_cache')->set($routeKey, $dispatch); + } + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->make($class, true); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->make($emptyClass, true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->appDebug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 + * @return void + */ + public function setModulePath($path) + { + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + if (is_null($this->appPath)) { + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; + } + + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + +}