diff --git a/app/Admin/Controllers/MessageController.php b/app/Admin/Controllers/MessageController.php index a8ba8389..c77dd23c 100644 --- a/app/Admin/Controllers/MessageController.php +++ b/app/Admin/Controllers/MessageController.php @@ -3,6 +3,8 @@ namespace App\Admin\Controllers; use App\Admin\Repositories\Message; +use App\Models\Message as MessageModel; +use App\Services\Push\MallUnipushService; use Dcat\Admin\Admin; use Dcat\Admin\Form; use Dcat\Admin\Grid; @@ -29,6 +31,7 @@ class MessageController extends AdminController '2'=>__('admin_message.ad.jump_type.radio.2'), ])->label(); $grid->column('jump_link'); + $grid->column('is_push')->bool(); $grid->column('created_at')->sortable(); $grid->model()->orderBy('created_at', 'desc'); @@ -94,6 +97,19 @@ class MessageController extends AdminController '2'=>__('admin_message.ad.jump_type.radio.2'), ])->default(0); $form->text('jump_link'); + if ($form->isEditing()) { + $form->switch('is_push')->disable(); + } else { + $form->switch('is_push')->default(0); + } + $form->saved(function (Form $form, $result) { + // 判断是否是新增操作 + if ($form->isCreating() && $form->is_push) { + $pushService = new MallUnipushService(); + $pushService->pushAllMessage(MessageModel::findOrFail($result)); + return; + } + }); $form->display('created_at'); $form->display('updated_at'); diff --git a/app/Endpoint/Api/Http/Controllers/PushController.php b/app/Endpoint/Api/Http/Controllers/PushController.php new file mode 100644 index 00000000..4c1e5d0d --- /dev/null +++ b/app/Endpoint/Api/Http/Controllers/PushController.php @@ -0,0 +1,47 @@ +user()) { + throw new BizException('绑定失败,请稍后再试'); + } + + $input = $request->validate([ + 'cid' => ['bail', 'required', 'string', 'max:64'], + ]); + try { + DB::beginTransaction(); + + //查询目前有没有人已绑定这个cid, 有就解绑 + UserCid::where('u_cid', $input['cid'])->where('user_id', '<>', $request->user()->id)->update([ + 'u_cid' => null, + ]); + $request->user()->cid()->updateOrCreate([ + 'u_cid'=>$input['cid'], + ]); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException('绑定失败,请稍后再试'); + } + return response()->noContent(); + } +} diff --git a/app/Endpoint/Api/routes.php b/app/Endpoint/Api/routes.php index 384c9608..f2739469 100644 --- a/app/Endpoint/Api/routes.php +++ b/app/Endpoint/Api/routes.php @@ -22,6 +22,7 @@ use App\Endpoint\Api\Http\Controllers\Product\ProductCategoryController; use App\Endpoint\Api\Http\Controllers\Product\ProductFavoriteController; use App\Endpoint\Api\Http\Controllers\Product\ProductSkuController; use App\Endpoint\Api\Http\Controllers\Product\ProductViewLogController; +use App\Endpoint\Api\Http\Controllers\PushController; use App\Endpoint\Api\Http\Controllers\ShippingAddressController; use App\Endpoint\Api\Http\Controllers\ShoppingCartItemController; use App\Endpoint\Api\Http\Controllers\SmsCodeController; @@ -66,6 +67,8 @@ Route::group([ Route::get('article-config', [ArticleController::class, 'config']); //获取最新app版本信息 Route::get('app-version', [AppVersionController::class, 'index']); + //绑定用户端cid + Route::post('push-bind-uni', [PushController::class, 'bindUniCid']); Route::middleware(['auth:api'])->group(function () { // 我的信息 diff --git a/app/Models/Message.php b/app/Models/Message.php index 43481312..5117a828 100644 --- a/app/Models/Message.php +++ b/app/Models/Message.php @@ -19,6 +19,7 @@ class Message extends Model protected $casts = [ 'ext'=>JsonArray::class, + 'is_push'=>'bool', ]; protected $fillable = [ @@ -30,6 +31,11 @@ class Message extends Model return $this->hasMany(MessageReadLog::class); } + public function user() + { + return $this->belongsTo(User::class); + } + public static function userMessages(User $user) { return self::where(function ($q) use ($user) { diff --git a/app/Models/User.php b/app/Models/User.php index 8ba6bd4d..bdba36b7 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -118,6 +118,14 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac return $this->hasMany(ProductSkuFavorite::class); } + /** + * 属于此用户的推送cid + */ + public function cid() + { + return $this->hasOne(UserCid::class, 'user_id'); + } + /** * 确认此用户是否是 VIP * diff --git a/app/Models/UserCid.php b/app/Models/UserCid.php new file mode 100644 index 00000000..a16fe616 --- /dev/null +++ b/app/Models/UserCid.php @@ -0,0 +1,16 @@ +appId = config('settings.mall_push_app_id', ''); + $this->appKey = config('settings.mall_push_app_key', ''); + $this->appSecret = config('settings.mall_push_app_secret', ''); + $this->masterSecret = config('settings.mall_push_master_secret', ''); + parent::__construct($this->appId, $this->appKey, $this->appSecret, $this->masterSecret); + } + + /** + * 推送公告消息 + * + * @param [type] $message + * @return void + */ + public function pushAllMessage(Message $message) + { + $this->pushAll($message->title, $message->content, [ + 'jump_type' => $message->jump_type == 0 ? 1 : $message->jump_type, + 'jump_link' => $message->type == 1 || empty($message->jump_link) ? '/pages/news/index' : $message->jump_link, + ]); + } + + /** + * 推送单条消息 + * + * @param Message $message + * @return void + */ + public function pushMessage(Message $message) + { + //如果不是指定消息,直接退出; + if (is_null($message->user)) { + return; + } + //如果拿不到u_cid直接退出 + if (!$message->user->cid->u_cid) { + return; + } + $this->pushCid($message->user->cid->u_cid, $message->title, $message->content, [ + 'jump_type' => $message->jump_type == 0 ? 1 : $message->jump_type, + 'jump_link' => $message->type == 1 || empty($message->jump_link) ? '/pages/news/index' : $message->jump_link, + ]); + } +} diff --git a/app/Services/Push/MerchantUnipushService.php b/app/Services/Push/MerchantUnipushService.php new file mode 100644 index 00000000..34a22799 --- /dev/null +++ b/app/Services/Push/MerchantUnipushService.php @@ -0,0 +1,10 @@ +appId = $appId; + $this->appKey = $appKey; + $this->appSecret = $appSecret; + $this->masterSecret = $masterSecret; + + $this->cacheKey = 'uni-push-'.$this->appId; + } + + /** + * 获取当前实例token + * + * @return void + */ + public function getToken() + { + if (Cache::has($this->cacheKey)) { + return Cache::get($this->cacheKey); + } else { + return $this->getTokenFromServer(); + } + } + + protected function getUri($uri) + { + return $this->uri.$this->appId.'/'.$uri; + } + + /** + * 获取unix毫秒时间戳 + * + * @return void + */ + protected function msectime() + { + list($msec, $sec) = explode(' ', microtime()); + $msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000); + return $msectime; + } + + /** + * 创建签名 + * + * @return void + */ + protected function makeSign($param) + { + return hash('sha256', $param['appkey'].$param['timestamp'].$this->masterSecret); + } + + /** + * 重新获取token + * + * @return void + */ + protected function getTokenFromServer() + { + $token = ''; + $param = [ + 'appkey'=>$this->appKey, + 'timestamp'=>$this->msectime(), + ]; + $param['sign'] = $this->makeSign($param); + $response = Http::withHeaders([ + 'Accept' => 'application/json', + ])->post($this->getUri('auth'), $param); + + if ($response->successful()) { + $data = $response->json(); + $token = $data['data']['token']; + Cache::put($this->cacheKey, $data['data']['token'], $data['data']['expire_time']); + } + return $token; + } + + /** + * 封装公共请求 + * + * @return void + */ + protected function post($uri, $params) + { + $token = $this->getToken(); + // dd($params); + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'token' => $token, + ])->post($this->getUri($uri), $params); + + $resData = []; + if ($response->successful()) { + $resData = $response->json(); + } + return $resData; + } + + protected function get($uri, $params = null) + { + $token = $this->getToken(); + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'token' => $token, + ])->get($this->getUri($uri), $params); + + $resData = []; + if ($response->successful()) { + $resData = $response->json(); + } + return $resData; + } + + protected function createPushData($title, $body, $params = []) + { + $payload = urlencode(json_encode($params)); + return [ + 'request_id' => OrderHelper::serialNumber(), + 'settings'=>[ + 'ttl'=> 3600000, + 'strategy'=> [ + 'default'=>1, + 'ios'=>4, + ], + ], + 'push_message'=>[ + 'transmission'=> json_encode([ + 'title' => $title, + 'content'=> $body, + 'payload'=> $payload, + ]), + // 'notification'=>[ + // 'title' => $title, + // 'body' => $body, + // 'click_type'=> 'payload', //默认打开首页 + // 'payload'=> $payload, + // ], + ], + 'push_channel'=>[ + 'android'=>[ + 'ups'=> [ + 'transmission'=> json_encode([ + 'title' => $title, + 'content'=> $body, + 'payload'=> $payload, + ]), + // 'notification'=>[ + // 'title' => $title, + // 'body' => $body, + // 'click_type'=> 'payload', //默认打开首页 + // 'payload'=> $payload, + // ], + ], + ], + 'ios'=>[ + 'type'=>'notify', + 'payload' => $payload, + 'aps'=>[ + 'alert'=>[ + 'title'=>$title, + 'body'=>$body, + ], + 'content-available'=> 0, + ], + ], + ], + ]; + } + + /** + * 绑定cid和user_id + * + * @return void + */ + public function bindCid($userId, $cid) + { + $this->post('user/alias', [ + 'data_list'=>[ + 'cid' => $cid, + 'alias'=>$userId, + ], + ]); + } + + /** + * 解绑所有与该别名绑定的cid + */ + public function unbindCid($userId) + { + $res = $this->get('user/alias/'.$userId); + + if (Arr::get($res, 'code', 1) === 0) { + return true; + } + return false; + } + + /** + * 根据cid单推 + * + * @return void + */ + public function pushCid(string $cid, string $title, string $body, array $params = []) + { + $this->post('push/single/cid', array_merge([ + 'audience'=>[ + 'cid'=>[ + $cid, + ], + ], + ], $this->createPushData($title, $body, $params))); + } + + /** + * 给所有人推送 + * + * @param string $title + * @param string $body + * @param array $params + * @return void + */ + public function pushAll(string $title, string $body, array $params = []) + { + $this->post('push/all', array_merge([ + 'audience'=>'all', + ], $this->createPushData($title, $body, $params))); + } +} diff --git a/config/settings.php b/config/settings.php index 4f71d104..63f32e2d 100644 --- a/config/settings.php +++ b/config/settings.php @@ -32,4 +32,18 @@ return [ 'kuaidi100_secret'=> '1bd287d1981749f2a30ea74cac0ab99c', 'kuaidi100_userid'=> 'ec0b6ec7729d4f22824cfd3c519dd45b', + //推送 + 'mall_push_app_id'=>'iikmCoESID8bC1LhOPG1r8', + 'mall_push_app_key'=>'JX33P0wP8bAQprI953hpN6', + 'mall_push_app_secret'=>'a3u3B6lXjq6fPTBlOGiOc9', + 'mall_push_master_secret'=>'MAxmqomwo597xJeDuMCvx1', + + 'merchant_push_app_id'=>'', + 'merchant_push_app_key'=>'', + 'merchant_push_app_secret'=>'', + 'merchant_push_master_secret'=>'', + + //app配置 + 'user_center_is_open'=>true, + ]; diff --git a/database/migrations/2021_12_21_160332_add_is_push_to_messages_table.php b/database/migrations/2021_12_21_160332_add_is_push_to_messages_table.php new file mode 100644 index 00000000..91abc888 --- /dev/null +++ b/database/migrations/2021_12_21_160332_add_is_push_to_messages_table.php @@ -0,0 +1,34 @@ +unsignedTinyInteger('is_push')->default(0)->comment('是否推送'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('messages', function (Blueprint $table) { + // + $table->dropColumn('is_push'); + }); + } +} diff --git a/database/migrations/2021_12_21_160716_create_user_cids_table.php b/database/migrations/2021_12_21_160716_create_user_cids_table.php new file mode 100644 index 00000000..46430430 --- /dev/null +++ b/database/migrations/2021_12_21_160716_create_user_cids_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('用户ID'); + $table->string('u_cid')->nullable()->comment('用户推送cid'); + $table->string('m_cid')->nullable()->comment('商户推送cid'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_cids'); + } +} diff --git a/resources/lang/zh_CN/message.php b/resources/lang/zh_CN/message.php index 23d93d82..30be57df 100644 --- a/resources/lang/zh_CN/message.php +++ b/resources/lang/zh_CN/message.php @@ -12,6 +12,7 @@ return [ 'ext' => '扩展内容', 'jump_type' => '跳转类型', 'jump_link' => '跳转地址', + 'is_push'=>'推送', ], 'options' => [ ],