diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5ea9ebf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [overtrue] diff --git a/.gitignore b/.gitignore index 3a9875b..006ab6d 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /vendor/ composer.lock +.php-cs-fixer.cache +.ideacghooks.lock + +cghooks.lock diff --git a/LICENSE b/LICENSE index 261e0fa..15c8b29 100755 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 安正超 +Copyright (c) 2015 overtrue 注意:此版本为 3.x 版本,不兼容 2.x 与 1.x,与 [overtrue/wechat 3.x](https://github.com/overtrue/wechat) 同步 +微信 SDK EasyWeChat for Laravel, 基于 [w7corp/easywechat](https://github.com/w7corp/easywechat) -微信 SDK for Laravel 5 / Lumen, 基于 [overtrue/wechat](https://github.com/overtrue/wechat) +[![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me-button-s.svg?raw=true)](https://github.com/sponsors/overtrue) -本项目只适用于,只有一个固定的账号,如果是开发微信公众号管理系统就不要使用了,直接用 [overtrue/wechat](https://github.com/overtrue/wechat) 更方便些。 +> 7.x 起不再默认支持 Lumen。 -> 交流QQ群:319502940 +## 框架要求 -## 安装 - -1. 安装包文件 - - ```shell - composer require "overtrue/laravel-wechat:~3.0" - ``` - -> 如果你用了 laravel-debugbar,请禁用或者关掉,否则这模块别想正常使用!!! +- overtrue/laravel-wechat:^7.0 -> Laravel >= 8.0 +- overtrue/laravel-wechat:^6.0 -> Laravel/Lumen >= 7.0 +- overtrue/laravel-wechat:^5.1 -> Laravel/Lumen >= 5.1 -> 如果你用了 laravel-debugbar,请禁用或者关掉,否则这模块别想正常使用!!! +## 安装 -> 如果你用了 laravel-debugbar,请禁用或者关掉,否则这模块别想正常使用!!! +```bash +composer require overtrue/laravel-wechat:^7.2 +``` ## 配置 -### Laravel 应用 - -1. 注册 `ServiceProvider`: - - ```php - Overtrue\LaravelWechat\ServiceProvider::class, - ``` - -2. 创建配置文件: - - ```shell - php artisan vendor:publish --provider="Overtrue\LaravelWechat\ServiceProvider" - ``` - -3. 请修改应用根目录下的 `config/wechat.php` 中对应的项即可; - -4. (可选)添加外观到 `config/app.php` 中的 `aliases` 部分: - - ```php - 'EasyWeChat' => Overtrue\LaravelWechat\Facade::class, - ``` +1. 创建配置文件: -### Lumen 应用 - -1. 在 `bootstrap/app.php` 中 82 行左右: - - ```php - $app->register(Overtrue\LaravelWechat\ServiceProvider::class); - ``` +```shell +php artisan vendor:publish --provider="Overtrue\\LaravelWeChat\\ServiceProvider" +``` -2. ENV 中支持以下配置: +2. 可选,添加别名 ```php -WECHAT_APPID -WECHAT_SECRET -WECHAT_TOKEN -WECHAT_AES_KEY - -WECHAT_LOG_LEVEL -WECHAT_LOG_FILE - -WECHAT_OAUTH_SCOPES -WECHAT_OAUTH_CALLBACK - -WECHAT_PAYMENT_MERCHANT_ID -WECHAT_PAYMENT_KEY -WECHAT_PAYMENT_CERT_PATH -WECHAT_PAYMENT_KEY_PATH -WECHAT_PAYMENT_DEVICE_INFO -WECHAT_PAYMENT_SUB_APP_ID -WECHAT_PAYMENT_SUB_MERCHANT_ID -WECHAT_ENABLE_MOCK +'aliases' => [ + // ... + 'EasyWeChat' => Overtrue\LaravelWeChat\EasyWeChat::class, +], ``` -3. 如果你习惯使用 `config/wechat.php` 来配置的话,将 `vendor/overtrue/laravel-wechat/src/config.php` 拷贝到`app/config`目录下,并将文件名改成`wechat.php`。 +3. 每个模块基本都支持多账号,默认为 `default`。 ## 使用 -### Laravel <= 5.1 - -1. Laravel 5 起默认启用了 CSRF 中间件,因为微信的消息是 POST 过来,所以会触发 CSRF 检查导致无法正确响应消息,所以请去除默认的 CSRF 中间件,改成路由中间件。可以参考我的写法:[overtrue gist:Kernel.php](https://gist.github.com/overtrue/ff6cd3a4e869fbaf6c01#file-kernel-php-L31) -2. 5.1 里的 CSRF 已经带了可忽略部分url的功能,你可以参考:http://laravel.com/docs/master/routing#csrf-protection - -### Laravel 5.2+ +:rotating_light: 在中间件 `App\Http\Middleware\VerifyCsrfToken` 排除微信相关的路由,如: -Laravel 5.2 以后的版本默认启用了 web 中间件,意味着 CSRF 会默认打开,有两种方案: - -1. 在 CSRF 中间件里排除微信相关的路由 -2. 关掉 CSRF 中间件(极不推荐) - - -下面以接收普通消息为例写一个例子: +```php +protected $except = [ + // ... + 'wechat', +]; +``` +对于 Laravel 11.x 可以使用`bootstrap/app.php` 中的`$middleware->validateCsrfTokens`方法: +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + // ... + 'wechat', + ]); +}) +``` -> 假设您的域名为 `overtrue.me` 那么请登录微信公众平台 “开发者中心” 修改 “URL(服务器配置)” 为: `http://overtrue.me/wechat`。 +下面以接收普通消息为例写一个例子。 路由: ```php -Route::any('/wechat', 'WechatController@serve'); +Route::any('/wechat', 'WeChatController@serve'); ``` > 注意:一定是 `Route::any`, 因为微信服务端认证的时候是 `GET`, 接收用户消息时是 `POST` ! -然后创建控制器 `WechatController`: +然后创建控制器 `WeChatController`: ```php server->setMessageHandler(function($message){ + $server = app('easywechat.official_account')->getServer(); + + $server->with(function($message){ return "欢迎关注 overtrue!"; }); - Log::info('return response.'); - - return $wechat->server->serve(); + return $server->serve(); } } ``` -> 上面例子里的 Log 是 Laravel 组件,所以它的日志不会写到 EasyWeChat 里的,建议把 wechat 的日志配置到 Laravel 同一个日志文件,便于调试。 +## OAuth 中间件 -### 我们有以下方式获取 SDK 的服务实例 +使用中间件的情况下 `app/config/easywechat.php` 中的 `oauth.callback` 就随便填写吧(因为用不着了 :smile:)。 -##### 使用容器的自动注入 +1. 在 `app/Http/Kernel.php` 中添加路由中间件: ```php - \Overtrue\LaravelWeChat\Middleware\OAuthAuthenticate::class, +]; +``` -namespace App\Http\Controllers; +2. 在路由中添加中间件: -use EasyWeChat\Foundation\Application; +```php +//... +Route::group(['middleware' => ['web', 'easywechat.oauth']], function () { + Route::get('/user', function () { + $user = session('easywechat.oauth_user.default'); // 拿到授权用户资料 -class WechatController extends Controller -{ + dd($user); + }); +}); +``` - public function demo(Application $wechat) - { - // $wechat 则为容器中 EasyWeChat\Foundation\Application 的实例 - } -} +中间件支持指定配置名称:`'easywechat.oauth:default'`,当然,你也可以在中间件参数指定当前的 `scopes`: + +```php +Route::group(['middleware' => ['easywechat.oauth:snsapi_userinfo']], function () { + // ... +}); + +// 或者指定账户的同时指定 scopes: +Route::group(['middleware' => ['easywechat.oauth:default,snsapi_userinfo']], function () { + // ... +}); ``` -##### 使用外观 +上面的路由定义了 `/user` 是需要微信授权的,那么在这条路由的**回调 或 控制器对应的方法里**, 你就可以从 `session('easywechat.oauth_user.default')` 拿到已经授权的用户信息了。 + +## 模拟授权 -在 `config/app.php` 中 `alias` 部分添加外观别名: +有时候我们希望在本地开发完成后线上才真实的走微信授权流程,这将减少我们的开发成本,那么你需要做以下两步: +1. 准备模拟授权资料: +2. ```php -'EasyWeChat' => Overtrue\LaravelWechat\Facade::class, +use Illuminate\Support\Arr; +use Overtrue\Socialite\User as SocialiteUser; + +$user = new SocialiteUser([ + 'id' => 'mock-openid', + 'name' => 'overtrue', + 'nickname' => 'overtrue', + 'avatar' => 'http://example.com/avatars/overtrue.png', + 'email' => null, + 'original' => [], + 'provider' => 'WeChat', + ]); ``` -然后就可以在任何地方使用外观方式调用 SDK 对应的服务了: +> 以上字段在 scope 为 `snsapi_userinfo` 时尽可能配置齐全哦,当然,如果你的模式只是 `snsapi_base` 的话只需要 `openid` 就好了。 + +2. 将资料写入 session: + +> 注意:一定要在调用 OAuth 中间件之前写入,比如你可以创建一个全局中间件来完成这件事儿,只在开发环境启用即可。 ```php - $wechatServer = EasyWeChat::server(); // 服务端 - $wechatUser = EasyWeChat::user(); // 用户服务 - // ... 其它同理 +session(['easywechat.oauth_user.default' => $user]); // 同理,`default` 可以更换为您对应的其它配置名 ``` +## 事件 -## OAuth 中间件 - -使用中间件的情况下 `app/config/wechat.php` 中的 `oauth.callback` 就随便填写吧(因为用不着了 :smile:)。 +> 你可以监听相应的事件,并对事件发生后执行相应的操作。 -1. 在 `app/Http/Kernel.php` 中添加路由中间件: +- OAuth 网页授权:`Overtrue\LaravelWeChat\Events\WeChatUserAuthorized` ```php -protected $routeMiddleware = [ - // ... - 'wechat.oauth' => \Overtrue\LaravelWechat\Middleware\OAuthAuthenticate::class, -]; +// 该事件有以下属性 +$event->user; // 同 session('easywechat.oauth_user.default') 一样 +$event->isNewSession; // 是不是新的会话(第一次创建 session 时为 true) +$event->account; // 当前中间件所使用的账号,对应在配置文件中的配置项名称 ``` -2. 在路由中添加中间件: -以 5.2 为例: +## 开放平台支持 -```php -//... -Route::group(['middleware' => ['web', 'wechat.oauth']], function () { - Route::get('/user', function () { - $user = session('wechat.oauth_user'); // 拿到授权用户资料 +您可以适用内置的 `Overtrue\LaravelWeChat\Traits\HandleOpenPlatformServerEvents` 来快速完成开放平台的服务端验证工作: - dd($user); - }); -}); +*routes/web.php:* +```php +Route::any('/open-platform/server', OpenPlatformController::class); ``` -_如果你在用 5.1 上面没有 'web' 中间件_ -当然,你也可以在中间件参数指定当前的 `scopes`: +*app/Http/Controllers/OpenPlatformController.php:* ```php -Route::group(['middleware' => ['web', 'wechat.oauth:snsapi_userinfo']], function () { - // ... -}); -``` +handleServerEvents($app); + } +} +``` -有时候我们希望在本地开发完成后线上才真实的走微信授权流程,这将减少我们的开发成本,那么你需要做以下两步: +Tips: 默认会根据微信开放平台的推送内容触发如下事件,你可以监听相应的事件并进行处理: -1. 在 config/wechat.php 中将:'enable_mock' 启用,不论你是用 `.env` 文件里 `WECHAT_ENABLE_MOCK=true` 或者其它什么方法都可以。 -2. 在 config/wechat.php 中配置 `mock_user` 为微信的模拟的用户资料: +- 授权方成功授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\Authorized` +- 授权方更新授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\AuthorizeUpdated` +- 授权方取消授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\Unauthorized` +- 开放平台推送 VerifyTicket:`Overtrue\LaravelWeChat\Events\OpenPlatform\VerifyTicketRefreshed` ```php - /* - * 开发模式下的免授权模拟授权用户资料 - * - * 当 enable_mock 为 true 则会启用模拟微信授权,用于开发时使用,开发完成请删除或者改为 false 即可 - */ - 'enable_mock' => env('WECHAT_ENABLE_MOCK', true), - 'mock_user' => [ - "openid" =>"odh7zsgI75iT8FRh0fGlSojc9PWM", - // 以下字段为 scope 为 snsapi_userinfo 时需要 - "nickname" => "overtrue", - "sex" =>"1", - "language" =>"zh_CN", - "province" =>"北京", - "city" =>"北京", - "country" =>"中国", - "headimgurl" => "http://wx.qlogo.cn/mmopen/C2rEUskXQiblFYMUl9O0G05Q6pKibg7V1WpHX6CIQaic824apriabJw4r6EWxziaSt5BATrlbx1GVzwW2qjUCqtYpDvIJLjKgP1ug/0", -], +// 事件有如下属性 +$message = $event->payload; // 开放平台事件通知内容 ``` -以上字段在 scope 为 `snsapi_userinfo` 时尽可能配置齐全哦,当然,如果你的模式只是 `snsapi_base` 的话只需要 `openid` 就好了。 +配置后 `http://example.com/open-platform/server` 则为开放平台第三方应用设置的授权事件接收 URL。 -## 授权事件 -每次授权均会触发 `Overtrue\LaravelWechat\Events\WeChatUserAuthorized`,你可以监听该事件,该事件有两个属性: +更多 SDK 的具体使用请参考: -```php -$event->user; // 同 session('wechat.oauth_user') 一样 -$event->isNewSession; // 是不是新的会话(第一次创建 session 时为 true) -``` +## :heart: Sponsor me + +[![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me.svg?raw=true)](https://github.com/sponsors/overtrue) + +如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue) + +## Project supported by JetBrains + +Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects. + +[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/overtrue) + + +## PHP 扩展包开发 -更多 SDK 的具体使用请参考:https://easywechat.org +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) ## License diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..10d59f7 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,241 @@ +# EasyWeChat for Laravel + +WeChat SDK [w7corp/easywechat](https://github.com/w7corp/easywechat) wrapper for Laravel. + +[![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me-button-s.svg?raw=true)](https://github.com/sponsors/overtrue) + +## Requirements + +- overtrue/laravel-wechat:^7.0 -> Laravel >= 8.0 +- overtrue/laravel-wechat:^6.0 -> Laravel/Lumen >= 7.0 +- overtrue/laravel-wechat:^5.1 -> Laravel/Lumen >= 5.1 + +## Installation + +```bash +composer require "overtrue/laravel-wechat" +``` + +## Config + +1. publishe the config file to `config` directory: + + ```shell + php artisan vendor:publish --provider="Overtrue\LaravelWeChat\ServiceProvider" + ``` + +2. (Optional) You can add the alias to `config/app.php`: + + ```php + 'aliases' => [ + // ... + 'EasyWeChat' => Overtrue\LaravelWeChat\EasyWeChat::class, + ], + ``` + +3. Each module basically supports multiple accounts, the default name is `default`. + +## Usage + +Ignore the CSRF check for WeChat related routes: + +```php +protected $except = [ + // ... + 'wechat', +]; +``` + +The following is an example written to receive a server message: + +Routes: + +```php +Route::any('/wechat', 'WeChatController@serve'); +``` + +> **Note** +> +> It must be `Route::any`, because the WeChat server validation is `GET` request, and when receiving user messages is `POST` request! + +Then, let's create a controller `WeChatController`: + +```php +getServer(); + + $server->with(function($message){ + return "欢迎关注 overtrue!"; + }); + + return $server->serve(); + } +} +``` + +## OAuth middleware + +If you're using middleware, just fill in `oauth.callback` with any value in `app/config/wechat.php` (because you won't need it :smile:). + +1. Register the middleware to `app/Http/Kernel.php`: + + ```php + protected $routeMiddleware = [ + // ... + 'easywechat.oauth' => \Overtrue\LaravelWeChat\Middleware\OAuthAuthenticate::class, + ]; + ``` + +2. Add the middleware to oauth route: + + ```php + //... + Route::group(['middleware' => ['web', 'easywechat.oauth']], function () { + Route::get('/user', function () { + $user = session('easywechat.oauth_user.default'); // oauth user + + \dd($user); + }); + }); + ``` + + You can also set the config name and scopes as middleware paremeter: + + ```php + // scopes + Route::group(['middleware' => ['easywechat.oauth:snsapi_userinfo']], function () { + // ... + }); + + // account name and scopes: + Route::group(['middleware' => ['easywechat.oauth:default,snsapi_userinfo']], function () { + // ... + }); + ``` + + The above route defines `/user` as requiring authorization from WeChat, so in the **callback or controller method** of this route, you can get the authorized user information from `session('easywechat.oauth_user.default')`. + +## Mock Authorization + +Sometimes we want to go real WeChat authorization process only after local development is completed online, which will reduce our development cost, then you need to do the following two steps. + +1. Prepare mock authorization information: + + ```php + use Illuminate\Support\Arr; + use Overtrue\Socialite\User as SocialiteUser; + + $user = new SocialiteUser([ + 'id' => 'mock-openid', + 'name' => 'overtrue', + 'nickname' => 'overtrue', + 'avatar' => 'http://example.com/avatars/overtrue.png', + 'email' => null, + 'original' => [], + 'provider' => 'WeChat', + ]); + ``` + + > If your schema is only `snsapi_base`, you only need `openid`. + +2. Write the information to the session: + + ```php + // Similarly, `default` can be replaced with your corresponding other configuration name + session(['easywechat.oauth_user.default' => $user]); + ``` + + > **Note** + > + > Be sure to write before calling the OAuth middleware, for example, you can create a global middleware to do this, and just enable it in the development environment. + + +## Events + +You can listen to the corresponding events and perform the corresponding actions when they occur. + +- **OAuth authorized**: `Overtrue\LaravelWeChat\Events\WeChatUserAuthorized` + +```php +// The event has the following properties +$event->user; // same as session('easywechat.oauth_user.default') +$event->isNewSession; // if it is a new session (true when first creating a session) +$event->account; // the current account used by the middleware, corresponding to the name of the configuration item in the configuration file +``` + + +## Open Platform Support + +You can apply the built-in `Overtrue\LaravelWeChat\Traits\HandleOpenPlatformServerEvents` to quickly complete the server-side validation for the open platform: + +*routes/web.php:* + +```php +Route::any('/open-platform/server', OpenPlatformController::class); +``` + +*app/Http/Controllers/OpenPlatformController.php:* + +```php +handleServerEvents($app); + } +} +``` + +> **Note** +> +> By default, the following events will be triggered based on the push content of WeChat Open Platform, you can listen to the corresponding events and process them. + +- **Authorized party successfully authorized**: `Overtrue\LaravelWeChat\Events\OpenPlatform\Authorized` +- **Authorized party updates authorization**: `Overtrue\LaravelWeChat\Events\OpenPlatform\AuthorizeUpdated` +- **Authorizer deauthorized**: `Overtrue\LaravelWeChat\Events\OpenPlatform\Unauthorized` +- **OpenPlatform Push VerifyTicket**: `Overtrue\LaravelWeChat\Events\OpenPlatform\VerifyTicketRefreshed` + +```php +// Events have the following properties +$message = $event->payload; // Open Platform event notification content +``` + +After configuration `http://example.com/open-platform/server` is the authorized event receiving URL set for the Open Platform third party application. + + +For more SDK specific usage, please refer to: + +## :heart: Sponsor me + +[![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me.svg?raw=true)](https://github.com/sponsors/overtrue) + +If you like my project and want to support it, [click here :heart:](https://github.com/sponsors/overtrue) + +## Project supported by JetBrains + +Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects. + +[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/overtrue) + +## License + +MIT diff --git a/composer.json b/composer.json old mode 100755 new mode 100644 index b74ae3a..878bb04 --- a/composer.json +++ b/composer.json @@ -1,22 +1,67 @@ { - "name": "overtrue/laravel-wechat", - "description": "微信 SDK for Laravel", - "keywords": ["wechat", "weixin","laravel", "sdk"], - "require": { - "overtrue/wechat": "~3.0" + "name": "overtrue/laravel-wechat", + "description": "微信 SDK for Laravel", + "keywords": [ + "wechat", + "weixin", + "laravel", + "sdk" + ], + "license": "MIT", + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "require": { + "illuminate/container": "^9.0|^10.0|^11.0|^12.0", + "w7corp/easywechat": "^6.0.0" + }, + "require-dev": { + "laravel/framework": "^10.0", + "jetbrains/phpstorm-attributes": "^1.0", + "brainmaestro/composer-git-hooks": "dev-master", + "laravel/pint": "^1.5" + }, + "autoload": { + "psr-4": { + "Overtrue\\LaravelWeChat\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Overtrue\\LaravelWeChat\\ServiceProvider" + ] }, - "autoload": { - "psr-4": { - "Overtrue\\LaravelWechat\\": "src/" - } - }, - - "license": "MIT", - "minimum-stability" : "dev", - "authors": [ - { - "name": "overtrue", - "email": "anzhengchao@gmail.com" - } - ] + "hooks": { + "pre-commit": [ + "composer check-style" + ], + "pre-push": [ + "composer check-style" + ] + } + }, + "scripts": { + "post-update-cmd": [ + "cghooks remove", + "cghooks add --ignore-lock", + "cghooks update" + ], + "post-merge": "composer install", + "post-install-cmd": [ + "cghooks remove", + "cghooks add --ignore-lock", + "cghooks update" + ], + "check-style": "vendor/bin/pint --test", + "fix-style": "vendor/bin/pint" + }, + "config": { + "allow-plugins": { + "easywechat-composer/easywechat-composer": true + } + } } diff --git a/config/easywechat.php b/config/easywechat.php new file mode 100755 index 0000000..721c102 --- /dev/null +++ b/config/easywechat.php @@ -0,0 +1,168 @@ + [ + 'http' => [ + 'timeout' => 5.0, + ], + ], + + /* + * 公众号 + */ + 'official_account' => [ + 'default' => [ + 'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''), // AppID + 'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', ''), // AppSecret + 'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''), // Token + 'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''), // EncodingAESKey + + /* + * OAuth 配置 + * + * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login + * callback:OAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。) + * enforce_https:是否强制使用 HTTPS 跳转 + */ + // 'oauth' => [ + // 'scopes' => array_map('trim', explode(',', env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_SCOPES', 'snsapi_userinfo'))), + // 'callback' => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/examples/oauth_callback.php'), + // 'enforce_https' => true, + // ], + + /** + * 接口请求相关配置,超时时间等,具体可用参数请参考: + * https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26 + */ + //'http' => [ + // 'timeout' => 5.0, + // // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri + // 'base_uri' => 'https://api.weixin.qq.com/', + //], + ], + ], + + /* + * 开放平台第三方平台 + */ + // 'open_platform' => [ + // 'default' => [ + // 'app_id' => env('WECHAT_OPEN_PLATFORM_APPID', ''), + // 'secret' => env('WECHAT_OPEN_PLATFORM_SECRET', ''), + // 'token' => env('WECHAT_OPEN_PLATFORM_TOKEN', ''), + // 'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''), + +/** + * 接口请求相关配置,超时时间等,具体可用参数请参考: + * https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26 + */ + // 'http' => [ + // 'timeout' => 5.0, + // // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri + // 'base_uri' => 'https://api.weixin.qq.com/', + // ], + // ], + // ], + + /* + * 小程序 + */ + // 'mini_app' => [ + // 'default' => [ + // 'app_id' => env('WECHAT_MINI_APP_APPID', ''), + // 'secret' => env('WECHAT_MINI_APP_SECRET', ''), + // 'token' => env('WECHAT_MINI_APP_TOKEN', ''), + // 'aes_key' => env('WECHAT_MINI_APP_AES_KEY', ''), + +/** + * 接口请求相关配置,超时时间等,具体可用参数请参考: + * https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26 + */ + // 'http' => [ + // 'timeout' => 5.0, + // // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri + // 'base_uri' => 'https://api.weixin.qq.com/', + // ], + // ], + // ], + + /* + * 微信支付 + */ + // 'pay' => [ + // 'default' => [ + // 'app_id' => env('WECHAT_PAY_APPID', ''), + // 'mch_id' => env('WECHAT_PAY_MCH_ID', 'your-mch-id'), + // 'private_key' => '/data/private/certs/apiclient_key.pem', + // 'certificate' => '/data/private/certs/apiclient_cert.pem', + // 'notify_url' => 'http://example.com/payments/wechat-notify', // 默认支付结果通知地址 + // /** + // * 证书序列号,可通过命令从证书获取: + // * `openssl x509 -in application_cert.pem -noout -serial` + // */ + // 'certificate_serial_no' => '6F2BADBE1738B07EE45C6A85C5F86EE343CAABC3', + // + // 'http' => [ + // 'base_uri' => 'https://api.mch.weixin.qq.com/', + // ], + // + // // v2 API 秘钥 + // //'v2_secret_key' => '26db3e15cfedb44abfbb5fe94fxxxxx', + // + // // v3 API 秘钥 + // //'secret_key' => '43A03299A3C3FED3D8CE7B820Fxxxxx', + // + // // 注意 此处为微信支付平台证书 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml + // 'platform_certs' => [ + // '/data/private/certs/platform_key.pem', + // ], + // ], + // ], + + /* + * 企业微信 + */ + // 'work' => [ + // 'default' => [ + // 'corp_id' => env('WECHAT_WORK_CORP_ID', ''), + // 'secret' => env('WECHAT_WORK_SECRET', ''), + // 'token' => env('WECHAT_WORK_TOKEN', ''), + // 'aes_key' => env('WECHAT_WORK_AES_KEY', ''), + +/** + * 接口请求相关配置,超时时间等,具体可用参数请参考: + * https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26 + */ + // 'http' => [ + // 'timeout' => 5.0, + // // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri + // 'base_uri' => 'https://api.weixin.qq.com/', + // ], + // ], + // ], + + /* + * 企业微信开放平台 + */ + // 'open_work' => [ + // 'default' => [ + // 'corp_id' => env('WECHAT_OPEN_WORK_CORP_ID', ''), + // 'provider_secret' => env('WECHAT_OPEN_WORK_SECRET', ''), + // 'token' => env('WECHAT_OPEN_WORK_TOKEN', ''), + // 'aes_key' => env('WECHAT_OPEN_WORK_AES_KEY', ''), + +/** + * 接口请求相关配置,超时时间等,具体可用参数请参考: + * https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26 + */ + // 'http' => [ + // 'timeout' => 5.0, + // // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri + // 'base_uri' => 'https://api.weixin.qq.com/', + // ], + // ], + // ], +]; diff --git a/src/CacheBridge.php b/src/CacheBridge.php deleted file mode 100644 index 3669ad4..0000000 --- a/src/CacheBridge.php +++ /dev/null @@ -1,80 +0,0 @@ - Events\Authorized::class, - 'unauthorized' => Events\Unauthorized::class, - 'updateauthorized' => Events\UpdateAuthorized::class, - ]; - - /** - * Handle request and fire events. - * - * @return string - */ - public function openPlatformServe() - { - $app = new Application(config('wechat')); - - $server = $app->open_platform->server; - - list($name, $event) = $server->listServe(); - - if ($class = Arr::get($this->events, $name)) { - Event::fire(new $class($event)); - } - - return 'success'; - } -} diff --git a/src/EasyWeChat.php b/src/EasyWeChat.php new file mode 100755 index 0000000..7a9f9a4 --- /dev/null +++ b/src/EasyWeChat.php @@ -0,0 +1,43 @@ +message = $message; - } - - /** - * Get the channels the event should be broadcast on. - * - * @return array - */ - public function broadcastOn() - { - return []; - } } diff --git a/src/Events/OpenPlatform/OpenPlatformEvent.php b/src/Events/OpenPlatform/OpenPlatformEvent.php new file mode 100644 index 0000000..72e7cd2 --- /dev/null +++ b/src/Events/OpenPlatform/OpenPlatformEvent.php @@ -0,0 +1,15 @@ +payload[substr($name, 3)] ?? null; + } +} diff --git a/src/Events/OpenPlatform/Unauthorized.php b/src/Events/OpenPlatform/Unauthorized.php index 090d8e0..24f9426 100755 --- a/src/Events/OpenPlatform/Unauthorized.php +++ b/src/Events/OpenPlatform/Unauthorized.php @@ -1,33 +1,13 @@ message = $message; - } - - /** - * Get the channels the event should be broadcast on. - * - * @return array - */ - public function broadcastOn() - { - return []; - } } diff --git a/src/Events/OpenPlatform/UpdateAuthorized.php b/src/Events/OpenPlatform/UpdateAuthorized.php deleted file mode 100755 index 0669263..0000000 --- a/src/Events/OpenPlatform/UpdateAuthorized.php +++ /dev/null @@ -1,33 +0,0 @@ -message = $message; - } - - /** - * Get the channels the event should be broadcast on. - * - * @return array - */ - public function broadcastOn() - { - return []; - } -} diff --git a/src/Events/OpenPlatform/VerifyTicketRefreshed.php b/src/Events/OpenPlatform/VerifyTicketRefreshed.php new file mode 100644 index 0000000..b51ea3d --- /dev/null +++ b/src/Events/OpenPlatform/VerifyTicketRefreshed.php @@ -0,0 +1,13 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\LaravelWeChat\Events; use Illuminate\Queue\SerializesModels; use Overtrue\Socialite\User; @@ -9,50 +18,25 @@ class WeChatUserAuthorized { use SerializesModels; - public $user; - public $isNewSession; - - /** - * Create a new event instance. - * - * @param \Overtrue\Socialite\User $user - * @param bool $isNewSession - * - * @return void - */ - public function __construct(User $user, $isNewSession = false) - { - $this->user = $user; - $this->isNewSession = $isNewSession; + public function __construct( + public User $user, + public bool $isNewSession = false, + public string $account = '' + ) { } - /** - * Retrieve the authorized user. - * - * @return \Overtrue\Socialite\User - */ - public function getUser() + public function getUser(): User { return $this->user; } - /** - * Check the user session is first created. - * - * @return bool - */ - public function isNewSession() + public function getAccount(): string { - return $this->isNewSession; + return $this->account; } - /** - * Get the channels the event should be broadcast on. - * - * @return array - */ - public function broadcastOn() + public function isNewSession(): bool { - return []; + return $this->isNewSession; } } diff --git a/src/Facade.php b/src/Facade.php deleted file mode 100755 index 65df272..0000000 --- a/src/Facade.php +++ /dev/null @@ -1,37 +0,0 @@ -$name; - } -} diff --git a/src/Middleware/OAuthAuthenticate.php b/src/Middleware/OAuthAuthenticate.php index 50cea92..2eb94f9 100644 --- a/src/Middleware/OAuthAuthenticate.php +++ b/src/Middleware/OAuthAuthenticate.php @@ -1,113 +1,103 @@ + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\LaravelWeChat\Middleware; use Closure; -use EasyWeChat\Foundation\Application; -use Event; -use Log; -use Overtrue\LaravelWechat\Events\WeChatUserAuthorized; +use Illuminate\Http\Request; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Session; +use Illuminate\Support\Str; +use JetBrains\PhpStorm\Pure; +use Overtrue\LaravelWeChat\Events\WeChatUserAuthorized; /** - * Class OAuthAuthenticate. + * 仅适用于微信公众号, 企业微信的网页应用。 */ class OAuthAuthenticate { - /** - * Use Service Container would be much artisan. - */ - private $wechat; - - /** - * Inject the wechat service. - */ - public function __construct(Application $wechat) - { - $this->wechat = $wechat; - } - - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $scopes - * - * @return mixed - */ - public function handle($request, Closure $next, $scopes = null) - { - $isNewSession = false; - $onlyRedirectInWeChatBrowser = config('wechat.oauth.only_wechat_browser', false); - - if ($onlyRedirectInWeChatBrowser && !$this->isWeChatBrowser($request)) { - if (config('debug')) { - Log::debug('[not wechat browser] skip wechat oauth redirect.'); - } - - return $next($request); + public function handle( + Request $request, + Closure $next, + string $account = 'default', + string $scope = null, + ?string $type = 'service' + ): mixed { + // 保证兼容性参数处理 + $prefix = ('work' !== $type) ? 'official_account' : 'work'; + + $sessionKey = \sprintf('easywechat.oauth_user.%s', $account); + $name = \sprintf('easywechat.%s.%s', $prefix, $account); + $config = config($name, []); + $service = app($name); + + $scope = $scope ?: Arr::get($config, 'oauth.scopes', ['snsapi_base']); + + if (\is_string($scope)) { + $scope = \array_map('trim', explode(',', $scope)); } - $scopes = $scopes ?: config('wechat.oauth.scopes', ['snsapi_base']); + if (Session::has($sessionKey)) { + event(new WeChatUserAuthorized(session($sessionKey), false, $account)); - if (is_string($scopes)) { - $scopes = array_map('trim', explode(',', $scopes)); + return $next($request); } - if (!session('wechat.oauth_user') || $this->needReauth($scopes)) { - if ($request->has('code')) { - session(['wechat.oauth_user' => $this->wechat->oauth->user()]); - $isNewSession = true; - - Event::fire(new WeChatUserAuthorized(session('wechat.oauth_user'), $isNewSession)); + // 是否强制使用 HTTPS 跳转 + $enforceHttps = Arr::get($config, 'oauth.enforce_https', false); - return redirect()->to($this->getTargetUrl($request)); - } + if ($request->has('code')) { + session([$sessionKey => $service->getOAuth()->userFromCode($request->query('code'))]); - session()->forget('wechat.oauth_user'); + event(new WeChatUserAuthorized(session($sessionKey), true, $account)); - return $this->wechat->oauth->scopes($scopes)->redirect($request->fullUrl()); + return redirect()->to($this->getIntendUrl($request, $enforceHttps)); } - Event::fire(new WeChatUserAuthorized(session('wechat.oauth_user'), $isNewSession)); + session()->forget($sessionKey); - return $next($request); + // 跳转到微信授权页 + return redirect()->away( + $service->getOAuth()->scopes($scope)->redirect($this->getRedirectUrl($request, $enforceHttps)) + ); } - /** - * Build the target business url. - * - * @param Request $request - * - * @return string - */ - protected function getTargetUrl($request) + protected function getIntendUrl(Request $request, bool $https = false): string { - $queries = array_except($request->query(), ['code', 'state']); + $query = Arr::except($request->query(), ['code', 'state']); + $url = $request->url(); + + if ($https) { + $url = $this->ensureHttpsScheme($url); + } - return $request->url().(empty($queries) ? '' : '?'.http_build_query($queries)); + return $url.(empty($query) ? '' : '?'.http_build_query($query)); } - /** - * Is different scopes. - * - * @param array $scopes - * - * @return bool - */ - protected function needReauth($scopes) + protected function getRedirectUrl(Request $request, bool $https = false): string { - return session('wechat.oauth_user.original.scope') == 'snsapi_base' && in_array("snsapi_userinfo", $scopes); + if (! $https) { + return $request->fullUrl(); + } + + return $this->ensureHttpsScheme($request->fullUrl()); } - /** - * Detect current user agent type. - * - * @param \Illuminate\Http\Request $request - * @return bool - */ - protected function isWeChatBrowser($request) + #[Pure] + protected function ensureHttpsScheme(string $url): string { - return strpos($request->header('user_agent'), 'MicroMessenger') !== false; + if (Str::startsWith($url, 'http://')) { + $url = Str::replaceFirst('http', 'https', $url); + } + + return $url; } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index b360701..4a6caa1 100755 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -1,131 +1,81 @@ + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\LaravelWeChat; + +use EasyWeChat\MiniApp\Application as MiniApp; +use EasyWeChat\OfficialAccount\Application as OfficialAccount; +use EasyWeChat\OpenPlatform\Application as OpenPlatform; +use EasyWeChat\OpenWork\Application as OpenWork; +use EasyWeChat\Pay\Application as Payment; +use EasyWeChat\Work\Application as Work; use Illuminate\Support\ServiceProvider as LaravelServiceProvider; -use Laravel\Lumen\Application as LumenApplication; -use Overtrue\LaravelWechat\ServiceProviders\RouteServiceProvider; -use Overtrue\Socialite\User as SocialiteUser; class ServiceProvider extends LaravelServiceProvider { - /** - * 延迟加载. - * - * @var bool - */ - protected $defer = true; - - /** - * Boot the provider. - * - * @return void - */ - public function boot() - { - $this->setupConfig(); - - if ($this->isEnableOpenPlatform()) { - $this->app->register(RouteServiceProvider::class); - } - } - - /** - * Setup the config. - * - * @return void - */ protected function setupConfig() { - $source = realpath(__DIR__.'/config.php'); - - if ($this->app instanceof LaravelApplication) { - if ($this->app->runningInConsole()) { - $this->publishes([ - $source => config_path('wechat.php'), - ]); - } + $source = realpath(__DIR__.'/../config/easywechat.php'); - // 创建模拟授权 - $this->setUpMockAuthUser(); - } elseif ($this->app instanceof LumenApplication) { - $this->app->configure('wechat'); + if ($this->app->runningInConsole()) { + $this->publishes([$source => \config_path('easywechat.php')], 'laravel-wechat'); } - $this->mergeConfigFrom($source, 'wechat'); + $this->mergeConfigFrom($source, 'easywechat'); } - /** - * Register the provider. - * - * @return void - */ public function register() { - $this->app->singleton(EasyWeChatApplication::class, function ($laravelApp) { - $app = new EasyWeChatApplication(config('wechat')); - if (config('wechat.use_laravel_cache')) { - $app->cache = new CacheBridge(); - } - $app->server->setRequest($laravelApp['request']); + $this->setupConfig(); - return $app; - }); + $apps = [ + 'official_account' => OfficialAccount::class, + 'work' => Work::class, + 'mini_app' => MiniApp::class, + 'pay' => Payment::class, + 'open_platform' => OpenPlatform::class, + 'open_work' => OpenWork::class, + ]; + + foreach ($apps as $name => $class) { + if (empty(config('easywechat.'.$name))) { + continue; + } - $this->app->alias(EasyWeChatApplication::class, 'wechat'); - $this->app->alias(EasyWeChatApplication::class, 'easywechat'); - } + if (! empty(config('easywechat.'.$name.'.app_id')) || ! empty(config('easywechat.'.$name.'.corp_id'))) { + $accounts = [ + 'default' => config('easywechat.'.$name), + ]; + config(['easywechat.'.$name.'.default' => $accounts['default']]); + } else { + $accounts = config('easywechat.'.$name); + } - /** - * 提供的服务 - * - * @return array - */ - public function provides() - { - return ['wechat', EasyWeChatApplication::class]; - } + foreach ($accounts as $account => $config) { + $this->app->bind("easywechat.{$name}.{$account}", function ($laravelApp) use ($config, $class) { + $app = new $class(array_merge(config('easywechat.defaults', []), $config)); - /** - * 创建模拟登录. - */ - protected function setUpMockAuthUser() - { - $user = config('wechat.mock_user'); + if (\is_callable([$app, 'setCache'])) { + $app->setCache($laravelApp['cache.store']); + } - if (is_array($user) && !empty($user['openid']) && config('wechat.enable_mock')) { - $user = new SocialiteUser([ - 'id' => array_get($user, 'openid'), - 'name' => array_get($user, 'nickname'), - 'nickname' => array_get($user, 'nickname'), - 'avatar' => array_get($user, 'headimgurl'), - 'email' => null, - 'original' => array_merge($user, ['privilege' => []]), - ]); + if (\is_callable([$app, 'setRequestFromSymfonyRequest'])) { + $app->setRequestFromSymfonyRequest($laravelApp['request']); + } - session(['wechat.oauth_user' => $user]); + return $app; + }); + } + $this->app->alias("easywechat.{$name}.default", 'easywechat.'.$name); + $this->app->alias('easywechat.'.$name, $class); } } - - /** - * Check open platform is configured. - * - * @return bool - */ - private function isEnableOpenPlatform() - { - return $this->config()->has('wechat.open_platform'); - } - - /** - * Get config value by key - * - * @return \Illuminate\Config\Repository - */ - private function config() - { - return $this->app['config']; - } } diff --git a/src/ServiceProviders/RouteServiceProvider.php b/src/ServiceProviders/RouteServiceProvider.php deleted file mode 100644 index c5e7204..0000000 --- a/src/ServiceProviders/RouteServiceProvider.php +++ /dev/null @@ -1,23 +0,0 @@ -group([ - 'namespace' => 'Overtrue\LaravelWechat\Controllers' - ], function (Router $router) { - $router->post(config('wechat.open_platform.serve_url'), 'EasyWeChatController@openPlatformServe'); - }); - } -} diff --git a/src/Traits/HandleOpenPlatformServerEvents.php b/src/Traits/HandleOpenPlatformServerEvents.php new file mode 100644 index 0000000..494c6d8 --- /dev/null +++ b/src/Traits/HandleOpenPlatformServerEvents.php @@ -0,0 +1,61 @@ +disableLaravelDebugbar(); + + $server = $application->getServer(); + + $server->handleAuthorized(function ($payload) { + event(new Authorized($payload->toArray())); + }); + + $server->handleUnauthorized(function ($payload) { + event(new Unauthorized($payload->toArray())); + }); + + $server->handleAuthorizeUpdated(function ($payload) { + event(new AuthorizeUpdated($payload->toArray())); + }); + + $server->handleVerifyTicketRefreshed(function ($payload) { + event(new VerifyTicketRefreshed($payload->toArray())); + }); + + if ($callback) { + $callback($server); + } + + return $server->serve(); + } + + protected function disableLaravelDebugbar(): void + { + $debugbar = 'Barryvdh\Debugbar\LaravelDebugbar'; + + if (class_exists($debugbar)) { + try { + resolve($debugbar)->disable(); + } catch (\Throwable) { + // + } + } + } +} diff --git a/src/config.php b/src/config.php deleted file mode 100755 index a1ef628..0000000 --- a/src/config.php +++ /dev/null @@ -1,89 +0,0 @@ - true, - - /* - * 使用 Laravel 的缓存系统 - */ - 'use_laravel_cache' => true, - - /* - * 账号基本信息,请从微信公众平台/开放平台获取 - */ - 'app_id' => env('WECHAT_APPID', 'your-app-id'), // AppID - 'secret' => env('WECHAT_SECRET', 'your-app-secret'), // AppSecret - 'token' => env('WECHAT_TOKEN', 'your-token'), // Token - 'aes_key' => env('WECHAT_AES_KEY', ''), // EncodingAESKey - - /** - * 开放平台第三方平台配置信息 - */ - //'open_platform' => [ - /** - * 事件推送URL - */ - //'serve_url' => env('WECHAT_OPEN_PLATFORM_SERVE_URL', 'serve'), - //], - - /* - * 日志配置 - * - * level: 日志级别,可选为: - * debug/info/notice/warning/error/critical/alert/emergency - * file:日志文件位置(绝对路径!!!),要求可写权限 - */ - 'log' => [ - 'level' => env('WECHAT_LOG_LEVEL', 'debug'), - 'file' => env('WECHAT_LOG_FILE', storage_path('logs/wechat.log')), - ], - - /* - * OAuth 配置 - * - * only_wechat_browser: 只在微信浏览器跳转 - * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login - * callback:OAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。) - */ - // 'oauth' => [ - // 'only_wechat_browser' => false, - // 'scopes' => array_map('trim', explode(',', env('WECHAT_OAUTH_SCOPES', 'snsapi_userinfo'))), - // 'callback' => env('WECHAT_OAUTH_CALLBACK', '/examples/oauth_callback.php'), - // ], - - /* - * 微信支付 - */ - // 'payment' => [ - // 'merchant_id' => env('WECHAT_PAYMENT_MERCHANT_ID', 'your-mch-id'), - // 'key' => env('WECHAT_PAYMENT_KEY', 'key-for-signature'), - // 'cert_path' => env('WECHAT_PAYMENT_CERT_PATH', 'path/to/your/cert.pem'), // XXX: 绝对路径!!!! - // 'key_path' => env('WECHAT_PAYMENT_KEY_PATH', 'path/to/your/key'), // XXX: 绝对路径!!!! - // // 'device_info' => env('WECHAT_PAYMENT_DEVICE_INFO', ''), - // // 'sub_app_id' => env('WECHAT_PAYMENT_SUB_APP_ID', ''), - // // 'sub_merchant_id' => env('WECHAT_PAYMENT_SUB_MERCHANT_ID', ''), - // // ... - // ], - - /* - * 开发模式下的免授权模拟授权用户资料 - * - * 当 enable_mock 为 true 则会启用模拟微信授权,用于开发时使用,开发完成请删除或者改为 false 即可 - */ - // 'enable_mock' => env('WECHAT_ENABLE_MOCK', true), - // 'mock_user' => [ - // "openid" =>"odh7zsgI75iT8FRh0fGlSojc9PWM", - // // 以下字段为 scope 为 snsapi_userinfo 时需要 - // "nickname" => "overtrue", - // "sex" =>"1", - // "province" =>"北京", - // "city" =>"北京", - // "country" =>"中国", - // "headimgurl" => "http://wx.qlogo.cn/mmopen/C2rEUskXQiblFYMUl9O0G05Q6pKibg7V1WpHX6CIQaic824apriabJw4r6EWxziaSt5BATrlbx1GVzwW2qjUCqtYpDvIJLjKgP1ug/0", - // ], -];