214 lines
6.9 KiB
PHP
214 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace app\common\service\notice;
|
|
|
|
use app\common\enum\notice\NoticeEnum;
|
|
use app\common\enum\notice\UserNoticeEnum;
|
|
use app\common\enum\YesNoEnum;
|
|
use app\common\model\notice\NoticeRecord;
|
|
use app\common\model\user\User;
|
|
use think\facade\Db;
|
|
|
|
/**
|
|
* 用户站内消息服务。
|
|
*/
|
|
class UserNoticeService
|
|
{
|
|
public static function postComment(int $recipientUserId, int $sourceUserId, int $postId, int $commentId, string $content): bool
|
|
{
|
|
return self::create(
|
|
$recipientUserId,
|
|
UserNoticeEnum::POST_COMMENT,
|
|
'你的帖子收到了评论',
|
|
self::sourceName($sourceUserId) . ' 评论了你的帖子:' . self::shortContent($content),
|
|
[
|
|
'source_user_id' => $sourceUserId,
|
|
'target_type' => 'community_post',
|
|
'target_id' => $postId,
|
|
'comment_id' => $commentId,
|
|
'target_url' => "/packages_community/pages/post_detail?id={$postId}&comment_id={$commentId}",
|
|
]
|
|
);
|
|
}
|
|
|
|
public static function commentReply(int $recipientUserId, int $sourceUserId, int $postId, int $commentId, string $content): bool
|
|
{
|
|
return self::create(
|
|
$recipientUserId,
|
|
UserNoticeEnum::COMMENT_REPLY,
|
|
'你的评论收到了回复',
|
|
self::sourceName($sourceUserId) . ' 回复了你的评论:' . self::shortContent($content),
|
|
[
|
|
'source_user_id' => $sourceUserId,
|
|
'target_type' => 'community_comment',
|
|
'target_id' => $commentId,
|
|
'post_id' => $postId,
|
|
'target_url' => "/packages_community/pages/post_detail?id={$postId}&comment_id={$commentId}",
|
|
]
|
|
);
|
|
}
|
|
|
|
public static function followed(int $recipientUserId, int $sourceUserId): bool
|
|
{
|
|
return self::create(
|
|
$recipientUserId,
|
|
UserNoticeEnum::FOLLOWED,
|
|
'你有一位新关注者',
|
|
self::sourceName($sourceUserId) . ' 关注了你',
|
|
[
|
|
'source_user_id' => $sourceUserId,
|
|
'target_type' => 'user_profile',
|
|
'target_id' => $sourceUserId,
|
|
'target_url' => "/packages_community/pages/user_profile?user_id={$sourceUserId}",
|
|
]
|
|
);
|
|
}
|
|
|
|
public static function adminPoints(int $recipientUserId, int $points, string $remark = ''): bool
|
|
{
|
|
$content = '管理员为你增加了 ' . $points . ' 积分';
|
|
$remark = self::shortContent($remark, 80);
|
|
if ($remark !== '') {
|
|
$content .= ',备注:' . $remark;
|
|
}
|
|
|
|
return self::create(
|
|
$recipientUserId,
|
|
UserNoticeEnum::ADMIN_POINTS,
|
|
'积分到账',
|
|
$content,
|
|
[
|
|
'target_type' => 'points',
|
|
'target_url' => '/packages/pages/points_log/points_log',
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 向全部启用用户或指定用户发布后台系统消息。
|
|
*/
|
|
public static function sendSystem(string $title, string $content, string $targetUrl = '', array $userIds = []): int
|
|
{
|
|
$query = User::where('is_disable', 0);
|
|
if (!empty($userIds)) {
|
|
$query->whereIn('id', $userIds);
|
|
}
|
|
|
|
$userIds = array_map('intval', $query->column('id'));
|
|
if (empty($userIds)) {
|
|
return 0;
|
|
}
|
|
|
|
$now = time();
|
|
$extra = self::encodeExtra([
|
|
'target_type' => 'system',
|
|
'target_url' => self::sanitizeTargetUrl($targetUrl),
|
|
]);
|
|
$sentCount = 0;
|
|
|
|
foreach (array_chunk($userIds, 300) as $chunk) {
|
|
$records = [];
|
|
foreach ($chunk as $userId) {
|
|
$records[] = self::recordData($userId, UserNoticeEnum::SYSTEM, $title, $content, $extra, $now);
|
|
}
|
|
Db::name('notice_record')->insertAll($records);
|
|
$sentCount += count($records);
|
|
}
|
|
|
|
return $sentCount;
|
|
}
|
|
|
|
public static function unreadCount(int $userId): int
|
|
{
|
|
if ($userId <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
return NoticeRecord::where('user_id', $userId)
|
|
->whereIn('scene_id', UserNoticeEnum::getSceneIds())
|
|
->where('read', YesNoEnum::NO)
|
|
->count();
|
|
}
|
|
|
|
public static function decodeExtra($value): array
|
|
{
|
|
if (!is_string($value) || $value === '') {
|
|
return [];
|
|
}
|
|
|
|
$data = json_decode($value, true);
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
public static function sanitizeTargetUrl(string $targetUrl): string
|
|
{
|
|
$targetUrl = trim($targetUrl);
|
|
if ($targetUrl === '' || !str_starts_with($targetUrl, '/') || str_starts_with($targetUrl, '//')) {
|
|
return '';
|
|
}
|
|
|
|
return mb_substr($targetUrl, 0, 200);
|
|
}
|
|
|
|
private static function create(int $recipientUserId, int $sceneId, string $title, string $content, array $extra = []): bool
|
|
{
|
|
$sourceUserId = (int) ($extra['source_user_id'] ?? 0);
|
|
if ($recipientUserId <= 0 || $recipientUserId === $sourceUserId || !in_array($sceneId, UserNoticeEnum::getSceneIds(), true)) {
|
|
return false;
|
|
}
|
|
|
|
$now = time();
|
|
NoticeRecord::create(self::recordData(
|
|
$recipientUserId,
|
|
$sceneId,
|
|
$title,
|
|
$content,
|
|
self::encodeExtra($extra),
|
|
$now
|
|
));
|
|
|
|
return true;
|
|
}
|
|
|
|
private static function recordData(int $userId, int $sceneId, string $title, string $content, string $extra, int $now): array
|
|
{
|
|
return [
|
|
'user_id' => $userId,
|
|
'title' => mb_substr(trim($title), 0, 50),
|
|
'content' => mb_substr(trim($content), 0, 1000),
|
|
'scene_id' => $sceneId,
|
|
'read' => YesNoEnum::NO,
|
|
'recipient' => 1,
|
|
'send_type' => NoticeEnum::SYSTEM,
|
|
'notice_type' => NoticeEnum::BUSINESS_NOTIFICATION,
|
|
'extra' => $extra,
|
|
'create_time' => $now,
|
|
'update_time' => $now,
|
|
];
|
|
}
|
|
|
|
private static function encodeExtra(array $extra): string
|
|
{
|
|
if (isset($extra['target_url'])) {
|
|
$extra['target_url'] = self::sanitizeTargetUrl((string) $extra['target_url']);
|
|
}
|
|
$encoded = json_encode($extra, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
return is_string($encoded) ? mb_substr($encoded, 0, 255) : '';
|
|
}
|
|
|
|
private static function sourceName(int $userId): string
|
|
{
|
|
$name = User::where('id', $userId)->value('nickname');
|
|
return $name ? (string) $name : '一位用户';
|
|
}
|
|
|
|
private static function shortContent(string $content, int $length = 60): string
|
|
{
|
|
$content = trim(preg_replace('/\s+/u', ' ', strip_tags($content)) ?: '');
|
|
if ($content === '') {
|
|
return '';
|
|
}
|
|
return mb_strlen($content) > $length ? mb_substr($content, 0, $length) . '…' : $content;
|
|
}
|
|
}
|