Commit f2b381e7 f2b381e7646a817596e7b6c078c42c8ced1e0964 by 杨俊

feat(master): 初始化

1 parent a4671c53
<?php
namespace App\Exceptions;
use Exception;
class TestCallbackException extends Exception
{
//
}
......@@ -2,13 +2,12 @@
namespace App\Helpers;
use Arr;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Str;
use Illuminate\Support\Str;
use Tencent\TLSSigAPIv2;
class IMHelper
......@@ -76,7 +75,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response
$result = Http::asJson()->post($host, $data);
Log::channel('jpush')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [
Log::channel('im')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [
'uri' => $uri, 'data' => $data, 'result' => $result->json()
]);
......@@ -89,7 +88,7 @@ public static function send(string $uri, array $data): PromiseInterface|Response
*/
public static function userKeyToAccount(int|string $userId): string
{
return (App::isProduction() ? '' : 'test') . $userId;
return config('services.im.prefix') . $userId;
}
/**
......@@ -98,7 +97,7 @@ public static function userKeyToAccount(int|string $userId): string
*/
public static function accountToUserKey(string $account): string
{
return Str::replace('test', '', $account);
return Str::replace(config('services.im.prefix'), '', $account);
}
/**
......@@ -200,7 +199,7 @@ public static function rollbackChatMessage(string $form, string $to, string $msg
*/
public static function checkAccount(array $account): PromiseInterface|Response
{
return self::send('/v4/im_open_login_svc/account_check ', [
return self::send('v4/im_open_login_svc/account_check ', [
'CheckItem' => Arr::map($account, static fn($item) => ['UserID' => self::userKeyToAccount($item)])
]);
}
......@@ -211,7 +210,7 @@ public static function checkAccount(array $account): PromiseInterface|Response
*/
public static function importSingleAccount(string $userId, string|array $name, string $avatar = ''): PromiseInterface|Response
{
return self::send('/v4/im_open_login_svc/account_import', [
return self::send('v4/im_open_login_svc/account_import', [
'UserID' => self::userKeyToAccount($userId),
'Nick' => is_array($name) ? json_encode($name, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $name,
'FaceUrl' => $avatar
......@@ -225,4 +224,38 @@ public static function updateSingleAccountInfo(string $userId, array $profileIte
{
return self::send('v4/profile/portrait_set', ['From_Account' => self::userKeyToAccount($userId), 'ProfileItem' => $profileItem]);
}
/**
* @throws \Exception
*/
public static function createGroup(string $userId, array $data = []): PromiseInterface|Response
{
return self::send('v4/group_open_http_svc/create_group', [
'Type' => 'Public',
'GroupId' => config('services.im.prefix') . now()->format('YmdHisu'),
'Owner_Account' => self::userKeyToAccount($userId),
...$data
]);
}
/**
* @throws \Random\RandomException
* @throws \Exception
*/
public static function sendGroupMessage(string $groupId, array $data): PromiseInterface|Response
{
return self::send('v4/group_open_http_svc/send_group_msg', ["GroupId" => $groupId, "Random" => random_int(0, 4294967295),] + $data);
}
/**
* @param string $groupId
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
* @throws \Exception
*/
public static function deleteGroup(string $groupId): PromiseInterface|Response
{
return self::send('v4/group_open_http_svc/destroy_group', ['GroupId' => $groupId]);
}
}
......
<?php
namespace App\Http\Container\ProviderSection\Controllers;
use App\Exceptions\TestCallbackException;
use App\Helpers\IMHelper;
use App\Jobs\ImMessageSaveJob;
use App\Jobs\WechatTemplateMessageJob;
use App\Models\ActivityGroup;
use App\Models\ActivityGroupUser;
use App\Models\SystemConfig;
use App\Models\User;
use Carbon\Carbon;
use GuzzleHttp\Promise\PromiseInterface;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Client\Response;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
class IMCallbackController extends ApiController
{
/**
* @param string $key
* @return bool
*/
protected function gate(string $key): bool
{
return Str::of($key)->match('/^' . config('services.im.prefix') . '\d+$/')->isNotEmpty();
}
/**
* @param \Illuminate\Http\Request $request
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response|\Illuminate\Http\JsonResponse
* @throws \Laravel\Octane\Exceptions\DdException
*/
public function __invoke(Request $request): PromiseInterface|JsonResponse|Response
{
$method = Str::of($request->input('CallbackCommand'))->replace('.', '')->lcfirst()->toString();
if (method_exists($this, $method)) {
try {
return $this->$method($request->except(['CallbackCommand']));
} catch (TestCallbackException) {
return App::isProduction() ?
Http::asJson()->timeout(2)->post('https://hi-sing-service-dev.hikoon.com/provider/imCallback', $request->all()) :
new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function stateStateChange(array $data): JsonResponse
{
$platform = Arr::get($data, 'OptPlatform');
$userId = Arr::get($data, 'Info.To_Account');
$action = Arr::get($data, 'Info.Action');
throw_unless($this->gate($userId), TestCallbackException::class);
if ($action === 'Login') {
User::query()->whereKey(IMHelper::accountToUserKey($userId))->update(['im_client' => $platform, 'im_status' => 1]);
Redis::client()->hDel('imOfflineMsg', IMHelper::accountToUserKey($userId));
} else {
User::query()->whereKey(IMHelper::accountToUserKey($userId))->where('im_client', $platform)->update(['im_status' => 0]);
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @throws \Throwable
*/
protected function c2CCallbackAfterSendMsg(array $data): JsonResponse
{
$toId = Arr::get($data, 'To_Account');
$formId = Arr::get($data, 'From_Account');
$online = Arr::get($data, 'OnlineOnlyFlag', 0);
$time = Arr::get($data, 'MsgTime', Carbon::now()->getTimestamp());
throw_unless($this->gate($formId), TestCallbackException::class);
$hashKey = 'imOfflineMsg';
$sendTime = Carbon::createFromTimestamp($time)->toDateTimeString();
$toId = IMHelper::accountToUserKey($toId);
$formId = IMHelper::accountToUserKey($formId);
ImMessageSaveJob::dispatch($data);
$openId = User::query()
->where('id', $toId)
->where('audit_status', 1)
->where('status', 1)
->where('im_client', 'Web')
->where('im_status', 0)
->value('official_id');
if ($openId && (int)$online === 0 && !Redis::client()->hExists($hashKey, $formId)) {
Redis::client()->hSet($hashKey, $formId, 0);
$customerId = SystemConfig::query()->where('identifier', 'customer_service')->value('remark');
$formUser = User::with('artTags:id,name')->find($formId, ['id', 'nick_name']);
$formName = $formUser->getAttribute('nick_name');
$formTag = $formUser->getRelation('artTags') ? $formUser->getRelation('artTags')->implode('name', ',') : '无';
$data = $customerId === $formId ?
['first' => '您收到平台客服发来的消息', 'keyword1' => $formName, 'keyword2' => '平台客服', 'keyword3' => '平台官方', 'keyword4' => $sendTime, 'remark' => '点击查看'] :
['first' => '您收到新的私聊消息', 'keyword1' => $formName, 'keyword2' => $formTag, 'keyword3' => '私聊消息', 'keyword4' => $sendTime, 'remark' => '点击查看'];
WechatTemplateMessageJob::dispatch($openId, 'HTRAwaRoJksSGCiSeVCyvpF9vXdeQhC6R4PbSYnO8NM', $data);
}
Redis::client()->hIncrBy($hashKey, $formId, 1);
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function groupCallbackAfterCreateGroup(array $data): JsonResponse
{
$groupId = Arr::get($data, 'GroupId');
throw_unless($this->gate($groupId), TestCallbackException::class);
$ownerId = IMHelper::accountToUserKey(Arr::get($data, 'Owner_Account', 0));
$userDefinedDataList = Arr::pluck($data['UserDefinedDataList'], 'Value', 'Key');
$group = ActivityGroup::query()->firstOrCreate(['im_group_id' => $groupId], ['user_id' => $ownerId, 'activity_id' => Arr::get($userDefinedDataList, 'ActivityKey', 0), 'name' => $data['Name']]);
$memberList = Arr::map($data['MemberList'], static fn($item) => ['user_id' => IMHelper::accountToUserKey($item['Member_Account']), 'group_id' => $group->getKey()]);
ActivityGroupUser::query()->upsert($memberList, ['user_id', 'guard_id']);
IMHelper::sendGroupMessage($groupId, [
"MsgBody" => [
IMHelper::createCustomMessage([
"Data" => [
"businessID" => "group_inviter_user",
'inviter' => User::query()->whereKey($ownerId)->value('nick_name'),
'memberList' => User::query()->whereIn('id', array_column($memberList, 'user_id'))->pluck('nick_name')->toArray(),
"Desc" => ''
]
])
]
]);
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function groupCallbackAfterGroupInfoChanged(array $data): JsonResponse
{
$groupId = Arr::get($data, 'GroupId');
throw_unless($this->gate($groupId), TestCallbackException::class);
if (isset($data['Name'])) {
ActivityGroup::query()->where('im_group_id', $groupId)->update(['name' => $data['Name']]);
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function groupCallbackAfterNewMemberJoin(array $data): JsonResponse
{
$groupId = Arr::get($data, 'GroupId');
throw_unless($this->gate($groupId), TestCallbackException::class);
if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
$inviterUser = IMHelper::accountToUserKey(Arr::get($data, 'Operator_Account', 0));
$memberList = Arr::map($data['NewMemberList'], static fn($item) => ['user_id' => IMHelper::accountToUserKey($item['Member_Account']), 'group_id' => $group]);
ActivityGroupUser::query()->upsert($memberList, ['user_id', 'group_id']);
IMHelper::sendGroupMessage($groupId, [
"MsgBody" => [
IMHelper::createCustomMessage([
"Data" => [
"businessID" => "group_inviter_user",
'inviter' => User::query()->whereKey($inviterUser)->value('nick_name'),
'memberList' => User::query()->whereIn('id', array_column($memberList, 'user_id'))->pluck('nick_name')->toArray(),
"Desc" => ''
]
])
]
]);
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function groupCallbackAfterMemberExit(array $data): JsonResponse
{
$groupId = Arr::get($data, 'GroupId');
throw_unless($this->gate($groupId), TestCallbackException::class);
if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
$memberList = Arr::map($data['ExitMemberList'], static fn($item) => IMHelper::accountToUserKey($item['Member_Account']));
ActivityGroupUser::query()->where('group_id', $group)->whereIn('user_id', $memberList)->delete();
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
/**
* @param array $data
* @return \Illuminate\Http\JsonResponse
* @throws \Throwable
*/
protected function groupCallbackAfterGroupDestroyed(array $data): JsonResponse
{
$groupId = Arr::get($data, 'GroupId');
throw_unless($this->gate($groupId), TestCallbackException::class);
if ($group = ActivityGroup::query()->where('im_group_id', $groupId)->value('id')) {
ActivityGroup::query()->whereKey($group)->delete();
ActivityGroupUser::query()->where('group_id', $group)->delete();
}
return new JsonResponse(['ActionStatus' => 'OK', 'ErrorInfo' => '', 'ErrorCode' => 0]);
}
}
<?php
namespace App\Jobs;
use App\Helpers\IMHelper;
use Arr;
use Carbon\Carbon;
use DB;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ImMessageSaveJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public array $data;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* Execute the job.
*
* @return void
* @throws \JsonException
*/
public function handle(): void
{
$data = [];
$fromIm = $this->data['From_Account'];
$from = IMHelper::accountToUserKey($fromIm);
$toIm = $this->data['To_Account'];
$to = IMHelper::accountToUserKey($this->data['To_Account']);
$time = Carbon::createFromTimestamp($this->data['MsgTime'])->toDateTimeString();
foreach ($this->data['MsgBody'] as $value) {
if ($value['MsgType'] === 'TIMCustomElem') {
$value['MsgContent']['Data'] = json_decode($value['MsgContent']['Data'], true, 512, JSON_THROW_ON_ERROR);
}
$data[] = array_merge([
'from' => $from, 'from_im' => $fromIm, 'to' => $to, 'to_im' => $toIm, 'created_at' => $time, 'updated_at' => $time,
'msg_key' => Arr::get($this->data, 'MsgKey', ''),
'content' => json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
], $this->formatMsg($value));
}
DB::table('im_messags')->insert($data);
}
/**
* @param array{MsgType:string,MsgContent:array} $msgItem
* @return array{type:int,value:string}
*/
public function formatMsg(array $msgItem): array
{
return match ($msgItem['MsgType']) {
'TIMTextElem' => ['type' => 1, 'value' => Arr::get($msgItem, 'MsgContent.Text', '')],
'TIMImageElem' => ['type' => 2, 'value' => Arr::get($msgItem, 'MsgContent.ImageInfoArray.0.URL', '')],
'TIMVideoFileElem' => ['type' => 3, 'value' => Arr::get($msgItem, 'MsgContent.VideoUrl', '')],
'TIMFileElem' => ['type' => 4, 'value' => Arr::get($msgItem, 'MsgContent.Url', '')],
'TIMCustomElem' => $this->formatMsgCustomData($msgItem['MsgContent']['Data']),
'TIMFaceElem' => ['type' => 17, 'value' => Arr::get($msgItem, 'MsgContent')],
default => ['type' => 18, 'value' => '其他消息'],
};
}
/**
* @param array $data
* @return array
*/
public function formatMsgCustomData(array $data): array
{
switch ($data['businessID']) {
case 'invite_team':
return ['type' => 5, 'value' => '邀请入队'];
case 'apply_team':
return ['type' => 6, 'value' => '申请入队'];
case 'custom_song':
return ['type' => 9, 'value' => Arr::get($data, 'id', 0)];
case 'exit_team':
return ['type' => 10, 'value' => '成员退出团队申请'];
case 'invite_brand':
return ['type' => 19, 'value' => '邀请成员加入厂牌'];
case 'exit_brand':
return ['type' => 23, 'value' => '成员申请退出厂牌'];
case 'system_notice':
return ['type' => 24, 'value' => '系统消息'];
case 'invite_tip':
if (in_array($data['tipStatus'], [1, 6], false)) {
return ['type' => 7, 'value' => '同意邀请或申请'];
}
return match ($data['tipStatus']) {
'3' => ['type' => 11, 'value' => '队长同意成员退出'],
'8' => ['type' => 12, 'value' => '到期自动退出团队'],
'5' => ['type' => 13, 'value' => '队长将成员从团队移出'],
'2' => ['type' => 14, 'value' => '拒绝团队邀请'],
'4' => ['type' => 15, 'value' => '取消退出团队'],
'7' => ['type' => 16, 'value' => '拒绝用户申请'],
'12' => ['type' => 20, 'value' => '同意厂牌邀请'],
'13' => ['type' => 21, 'value' => '拒绝厂牌邀请'],
'11' => ['type' => 22, 'value' => '将成员移出厂牌'],
'9' => ['type' => 24, 'value' => '成员取消退出厂牌'],
'10' => ['type' => 25, 'value' => '厂牌管理员同意退出'],
default => ['type' => 18, 'value' => '其他消息']
};
default:
return ['type' => 18, 'value' => '其他消息'];
}
}
}