Import JC Web IM
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
define('IM_ADMIN_URL', getenv('IM_ADMIN_URL') ?: 'http://127.0.0.1:18080');
|
||||
define('IM_ADMIN_SECRET', getenv('IM_ADMIN_SECRET') ?: '');
|
||||
define('IM_DB_DSN', getenv('IM_DB_DSN') ?: 'mysql:host=127.0.0.1;port=3306;dbname=wfchat;charset=utf8mb4');
|
||||
define('IM_DB_USER', getenv('IM_DB_USER') ?: '');
|
||||
define('IM_DB_PASSWORD', getenv('IM_DB_PASSWORD') ?: '');
|
||||
define('IM_APP_DB_DSN', getenv('IM_APP_DB_DSN') ?: 'mysql:host=127.0.0.1;port=3306;dbname=appdata;charset=utf8mb4');
|
||||
define('IM_APP_DB_USER', getenv('IM_APP_DB_USER') ?: '');
|
||||
define('IM_APP_DB_PASSWORD', getenv('IM_APP_DB_PASSWORD') ?: '');
|
||||
define('IM_DB_TIMEZONE', getenv('IM_DB_TIMEZONE') ?: 'Asia/Bangkok');
|
||||
|
||||
function respond(int $code, string $message, array $result = []): void
|
||||
{
|
||||
echo json_encode([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'result' => $result,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
function readJsonPayload(): array
|
||||
{
|
||||
$payload = json_decode(file_get_contents('php://input') ?: '[]', true);
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
|
||||
function imAdminPost(string $path, array $payload): array
|
||||
{
|
||||
$nonce = (string)random_int(1, 1000000);
|
||||
$timestamp = (string)((int)floor(microtime(true) * 1000));
|
||||
$sign = sha1($nonce . '|' . IM_ADMIN_SECRET . '|' . $timestamp);
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$curl = curl_init(IM_ADMIN_URL . $path);
|
||||
if ($curl === false) {
|
||||
throw new RuntimeException('IM admin client init failed');
|
||||
}
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
|
||||
CURLOPT_CONNECTTIMEOUT_MS => 1000,
|
||||
CURLOPT_TIMEOUT_MS => 4000,
|
||||
CURLOPT_FRESH_CONNECT => true,
|
||||
CURLOPT_FORBID_REUSE => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'Connection: close',
|
||||
'nonce: ' . $nonce,
|
||||
'timestamp: ' . $timestamp,
|
||||
'sign: ' . $sign,
|
||||
],
|
||||
]);
|
||||
|
||||
$responseBody = curl_exec($curl);
|
||||
$curlError = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($responseBody === false || $responseBody === '') {
|
||||
throw new RuntimeException($curlError ?: 'IM admin request failed');
|
||||
}
|
||||
|
||||
$data = json_decode($responseBody, true);
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException('Invalid IM admin response');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function requireImSuccess(array $response, string $fallbackMessage): array
|
||||
{
|
||||
if (($response['code'] ?? -1) !== 0) {
|
||||
throw new RuntimeException((string)($response['msg'] ?? $response['message'] ?? $fallbackMessage));
|
||||
}
|
||||
|
||||
return is_array($response['result'] ?? null) ? $response['result'] : [];
|
||||
}
|
||||
|
||||
function imDb(): PDO
|
||||
{
|
||||
static $pdo = null;
|
||||
if ($pdo instanceof PDO) {
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
$pdo = new PDO(IM_DB_DSN, IM_DB_USER, IM_DB_PASSWORD, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function imAppDb(): PDO
|
||||
{
|
||||
static $pdo = null;
|
||||
if ($pdo instanceof PDO) {
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
$pdo = new PDO(IM_APP_DB_DSN, IM_APP_DB_USER, IM_APP_DB_PASSWORD, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function ensureWebImAppTables(): void
|
||||
{
|
||||
static $ensured = false;
|
||||
if ($ensured) {
|
||||
return;
|
||||
}
|
||||
$ensured = true;
|
||||
|
||||
$db = imAppDb();
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_groups` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`group_id` varchar(64) not null,
|
||||
`category` varchar(20) not null default 'group',
|
||||
`name` varchar(128) not null,
|
||||
`portrait` varchar(1024) not null default '',
|
||||
`owner_uid` varchar(64) not null,
|
||||
`join_mode` varchar(20) not null default 'open',
|
||||
`announcement` text null,
|
||||
`native_group_id` varchar(64) not null default '',
|
||||
`native_status` varchar(32) not null default 'pending',
|
||||
`native_error` varchar(255) not null default '',
|
||||
`created_by` varchar(64) not null,
|
||||
`created_at_ms` bigint not null,
|
||||
`updated_at_ms` bigint not null,
|
||||
`deleted` tinyint not null default 0,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_groups_group_id` (`group_id`),
|
||||
key `idx_t_web_groups_owner` (`owner_uid`),
|
||||
key `idx_t_web_groups_category` (`category`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
try {
|
||||
$db->exec("alter table `t_web_groups` add column `welcome_text` text null after `announcement`");
|
||||
} catch (Throwable) {
|
||||
}
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_group_members` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`group_id` varchar(64) not null,
|
||||
`user_id` varchar(64) not null,
|
||||
`role` varchar(20) not null default 'member',
|
||||
`alias` varchar(128) not null default '',
|
||||
`joined_at_ms` bigint not null,
|
||||
`updated_at_ms` bigint not null,
|
||||
`status` tinyint not null default 0,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_group_members_gid_uid` (`group_id`, `user_id`),
|
||||
key `idx_t_web_group_members_user` (`user_id`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_group_messages` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`message_uid` varchar(64) not null,
|
||||
`group_id` varchar(64) not null,
|
||||
`sender_uid` varchar(64) not null,
|
||||
`text` text not null,
|
||||
`created_at_ms` bigint not null,
|
||||
`edited_at_ms` bigint not null default 0,
|
||||
`recalled` tinyint not null default 0,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_group_messages_uid` (`message_uid`),
|
||||
key `idx_t_web_group_messages_group_time` (`group_id`, `created_at_ms`),
|
||||
key `idx_t_web_group_messages_sender` (`sender_uid`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_message_attachments` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`message_uid` varchar(64) not null,
|
||||
`conversation_type` varchar(16) not null default 'single',
|
||||
`target_id` varchar(128) not null default '',
|
||||
`sender_uid` varchar(64) not null default '',
|
||||
`attachment_type` varchar(16) not null,
|
||||
`url` varchar(1024) not null,
|
||||
`name` varchar(255) not null default '',
|
||||
`mime` varchar(128) not null default '',
|
||||
`size_bytes` bigint not null default 0,
|
||||
`created_at_ms` bigint not null,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_message_attachments_uid` (`message_uid`),
|
||||
key `idx_t_web_message_attachments_target` (`conversation_type`, `target_id`, `created_at_ms`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_group_reads` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`group_id` varchar(64) not null,
|
||||
`user_id` varchar(64) not null,
|
||||
`read_at_ms` bigint not null default 0,
|
||||
`updated_at_ms` bigint not null,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_group_reads_gid_uid` (`group_id`, `user_id`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_group_mute_rules` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`group_id` varchar(64) not null,
|
||||
`mode` varchar(32) not null default 'off',
|
||||
`schedule_type` varchar(16) not null default 'once',
|
||||
`starts_at_ms` bigint not null default 0,
|
||||
`ends_at_ms` bigint not null default 0,
|
||||
`daily_start` varchar(8) not null default '',
|
||||
`daily_end` varchar(8) not null default '',
|
||||
`enabled` tinyint not null default 0,
|
||||
`created_by` varchar(64) not null default '',
|
||||
`updated_at_ms` bigint not null,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_group_mute_rules_group` (`group_id`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_broadcast_logs` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`sender_uid` varchar(64) not null,
|
||||
`target_count` int not null default 0,
|
||||
`success_count` int not null default 0,
|
||||
`failed_count` int not null default 0,
|
||||
`text` text not null,
|
||||
`target_json` text null,
|
||||
`result_json` text null,
|
||||
`created_at_ms` bigint not null,
|
||||
primary key (`id`),
|
||||
key `idx_t_web_broadcast_logs_sender_time` (`sender_uid`, `created_at_ms`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
$db->exec(
|
||||
"create table if not exists `t_web_user_remarks` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`owner_uid` varchar(64) not null,
|
||||
`target_uid` varchar(64) not null,
|
||||
`alias` varchar(128) not null default '',
|
||||
`created_at_ms` bigint not null,
|
||||
`updated_at_ms` bigint not null,
|
||||
primary key (`id`),
|
||||
unique key `uk_t_web_user_remarks_owner_target` (`owner_uid`, `target_uid`),
|
||||
key `idx_t_web_user_remarks_target` (`target_uid`)
|
||||
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci"
|
||||
);
|
||||
}
|
||||
|
||||
function webImPlaceholders(array $values): string
|
||||
{
|
||||
return implode(',', array_fill(0, count($values), '?'));
|
||||
}
|
||||
|
||||
function fetchWebUserRemarkMap(PDO $db, string $ownerId, array $targetIds): array
|
||||
{
|
||||
$targetIds = array_values(array_unique(array_filter(array_map(static function ($targetId): string {
|
||||
return trim((string)$targetId);
|
||||
}, $targetIds))));
|
||||
if ($ownerId === '' || !$targetIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$statement = $db->prepare(
|
||||
'select `target_uid`, `alias` from `t_web_user_remarks`'
|
||||
. ' where `owner_uid` = ? and `target_uid` in (' . webImPlaceholders($targetIds) . ')'
|
||||
);
|
||||
$statement->execute(array_merge([$ownerId], $targetIds));
|
||||
|
||||
$remarks = [];
|
||||
foreach ($statement->fetchAll() as $row) {
|
||||
$remarks[(string)$row['target_uid']] = trim((string)($row['alias'] ?? ''));
|
||||
}
|
||||
|
||||
return $remarks;
|
||||
}
|
||||
|
||||
function saveWebUserRemark(PDO $db, string $ownerId, string $targetId, string $alias): void
|
||||
{
|
||||
if ($ownerId === '' || $targetId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($alias === '') {
|
||||
$delete = $db->prepare('delete from `t_web_user_remarks` where `owner_uid` = ? and `target_uid` = ?');
|
||||
$delete->execute([$ownerId, $targetId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = webImNowMs();
|
||||
$statement = $db->prepare(
|
||||
'insert into `t_web_user_remarks` (`owner_uid`, `target_uid`, `alias`, `created_at_ms`, `updated_at_ms`)'
|
||||
. ' values (?, ?, ?, ?, ?)'
|
||||
. ' on duplicate key update `alias` = values(`alias`), `updated_at_ms` = values(`updated_at_ms`)'
|
||||
);
|
||||
$statement->execute([$ownerId, $targetId, $alias, $now, $now]);
|
||||
}
|
||||
|
||||
function webImNowMs(): int
|
||||
{
|
||||
return (int)floor(microtime(true) * 1000);
|
||||
}
|
||||
|
||||
function webImMessageUid(): string
|
||||
{
|
||||
$timestampPart = (webImNowMs() - 1514736000000) << 21;
|
||||
return (string)($timestampPart | random_int(1, 0x1FFFFF));
|
||||
}
|
||||
|
||||
function javaStringHashCode(string $value): int
|
||||
{
|
||||
$hash = 0;
|
||||
$length = strlen($value);
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$hash = (31 * $hash + ord($value[$i])) & 0xFFFFFFFF;
|
||||
if ($hash >= 0x80000000) {
|
||||
$hash -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
function imUserMessagesTable(string $userId): string
|
||||
{
|
||||
return 't_user_messages_' . (abs(javaStringHashCode($userId)) % 128);
|
||||
}
|
||||
|
||||
function imMessageTable(string $messageId): string
|
||||
{
|
||||
$timestamp = ((int)$messageId >> 21) + 1514736000000;
|
||||
$date = (new DateTimeImmutable('@' . (int)floor($timestamp / 1000)))
|
||||
->setTimezone(new DateTimeZone(IM_DB_TIMEZONE));
|
||||
$year = (int)$date->format('Y');
|
||||
$month = (int)$date->format('n') - 1;
|
||||
|
||||
return 't_messages_' . (($year % 3) * 12 + $month);
|
||||
}
|
||||
Reference in New Issue
Block a user