Files
IM-web-new/dist/api/im_web_bootstrap.php
T

444 lines
16 KiB
PHP

<?php
declare(strict_types=1);
function webImEnv(string $key, string $fallback = ''): string
{
$runtimeValue = getenv($key);
if ($runtimeValue !== false && $runtimeValue !== '') {
return $runtimeValue;
}
static $env = null;
if ($env === null) {
$env = [];
$paths = array_filter([
getenv('JC_WEB_IM_ENV_FILE') ?: '',
dirname(__DIR__, 2) . '/.env',
'/var/www/im-admin/.env',
'/var/www/im/.env',
]);
foreach ($paths as $path) {
if (!is_readable($path)) {
continue;
}
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '#') === 0 || strpos($line, '=') === false) {
continue;
}
[$name, $value] = array_map('trim', explode('=', $line, 2));
if ($name === '') {
continue;
}
$value = trim($value);
if (
strlen($value) >= 2
&& (($value[0] === '"' && substr($value, -1) === '"') || ($value[0] === "'" && substr($value, -1) === "'"))
) {
$value = substr($value, 1, -1);
}
$env[$name] = $value;
}
break;
}
}
return $env[$key] ?? $fallback;
}
function webImFirstEnv(array $keys, string $fallback = ''): string
{
foreach ($keys as $key) {
$value = webImEnv($key);
if ($value !== '') {
return $value;
}
}
return $fallback;
}
function webImMysqlDsn(string $host, string $port, string $database): string
{
return sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $database);
}
define('IM_ADMIN_URL', webImEnv('JC_WEB_IM_ADMIN_URL', 'http://127.0.0.1:18080'));
define('IM_ADMIN_SECRET', webImFirstEnv(['JC_WEB_IM_ADMIN_SECRET', 'SecretKey'], '123456'));
define('IM_DB_DSN', webImMysqlDsn(
webImEnv('JC_WEB_IM_DB_HOST', webImEnv('DB_HOST', '127.0.0.1')),
webImEnv('JC_WEB_IM_DB_PORT', webImEnv('DB_PORT', '3306')),
webImEnv('JC_WEB_IM_DB_DATABASE', webImEnv('DB_DATABASE', 'wfchat'))
));
define('IM_DB_USER', webImEnv('JC_WEB_IM_DB_USER', webImEnv('DB_USERNAME', 'winchen')));
define('IM_DB_PASSWORD', webImEnv('JC_WEB_IM_DB_PASSWORD', webImEnv('DB_PASSWORD', 'winchen@0904')));
define('IM_APP_DB_DSN', webImMysqlDsn(
webImFirstEnv(['JC_WEB_IM_APP_DB_HOST', 'DB_HOST_2', 'DB_HOST2_2'], webImEnv('DB_HOST', '127.0.0.1')),
webImFirstEnv(['JC_WEB_IM_APP_DB_PORT', 'DB_PORT_2', 'DB_PORT2_2'], webImEnv('DB_PORT', '3306')),
webImFirstEnv(['JC_WEB_IM_APP_DB_DATABASE', 'DB_DATABASE_2', 'DB_DATABASE2_2'], webImEnv('DB_DATABASE', 'appdata'))
));
define('IM_APP_DB_USER', webImFirstEnv(['JC_WEB_IM_APP_DB_USER', 'DB_USERNAME_2', 'DB_USERNAME2_2'], webImEnv('DB_USERNAME', 'winchen')));
define('IM_APP_DB_PASSWORD', webImFirstEnv(['JC_WEB_IM_APP_DB_PASSWORD', 'DB_PASSWORD_2', 'DB_PASSWORD2_2'], webImEnv('DB_PASSWORD', 'winchen@0904')));
define('IM_DB_TIMEZONE', webImEnv('JC_WEB_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 fetchImFriendAliasMap(PDO $db, string $ownerId, array $friendIds): array
{
$friendIds = array_values(array_unique(array_filter(array_map(static function ($friendId): string {
return trim((string)$friendId);
}, $friendIds))));
if ($ownerId === '' || !$friendIds) {
return [];
}
$aliases = [];
foreach (array_chunk($friendIds, 500) as $chunk) {
$statement = $db->prepare(
'select `_friend_uid`, `_alias` from `t_friend`'
. ' where `_uid` = ? and `_friend_uid` in (' . webImPlaceholders($chunk) . ') and `_state` = 0'
);
$statement->execute(array_merge([$ownerId], $chunk));
foreach ($statement->fetchAll() as $row) {
$aliases[(string)$row['_friend_uid']] = trim((string)($row['_alias'] ?? ''));
}
}
return $aliases;
}
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);
}