Files
sbnews/server/app/common/service/notice/UserNoticeService.php
T

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;
}
}