Commit f2b381e7 f2b381e7646a817596e7b6c078c42c8ced1e0964 by 杨俊

feat(master): 初始化

1 parent a4671c53
1 <?php
2
3 namespace App\Exceptions;
4
5 use Exception;
6
7 class TestCallbackException extends Exception
8 {
9 //
10 }
...@@ -2,13 +2,12 @@ ...@@ -2,13 +2,12 @@
2 2
3 namespace App\Helpers; 3 namespace App\Helpers;
4 4
5 use Arr;
6 use GuzzleHttp\Promise\PromiseInterface; 5 use GuzzleHttp\Promise\PromiseInterface;
7 use Illuminate\Http\Client\Response; 6 use Illuminate\Http\Client\Response;
8 use Illuminate\Support\Facades\App; 7 use Illuminate\Support\Arr;
9 use Illuminate\Support\Facades\Http; 8 use Illuminate\Support\Facades\Http;
10 use Illuminate\Support\Facades\Log; 9 use Illuminate\Support\Facades\Log;
11 use Str; 10 use Illuminate\Support\Str;
12 use Tencent\TLSSigAPIv2; 11 use Tencent\TLSSigAPIv2;
13 12
14 class IMHelper 13 class IMHelper
...@@ -76,7 +75,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response ...@@ -76,7 +75,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response
76 $result = Http::asJson()->post($host, $data); 75 $result = Http::asJson()->post($host, $data);
77 76
78 77
79 Log::channel('jpush')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [ 78 Log::channel('im')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [
80 'uri' => $uri, 'data' => $data, 'result' => $result->json() 79 'uri' => $uri, 'data' => $data, 'result' => $result->json()
81 ]); 80 ]);
82 81
...@@ -89,7 +88,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response ...@@ -89,7 +88,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response
89 */ 88 */
90 public static function userKeyToAccount(int|string $userId): string 89 public static function userKeyToAccount(int|string $userId): string
91 { 90 {
92 return (App::isProduction() ? '' : 'test') . $userId; 91 return config('services.im.prefix') . $userId;
93 } 92 }
94 93
95 /** 94 /**
...@@ -98,7 +97,7 @@ public static function userKeyToAccount(int|string $userId): string ...@@ -98,7 +97,7 @@ public static function userKeyToAccount(int|string $userId): string
98 */ 97 */
99 public static function accountToUserKey(string $account): string 98 public static function accountToUserKey(string $account): string
100 { 99 {
101 return Str::replace('test', '', $account); 100 return Str::replace(config('services.im.prefix'), '', $account);
102 } 101 }
103 102
104 /** 103 /**
...@@ -200,7 +199,7 @@ public static function rollbackChatMessage(string $form, string $to, string $msg ...@@ -200,7 +199,7 @@ public static function rollbackChatMessage(string $form, string $to, string $msg
200 */ 199 */
201 public static function checkAccount(array $account): PromiseInterface|Response 200 public static function checkAccount(array $account): PromiseInterface|Response
202 { 201 {
203 return self::send('/v4/im_open_login_svc/account_check ', [ 202 return self::send('v4/im_open_login_svc/account_check ', [
204 'CheckItem' => Arr::map($account, static fn($item) => ['UserID' => self::userKeyToAccount($item)]) 203 'CheckItem' => Arr::map($account, static fn($item) => ['UserID' => self::userKeyToAccount($item)])
205 ]); 204 ]);
206 } 205 }
...@@ -211,7 +210,7 @@ public static function checkAccount(array $account): PromiseInterface|Response ...@@ -211,7 +210,7 @@ public static function checkAccount(array $account): PromiseInterface|Response
211 */ 210 */
212 public static function importSingleAccount(string $userId, string|array $name, string $avatar = ''): PromiseInterface|Response 211 public static function importSingleAccount(string $userId, string|array $name, string $avatar = ''): PromiseInterface|Response
213 { 212 {
214 return self::send('/v4/im_open_login_svc/account_import', [ 213 return self::send('v4/im_open_login_svc/account_import', [
215 'UserID' => self::userKeyToAccount($userId), 214 'UserID' => self::userKeyToAccount($userId),
216 'Nick' => is_array($name) ? json_encode($name, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $name, 215 'Nick' => is_array($name) ? json_encode($name, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $name,
217 'FaceUrl' => $avatar 216 'FaceUrl' => $avatar
...@@ -225,4 +224,38 @@ public static function updateSingleAccountInfo(string $userId, array $profileIte ...@@ -225,4 +224,38 @@ public static function updateSingleAccountInfo(string $userId, array $profileIte
225 { 224 {
226 return self::send('v4/profile/portrait_set', ['From_Account' => self::userKeyToAccount($userId), 'ProfileItem' => $profileItem]); 225 return self::send('v4/profile/portrait_set', ['From_Account' => self::userKeyToAccount($userId), 'ProfileItem' => $profileItem]);
227 } 226 }
227
228
229 /**
230 * @throws \Exception
231 */
232 public static function createGroup(string $userId, array $data = []): PromiseInterface|Response
233 {
234 return self::send('v4/group_open_http_svc/create_group', [
235 'Type' => 'Public',
236 'GroupId' => config('services.im.prefix') . now()->format('YmdHisu'),
237 'Owner_Account' => self::userKeyToAccount($userId),
238 ...$data
239 ]);
240 }
241
242
243 /**
244 * @throws \Random\RandomException
245 * @throws \Exception
246 */
247 public static function sendGroupMessage(string $groupId, array $data): PromiseInterface|Response
248 {
249 return self::send('v4/group_open_http_svc/send_group_msg', ["GroupId" => $groupId, "Random" => random_int(0, 4294967295),] + $data);
250 }
251
252 /**
253 * @param string $groupId
254 * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
255 * @throws \Exception
256 */
257 public static function deleteGroup(string $groupId): PromiseInterface|Response
258 {
259 return self::send('v4/group_open_http_svc/destroy_group', ['GroupId' => $groupId]);
260 }
228 } 261 }
......
1 <?php
2
3 namespace App\Http\Container\ProviderSection\Controllers;
4
5 use App\Exceptions\TestCallbackException;
6 use App\Helpers\IMHelper;
7 use App\Jobs\ImMessageSaveJob;
8 use App\Jobs\WechatTemplateMessageJob;
9 use App\Models\ActivityGroup;
10 use App\Models\ActivityGroupUser;
11 use App\Models\SystemConfig;
12 use App\Models\User;
13 use Carbon\Carbon;
14 use GuzzleHttp\Promise\PromiseInterface;
15 use Hikoon\LaravelApi\Support\ApiController;
16 use Illuminate\Http\Client\Response;
17 use Illuminate\Http\JsonResponse;
18 use Illuminate\Http\Request;
19 use Illuminate\Support\Arr;
20 use Illuminate\Support\Facades\App;
21 use Illuminate\Support\Facades\Http;
22 use Illuminate\Support\Facades\Redis;
23 use Illuminate\Support\Str;
24
25 class IMCallbackController extends ApiController
26 {
27 /**
28 * @param string $key
29 * @return bool
30 */
31 protected function gate(string $key): bool
32 {
33 return Str::of($key)->match('/^' . config('services.im.prefix') . '\d+$/')->isNotEmpty();
34 }
35
36 /**
37 * @param \Illuminate\Http\Request $request
38 * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response|\Illuminate\Http\JsonResponse
39 * @throws \Laravel\Octane\Exceptions\DdException
40 */
41 public function __invoke(Request $request): PromiseInterface|JsonResponse|Response
42 {
43 $method = Str::of($request->input('CallbackCommand'))->replace('.', '')->lcfirst()->toString();
44
45 if (method_exists($this, $method)) {
46 try {
47 return $this->$method($request->except(['CallbackCommand']));
48 } catch (TestCallbackException) {
49 return App::isProduction() ?
50 Http::asJson()->timeout(2)->post('https://hi-sing-service-dev.hikoon.com/provider/imCallback', $request->all()) :
51 new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
52 }
53 }
54
55 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
56 }
57
58 /**
59 * @param array $data
60 * @return \Illuminate\Http\JsonResponse
61 * @throws \Throwable
62 */
63 protected function stateStateChange(array $data): JsonResponse
64 {
65 $platform = Arr::get($data, 'OptPlatform');
66 $userId = Arr::get($data, 'Info.To_Account');
67 $action = Arr::get($data, 'Info.Action');
68
69 throw_unless($this->gate($userId), TestCallbackException::class);
70
71
72 if ($action === 'Login') {
73 User::query()->whereKey(IMHelper::accountToUserKey($userId))->update(['im_client' => $platform, 'im_status' => 1]);
74 Redis::client()->hDel('imOfflineMsg', IMHelper::accountToUserKey($userId));
75 } else {
76 User::query()->whereKey(IMHelper::accountToUserKey($userId))->where('im_client', $platform)->update(['im_status' => 0]);
77 }
78
79 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
80 }
81
82 /**
83 * @throws \Throwable
84 */
85 protected function c2CCallbackAfterSendMsg(array $data): JsonResponse
86 {
87 $toId = Arr::get($data, 'To_Account');
88 $formId = Arr::get($data, 'From_Account');
89 $online = Arr::get($data, 'OnlineOnlyFlag', 0);
90 $time = Arr::get($data, 'MsgTime', Carbon::now()->getTimestamp());
91
92 throw_unless($this->gate($formId), TestCallbackException::class);
93
94 $hashKey = 'imOfflineMsg';
95 $sendTime = Carbon::createFromTimestamp($time)->toDateTimeString();
96 $toId = IMHelper::accountToUserKey($toId);
97 $formId = IMHelper::accountToUserKey($formId);
98
99
100 ImMessageSaveJob::dispatch($data);
101
102 $openId = User::query()
103 ->where('id', $toId)
104 ->where('audit_status', 1)
105 ->where('status', 1)
106 ->where('im_client', 'Web')
107 ->where('im_status', 0)
108 ->value('official_id');
109
110 if ($openId && (int)$online === 0 && !Redis::client()->hExists($hashKey, $formId)) {
111 Redis::client()->hSet($hashKey, $formId, 0);
112 $customerId = SystemConfig::query()->where('identifier', 'customer_service')->value('remark');
113 $formUser = User::with('artTags:id,name')->find($formId, ['id', 'nick_name']);
114 $formName = $formUser->getAttribute('nick_name');
115 $formTag = $formUser->getRelation('artTags') ? $formUser->getRelation('artTags')->implode('name', ',') : '无';
116
117 $data = $customerId === $formId ?
118 ['first' => '您收到平台客服发来的消息', 'keyword1' => $formName, 'keyword2' => '平台客服', 'keyword3' => '平台官方', 'keyword4' => $sendTime, 'remark' => '点击查看'] :
119 ['first' => '您收到新的私聊消息', 'keyword1' => $formName, 'keyword2' => $formTag, 'keyword3' => '私聊消息', 'keyword4' => $sendTime, 'remark' => '点击查看'];
120
121
122 WechatTemplateMessageJob::dispatch($openId, 'HTRAwaRoJksSGCiSeVCyvpF9vXdeQhC6R4PbSYnO8NM', $data);
123 }
124
125 Redis::client()->hIncrBy($hashKey, $formId, 1);
126
127 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
128 }
129
130 /**
131 * @param array $data
132 * @return \Illuminate\Http\JsonResponse
133 * @throws \Throwable
134 */
135 protected function groupCallbackAfterCreateGroup(array $data): JsonResponse
136 {
137 $groupId = Arr::get($data, 'GroupId');
138
139 throw_unless($this->gate($groupId), TestCallbackException::class);
140
141 $ownerId = IMHelper::accountToUserKey(Arr::get($data, 'Owner_Account', 0));
142 $userDefinedDataList = Arr::pluck($data['UserDefinedDataList'], 'Value', 'Key');
143
144 $group = ActivityGroup::query()->firstOrCreate(['im_group_id' => $groupId], ['user_id' => $ownerId, 'activity_id' => Arr::get($userDefinedDataList, 'ActivityKey', 0), 'name' => $data['Name']]);
145 $memberList = Arr::map($data['MemberList'], static fn($item) => ['user_id' => IMHelper::accountToUserKey($item['Member_Account']), 'group_id' => $group->getKey()]);
146
147 ActivityGroupUser::query()->upsert($memberList, ['user_id', 'guard_id']);
148
149 IMHelper::sendGroupMessage($groupId, [
150 "MsgBody" => [
151 IMHelper::createCustomMessage([
152 "Data" => [
153 "businessID" => "group_inviter_user",
154 'inviter' => User::query()->whereKey($ownerId)->value('nick_name'),
155 'memberList' => User::query()->whereIn('id', array_column($memberList, 'user_id'))->pluck('nick_name')->toArray(),
156 "Desc" => ''
157 ]
158 ])
159 ]
160 ]);
161
162 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
163 }
164
165 /**
166 * @param array $data
167 * @return \Illuminate\Http\JsonResponse
168 * @throws \Throwable
169 */
170 protected function groupCallbackAfterGroupInfoChanged(array $data): JsonResponse
171 {
172 $groupId = Arr::get($data, 'GroupId');
173
174 throw_unless($this->gate($groupId), TestCallbackException::class);
175
176 if (isset($data['Name'])) {
177 ActivityGroup::query()->where('im_group_id', $groupId)->update(['name' => $data['Name']]);
178 }
179
180 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
181 }
182
183 /**
184 * @param array $data
185 * @return \Illuminate\Http\JsonResponse
186 * @throws \Throwable
187 */
188 protected function groupCallbackAfterNewMemberJoin(array $data): JsonResponse
189 {
190 $groupId = Arr::get($data, 'GroupId');
191
192 throw_unless($this->gate($groupId), TestCallbackException::class);
193
194 if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
195 $inviterUser = IMHelper::accountToUserKey(Arr::get($data, 'Operator_Account', 0));
196 $memberList = Arr::map($data['NewMemberList'], static fn($item) => ['user_id' => IMHelper::accountToUserKey($item['Member_Account']), 'group_id' => $group]);
197 ActivityGroupUser::query()->upsert($memberList, ['user_id', 'group_id']);
198
199 IMHelper::sendGroupMessage($groupId, [
200 "MsgBody" => [
201 IMHelper::createCustomMessage([
202 "Data" => [
203 "businessID" => "group_inviter_user",
204 'inviter' => User::query()->whereKey($inviterUser)->value('nick_name'),
205 'memberList' => User::query()->whereIn('id', array_column($memberList, 'user_id'))->pluck('nick_name')->toArray(),
206 "Desc" => ''
207 ]
208 ])
209 ]
210 ]);
211 }
212 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
213 }
214
215 /**
216 * @param array $data
217 * @return \Illuminate\Http\JsonResponse
218 * @throws \Throwable
219 */
220 protected function groupCallbackAfterMemberExit(array $data): JsonResponse
221 {
222 $groupId = Arr::get($data, 'GroupId');
223
224 throw_unless($this->gate($groupId), TestCallbackException::class);
225
226 if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
227 $memberList = Arr::map($data['ExitMemberList'], static fn($item) => IMHelper::accountToUserKey($item['Member_Account']));
228 ActivityGroupUser::query()->where('group_id', $group)->whereIn('user_id', $memberList)->delete();
229 }
230
231 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
232 }
233
234 /**
235 * @param array $data
236 * @return \Illuminate\Http\JsonResponse
237 * @throws \Throwable
238 */
239 protected function groupCallbackAfterGroupDestroyed(array $data): JsonResponse
240 {
241 $groupId = Arr::get($data, 'GroupId');
242
243 throw_unless($this->gate($groupId), TestCallbackException::class);
244
245 if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
246 ActivityGroup::query()->whereKey($group)->delete();
247 ActivityGroupUser::query()->where('group_id', $group)->delete();
248 }
249
250 return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
251 }
252 }
1 <?php
2
3 namespace App\Jobs;
4
5 use App\Helpers\IMHelper;
6 use Arr;
7 use Carbon\Carbon;
8 use DB;
9 use Illuminate\Bus\Queueable;
10 use Illuminate\Contracts\Queue\ShouldQueue;
11 use Illuminate\Foundation\Bus\Dispatchable;
12 use Illuminate\Queue\InteractsWithQueue;
13 use Illuminate\Queue\SerializesModels;
14
15 class ImMessageSaveJob implements ShouldQueue
16 {
17 use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
18
19 public array $data;
20
21 /**
22 * Create a new job instance.
23 *
24 * @return void
25 */
26 public function __construct(array $data)
27 {
28 $this->data = $data;
29 }
30
31 /**
32 * Execute the job.
33 *
34 * @return void
35 * @throws \JsonException
36 */
37 public function handle(): void
38 {
39 $data = [];
40 $fromIm = $this->data['From_Account'];
41 $from = IMHelper::accountToUserKey($fromIm);
42 $toIm = $this->data['To_Account'];
43 $to = IMHelper::accountToUserKey($this->data['To_Account']);
44 $time = Carbon::createFromTimestamp($this->data['MsgTime'])->toDateTimeString();
45
46 foreach ($this->data['MsgBody'] as $value) {
47 if ($value['MsgType'] === 'TIMCustomElem') {
48 $value['MsgContent']['Data'] = json_decode($value['MsgContent']['Data'], true, 512, JSON_THROW_ON_ERROR);
49 }
50
51 $data[] = array_merge([
52 'from' => $from, 'from_im' => $fromIm, 'to' => $to, 'to_im' => $toIm, 'created_at' => $time, 'updated_at' => $time,
53 'msg_key' => Arr::get($this->data, 'MsgKey', ''),
54 'content' => json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
55 ], $this->formatMsg($value));
56 }
57
58 DB::table('im_messags')->insert($data);
59 }
60
61 /**
62 * @param array{MsgType:string,MsgContent:array} $msgItem
63 * @return array{type:int,value:string}
64 */
65 public function formatMsg(array $msgItem): array
66 {
67 return match ($msgItem['MsgType']) {
68 'TIMTextElem' => ['type' => 1, 'value' => Arr::get($msgItem, 'MsgContent.Text', '')],
69 'TIMImageElem' => ['type' => 2, 'value' => Arr::get($msgItem, 'MsgContent.ImageInfoArray.0.URL', '')],
70 'TIMVideoFileElem' => ['type' => 3, 'value' => Arr::get($msgItem, 'MsgContent.VideoUrl', '')],
71 'TIMFileElem' => ['type' => 4, 'value' => Arr::get($msgItem, 'MsgContent.Url', '')],
72 'TIMCustomElem' => $this->formatMsgCustomData($msgItem['MsgContent']['Data']),
73 'TIMFaceElem' => ['type' => 17, 'value' => Arr::get($msgItem, 'MsgContent')],
74 default => ['type' => 18, 'value' => '其他消息'],
75 };
76 }
77
78 /**
79 * @param array $data
80 * @return array
81 */
82 public function formatMsgCustomData(array $data): array
83 {
84 switch ($data['businessID']) {
85 case 'invite_team':
86 return ['type' => 5, 'value' => '邀请入队'];
87 case 'apply_team':
88 return ['type' => 6, 'value' => '申请入队'];
89 case 'custom_song':
90 return ['type' => 9, 'value' => Arr::get($data, 'id', 0)];
91 case 'exit_team':
92 return ['type' => 10, 'value' => '成员退出团队申请'];
93 case 'invite_brand':
94 return ['type' => 19, 'value' => '邀请成员加入厂牌'];
95 case 'exit_brand':
96 return ['type' => 23, 'value' => '成员申请退出厂牌'];
97 case 'system_notice':
98 return ['type' => 24, 'value' => '系统消息'];
99 case 'invite_tip':
100 if (in_array($data['tipStatus'], [1, 6], false)) {
101 return ['type' => 7, 'value' => '同意邀请或申请'];
102 }
103 return match ($data['tipStatus']) {
104 '3' => ['type' => 11, 'value' => '队长同意成员退出'],
105 '8' => ['type' => 12, 'value' => '到期自动退出团队'],
106 '5' => ['type' => 13, 'value' => '队长将成员从团队移出'],
107 '2' => ['type' => 14, 'value' => '拒绝团队邀请'],
108 '4' => ['type' => 15, 'value' => '取消退出团队'],
109 '7' => ['type' => 16, 'value' => '拒绝用户申请'],
110 '12' => ['type' => 20, 'value' => '同意厂牌邀请'],
111 '13' => ['type' => 21, 'value' => '拒绝厂牌邀请'],
112 '11' => ['type' => 22, 'value' => '将成员移出厂牌'],
113 '9' => ['type' => 24, 'value' => '成员取消退出厂牌'],
114 '10' => ['type' => 25, 'value' => '厂牌管理员同意退出'],
115 default => ['type' => 18, 'value' => '其他消息']
116 };
117 default:
118 return ['type' => 18, 'value' => '其他消息'];
119 }
120 }
121 }