- 新增 server/services/wechatSubscribeService.ts:access_token 缓存 + 模板配置 + 字段裁剪 + subscribe/send · 内置默认模板 SONG_REJECTED(模板 ID erAK**…,审核内容=thing6←歌名,驳回原因=thing4←驳回原因) · 环境变量 WECHAT_MINI_SUBSCRIBE_TEMPLATES 可按键覆盖默认模板 · thing 字段按微信限制裁到 20 字(超长截 19 字 + 省略号),避免 47003 · 发送永不抛异常;43101(用户没授权/没额度)只记 info - 新增 server/api/user/wechat-subscribe-templates.get.ts:给小程序返回模板 ID(客户端要拿它去申请授权) - 新增 server/api/admin/wechat/test-subscribe.post.ts:超管自测接口(token/openid/字段/微信原文一次看清) - notificationService.ts:歌曲被选中/已播放/被驳回/系统通知 四处顺带发微信订阅消息 - .env.example + 部署流水线:WECHAT_MINI_SUBSCRIBE_TEMPLATES 可用仓库 Secret 下发
813 lines
22 KiB
TypeScript
813 lines
22 KiB
TypeScript
import { db } from '~/drizzle/db'
|
||
import {
|
||
notifications,
|
||
notificationSettings,
|
||
playTimes,
|
||
schedules,
|
||
songCollaborators,
|
||
songs,
|
||
systemSettings,
|
||
users,
|
||
votes
|
||
} from '~/drizzle/schema'
|
||
import { and, eq, gte, inArray } from 'drizzle-orm'
|
||
import { sendBatchMeowNotifications, sendMeowNotificationToUser } from './meowNotificationService'
|
||
import { sendWechatSubscribeToUser } from './wechatSubscribeService'
|
||
import { sendBatchEmailNotifications, sendEmailNotificationToUser } from './smtpService'
|
||
import { formatDateTime, getBeijingTime } from '~/utils/timeUtils'
|
||
|
||
/**
|
||
* 创建联合投稿邀请通知
|
||
*/
|
||
export async function createCollaborationInvitationNotification(
|
||
inviterId: number,
|
||
inviteeId: number,
|
||
songId: number,
|
||
songTitle: string
|
||
) {
|
||
try {
|
||
const inviter = await db
|
||
.select()
|
||
.from(users)
|
||
.where(eq(users.id, inviterId))
|
||
.limit(1)
|
||
.then((res) => res[0])
|
||
const message = `用户 ${inviter?.name || '未知用户'} 邀请您共同投稿歌曲《${songTitle}》。`
|
||
|
||
// 创建通知
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId: inviteeId,
|
||
type: 'COLLABORATION_INVITE',
|
||
message,
|
||
songId
|
||
})
|
||
.returning()
|
||
|
||
// 发送外部通知(MeoW, Email等)
|
||
try {
|
||
await sendMeowNotificationToUser(inviteeId, '收到联合投稿邀请', message)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
inviteeId,
|
||
'收到联合投稿邀请',
|
||
message,
|
||
undefined,
|
||
'notification.collaborationInvite',
|
||
{
|
||
inviterName: inviter?.name || '未知用户',
|
||
songTitle: songTitle
|
||
}
|
||
)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notificationResult[0]
|
||
} catch (error) {
|
||
console.error('创建联合投稿邀请通知失败:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建联合投稿回复通知
|
||
*/
|
||
export async function createCollaborationResponseNotification(
|
||
inviteeId: number,
|
||
inviterId: number,
|
||
songTitle: string,
|
||
accepted: boolean
|
||
) {
|
||
try {
|
||
const invitee = await db
|
||
.select()
|
||
.from(users)
|
||
.where(eq(users.id, inviteeId))
|
||
.limit(1)
|
||
.then((res) => res[0])
|
||
const actionText = accepted ? '接受' : '拒绝'
|
||
const message = `用户 ${invitee?.name || '未知用户'} ${actionText}了您的歌曲《${songTitle}》的联合投稿邀请。`
|
||
|
||
// 创建通知
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId: inviterId,
|
||
type: 'COLLABORATION_RESPONSE',
|
||
message
|
||
// songId // 这里可能不需要songId,或者需要传进来
|
||
})
|
||
.returning()
|
||
|
||
return notificationResult[0]
|
||
} catch (error) {
|
||
console.error('创建联合投稿回复通知失败:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建歌曲被选中的通知
|
||
*/
|
||
export async function createSongSelectedNotification(
|
||
userId: number,
|
||
songId: number,
|
||
songInfo: {
|
||
title: string
|
||
artist: string
|
||
playDate: Date
|
||
},
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
// 获取系统设置,检查是否启用播出时段功能
|
||
const systemSettingsResult = await db.select().from(systemSettings).limit(1)
|
||
const systemConfig = systemSettingsResult[0]
|
||
const isPlayTimeEnabled = systemConfig?.enablePlayTimeSelection || false
|
||
|
||
// 获取排期对应的播出时段
|
||
const scheduleResult = await db
|
||
.select({
|
||
id: schedules.id,
|
||
songId: schedules.songId,
|
||
playDate: schedules.playDate,
|
||
playTimeId: schedules.playTimeId,
|
||
playTime: {
|
||
id: playTimes.id,
|
||
name: playTimes.name,
|
||
startTime: playTimes.startTime,
|
||
endTime: playTimes.endTime
|
||
}
|
||
})
|
||
.from(schedules)
|
||
.leftJoin(playTimes, eq(schedules.playTimeId, playTimes.id))
|
||
.where(and(eq(schedules.songId, songId), eq(schedules.playDate, songInfo.playDate)))
|
||
.limit(1)
|
||
const schedule = scheduleResult[0]
|
||
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, userId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了此类通知,则不发送
|
||
if (settings && !settings.enabled) {
|
||
return null
|
||
}
|
||
|
||
// 创建通知,根据播出时段功能启用状态决定显示内容
|
||
let message = `您投稿的歌曲《${songInfo.title}》已被安排播放,播放日期:${formatDate(songInfo.playDate)}。`
|
||
|
||
// 只有在启用播出时段功能且有播出时段信息时,才添加播出时段详情
|
||
if (isPlayTimeEnabled && schedule?.playTime) {
|
||
let timeInfo = ''
|
||
|
||
// 根据开始和结束时间的情况,格式化显示
|
||
if (schedule.playTime.startTime && schedule.playTime.endTime) {
|
||
timeInfo = `(${schedule.playTime.startTime}-${schedule.playTime.endTime})`
|
||
} else if (schedule.playTime.startTime) {
|
||
timeInfo = `(开始时间:${schedule.playTime.startTime})`
|
||
} else if (schedule.playTime.endTime) {
|
||
timeInfo = `(结束时间:${schedule.playTime.endTime})`
|
||
}
|
||
|
||
message += `播出时段:${schedule.playTime.name}${timeInfo ? ' ' + timeInfo : ''}。`
|
||
}
|
||
|
||
let notification
|
||
try {
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId,
|
||
type: 'SONG_SELECTED',
|
||
message,
|
||
songId
|
||
})
|
||
.returning()
|
||
notification = notificationResult[0]
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(userId, '收到新选中', message)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
// 同步发送 微信订阅消息(只有绑了微信、且刚点过授权的用户收得到,见 wechatSubscribeService)
|
||
try {
|
||
await sendWechatSubscribeToUser(userId, 'SONG_SELECTED', { songTitle: songInfo.title, message })
|
||
} catch (error) {
|
||
console.error('发送微信订阅消息失败:', error)
|
||
}
|
||
|
||
// 同步发送 邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
userId,
|
||
'歌曲被选中',
|
||
message,
|
||
ipAddress,
|
||
'notification.songSelected',
|
||
{
|
||
songTitle: songInfo.title,
|
||
playDate: formatDate(songInfo.playDate),
|
||
playTimeName: isPlayTimeEnabled && schedule?.playTime ? schedule.playTime.name : '',
|
||
playTimeRange:
|
||
isPlayTimeEnabled &&
|
||
schedule?.playTime &&
|
||
(schedule.playTime.startTime || schedule.playTime.endTime)
|
||
? `${schedule.playTime.startTime || ''}${schedule.playTime.startTime && schedule.playTime.endTime ? '-' : ''}${schedule.playTime.endTime || ''}`
|
||
: ''
|
||
}
|
||
)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notification
|
||
} catch (err) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 格式化日期为 yyyy-MM-dd 格式(北京时间)
|
||
function formatDate(date: Date): string {
|
||
return formatDateTime(date, 'YYYY-MM-DD')
|
||
}
|
||
|
||
/**
|
||
* 创建歌曲已播放的通知
|
||
*/
|
||
export async function createSongPlayedNotification(songId: number, ipAddress?: string) {
|
||
try {
|
||
// 获取歌曲信息
|
||
const songResult = await db.select().from(songs).where(eq(songs.id, songId)).limit(1)
|
||
const song = songResult[0]
|
||
|
||
if (!song) {
|
||
return null
|
||
}
|
||
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, song.requesterId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了此类通知,则不发送
|
||
if (settings && !settings.songPlayedEnabled) {
|
||
return null
|
||
}
|
||
|
||
// 创建通知
|
||
const message = `您投稿的歌曲《${song.title}》已播放。`
|
||
|
||
// 获取所有关联用户(投稿人 + 联合投稿人)
|
||
const userIdsToNotify = [song.requesterId]
|
||
|
||
// 获取联合投稿人
|
||
const collaborators = await db
|
||
.select()
|
||
.from(songCollaborators)
|
||
.where(and(eq(songCollaborators.songId, songId), eq(songCollaborators.status, 'ACCEPTED')))
|
||
|
||
collaborators.forEach((c) => {
|
||
if (!userIdsToNotify.includes(c.userId)) {
|
||
userIdsToNotify.push(c.userId)
|
||
}
|
||
})
|
||
|
||
const notificationsCreated = []
|
||
|
||
for (const targetUserId of userIdsToNotify) {
|
||
try {
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, targetUserId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了此类通知,则不发送
|
||
if (settings && !settings.songPlayedEnabled) {
|
||
continue
|
||
}
|
||
|
||
const userMessage =
|
||
targetUserId === song.requesterId
|
||
? message
|
||
: `您参与联合投稿的歌曲《${song.title}》已播放。`
|
||
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId: targetUserId,
|
||
type: 'SONG_PLAYED',
|
||
message: userMessage,
|
||
songId: songId
|
||
})
|
||
.returning()
|
||
notificationsCreated.push(notificationResult[0])
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(targetUserId, '歌曲已播放', userMessage)
|
||
} catch (error) {
|
||
console.error(`发送 MeoW 通知失败 (User: ${targetUserId}):`, error)
|
||
}
|
||
// 同步发送 微信订阅消息(只有绑了微信、且刚点过授权的用户收得到,见 wechatSubscribeService)
|
||
try {
|
||
await sendWechatSubscribeToUser(targetUserId, 'SONG_PLAYED', { songTitle: song.title, message: userMessage })
|
||
} catch (error) {
|
||
console.error('发送微信订阅消息失败:', error)
|
||
}
|
||
|
||
// 同步发送 邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
targetUserId,
|
||
'歌曲已播放',
|
||
userMessage,
|
||
ipAddress,
|
||
'notification.songPlayed',
|
||
{ songTitle: song.title }
|
||
)
|
||
} catch (error) {
|
||
console.error(`发送邮件通知失败 (User: ${targetUserId}):`, error)
|
||
}
|
||
} catch (err) {
|
||
console.error(`处理播放通知失败 (User: ${targetUserId}):`, err)
|
||
}
|
||
}
|
||
|
||
return notificationsCreated.length > 0 ? notificationsCreated[0] : null
|
||
} catch (err) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建歌曲获得投票的通知
|
||
*/
|
||
export async function createSongVotedNotification(
|
||
songId: number,
|
||
voterId: number,
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
// 获取歌曲信息
|
||
const songResult = await db.select().from(songs).where(eq(songs.id, songId)).limit(1)
|
||
const song = songResult[0]
|
||
|
||
if (!song) {
|
||
return null
|
||
}
|
||
|
||
// 获取歌曲的投票信息
|
||
const songVotes = await db.select().from(votes).where(eq(votes.songId, songId))
|
||
|
||
// 获取投票用户信息
|
||
const voterResult = await db.select().from(users).where(eq(users.id, voterId)).limit(1)
|
||
const voter = voterResult[0]
|
||
|
||
if (!voter) {
|
||
return null
|
||
}
|
||
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, song.requesterId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了此类通知,则不发送
|
||
if (settings && !settings.songVotedEnabled) {
|
||
return null
|
||
}
|
||
|
||
// 不要给自己投票发通知
|
||
if (song.requesterId === voterId) {
|
||
return null
|
||
}
|
||
|
||
// 检查投票阈值
|
||
const threshold = settings?.songVotedThreshold || 1
|
||
if (songVotes.length % threshold !== 0) {
|
||
return null
|
||
}
|
||
|
||
const message = `您投稿的歌曲《${song.title}》获得了一个新的投票,当前共有 ${songVotes.length} 个投票。`
|
||
|
||
// 防重复通知:检查最近5分钟内是否有相同歌曲的投票通知
|
||
const fiveMinutesAgo = new Date(getBeijingTime().getTime() - 5 * 60 * 1000)
|
||
|
||
const existingNotificationResult = await db
|
||
.select()
|
||
.from(notifications)
|
||
.where(
|
||
and(
|
||
eq(notifications.userId, song.requesterId),
|
||
eq(notifications.type, 'SONG_VOTED'),
|
||
eq(notifications.songId, songId),
|
||
eq(notifications.read, false),
|
||
gte(notifications.createdAt, fiveMinutesAgo)
|
||
)
|
||
)
|
||
.orderBy(notifications.createdAt)
|
||
.limit(1)
|
||
const existingNotification = existingNotificationResult[0]
|
||
|
||
let notification
|
||
if (existingNotification) {
|
||
// 更新现有通知
|
||
const updateResult = await db
|
||
.update(notifications)
|
||
.set({
|
||
message
|
||
})
|
||
.where(eq(notifications.id, existingNotification.id))
|
||
.returning()
|
||
notification = updateResult[0]
|
||
} else {
|
||
// 创建新通知
|
||
const createResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId: song.requesterId,
|
||
type: 'SONG_VOTED',
|
||
message,
|
||
songId: songId
|
||
})
|
||
.returning()
|
||
notification = createResult[0]
|
||
}
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(song.requesterId, '收到新投票', message)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
|
||
// 同步发送 邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
song.requesterId,
|
||
'收到新投票',
|
||
message,
|
||
ipAddress,
|
||
'notification.songVoted',
|
||
{ songTitle: song.title, votesCount: songVotes.length }
|
||
)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notification
|
||
} catch (err) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建歌曲驳回通知
|
||
*/
|
||
export async function createSongRejectedNotification(
|
||
userId: number,
|
||
songInfo: { title: string; artist: string },
|
||
reason: string,
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, userId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了通知,则不发送
|
||
if (settings && !settings.enabled) {
|
||
return null
|
||
}
|
||
|
||
// 创建通知消息
|
||
const message = `您投稿的歌曲《${songInfo.title} - ${songInfo.artist}》已被管理员驳回。驳回原因:${reason}`
|
||
|
||
// 创建站内通知
|
||
let notification
|
||
try {
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId,
|
||
type: 'SONG_REJECTED',
|
||
message
|
||
})
|
||
.returning()
|
||
notification = notificationResult[0]
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(userId, '歌曲被驳回', message)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
// 同步发送 微信订阅消息(只有绑了微信、且刚点过授权的用户收得到,见 wechatSubscribeService)
|
||
try {
|
||
await sendWechatSubscribeToUser(userId, 'SONG_REJECTED', { songTitle: songInfo.title, reason: reason, message })
|
||
} catch (error) {
|
||
console.error('发送微信订阅消息失败:', error)
|
||
}
|
||
|
||
// 同步发送邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
userId,
|
||
'歌曲被驳回',
|
||
message,
|
||
ipAddress,
|
||
'notification.songRejected',
|
||
{
|
||
songTitle: songInfo.title,
|
||
songArtist: songInfo.artist,
|
||
reason: reason
|
||
}
|
||
)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notification
|
||
} catch (err) {
|
||
console.error('创建歌曲驳回通知失败:', err)
|
||
return null
|
||
}
|
||
}
|
||
|
||
export async function createSubmissionNoteClearedNotification(
|
||
userIds: number[],
|
||
songInfo: { title: string; artist: string },
|
||
reason?: string,
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
const uniqueUserIds = [...new Set(userIds.filter((userId) => Number.isInteger(userId) && userId > 0))]
|
||
|
||
if (uniqueUserIds.length === 0) {
|
||
return []
|
||
}
|
||
|
||
const title = '歌曲备注已被管理员清空'
|
||
const content = reason?.trim()
|
||
? `您投稿歌曲《${songInfo.title} - ${songInfo.artist}》的备注已被管理员清空。原因:${reason.trim()}`
|
||
: `您投稿歌曲《${songInfo.title} - ${songInfo.artist}》的备注已被管理员清空。`
|
||
|
||
return await createBatchSystemNotifications(uniqueUserIds, title, content, ipAddress)
|
||
} catch (error) {
|
||
console.error('创建歌曲备注清空通知失败:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建系统通知
|
||
*/
|
||
export async function createSystemNotification(
|
||
userId: number,
|
||
title: string,
|
||
content: string,
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, userId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了通知,则不发送
|
||
if (settings && !settings.enabled) {
|
||
return null
|
||
}
|
||
|
||
// 创建通知
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId: userId,
|
||
type: 'SYSTEM_NOTICE',
|
||
message: content
|
||
})
|
||
.returning()
|
||
const notification = notificationResult[0]
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(userId, title, content)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
// 同步发送 微信订阅消息(只有绑了微信、且刚点过授权的用户收得到,见 wechatSubscribeService)
|
||
try {
|
||
await sendWechatSubscribeToUser(userId, 'SYSTEM_NOTICE', { songTitle: title, message: content })
|
||
} catch (error) {
|
||
console.error('发送微信订阅消息失败:', error)
|
||
}
|
||
|
||
// 同步发送邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(
|
||
userId,
|
||
title,
|
||
content,
|
||
undefined,
|
||
undefined,
|
||
undefined,
|
||
ipAddress
|
||
)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notification
|
||
} catch (err) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量创建系统通知
|
||
*/
|
||
export async function createBatchSystemNotifications(
|
||
userIds: number[],
|
||
title: string,
|
||
content: string,
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
if (!userIds.length) {
|
||
return []
|
||
}
|
||
|
||
// 获取用户通知设置
|
||
const userSettings = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(inArray(notificationSettings.userId, userIds))
|
||
|
||
// 构建用户ID到通知设置的映射
|
||
const settingsMap = new Map()
|
||
userSettings.forEach((setting) => {
|
||
settingsMap.set(setting.userId, setting)
|
||
})
|
||
|
||
// 准备要创建的通知数据
|
||
const notificationsToCreate = []
|
||
|
||
for (const userId of userIds) {
|
||
const settings = settingsMap.get(userId)
|
||
|
||
// 如果用户关闭了通知,则不发送
|
||
if (settings && !settings.enabled) {
|
||
continue
|
||
}
|
||
|
||
notificationsToCreate.push({
|
||
userId,
|
||
type: 'SYSTEM_NOTICE',
|
||
message: content
|
||
})
|
||
}
|
||
|
||
// 如果没有可发送的通知,直接返回
|
||
if (notificationsToCreate.length === 0) {
|
||
return []
|
||
}
|
||
|
||
// 批量创建通知
|
||
const createdNotifications = await db
|
||
.insert(notifications)
|
||
.values(notificationsToCreate)
|
||
.returning()
|
||
const notificationCount = { count: createdNotifications.length }
|
||
|
||
// 同步发送 MeoW 通知
|
||
let meowResults = { success: 0, failed: 0 }
|
||
try {
|
||
meowResults = await sendBatchMeowNotifications(userIds, title, content)
|
||
} catch (error) {
|
||
console.error('批量发送 MeoW 通知失败:', error)
|
||
}
|
||
|
||
// 同步发送邮件通知
|
||
let emailResults = { success: 0, failed: 0 }
|
||
try {
|
||
emailResults = await sendBatchEmailNotifications(
|
||
userIds,
|
||
title,
|
||
content,
|
||
undefined,
|
||
undefined,
|
||
ipAddress
|
||
)
|
||
} catch (error) {
|
||
console.error('批量发送邮件通知失败:', error)
|
||
}
|
||
|
||
return {
|
||
count: notificationCount.count,
|
||
total: userIds.length,
|
||
meowNotifications: meowResults,
|
||
emailNotifications: emailResults
|
||
}
|
||
} catch (err) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建重播申请拒绝通知
|
||
*/
|
||
export async function createReplayRequestRejectedNotification(
|
||
userId: number,
|
||
songInfo: { title: string; artist: string },
|
||
ipAddress?: string
|
||
) {
|
||
try {
|
||
// 获取用户通知设置
|
||
const settingsResult = await db
|
||
.select()
|
||
.from(notificationSettings)
|
||
.where(eq(notificationSettings.userId, userId))
|
||
.limit(1)
|
||
const settings = settingsResult[0]
|
||
|
||
// 如果用户关闭了通知,则不发送
|
||
if (settings && !settings.enabled) {
|
||
return null
|
||
}
|
||
|
||
// 创建通知消息
|
||
const message = `您的重播申请《${songInfo.title}》已被管理员拒绝。`
|
||
|
||
// 创建站内通知
|
||
let notification
|
||
try {
|
||
const notificationResult = await db
|
||
.insert(notifications)
|
||
.values({
|
||
userId,
|
||
type: 'REPLAY_REJECTED',
|
||
message
|
||
})
|
||
.returning()
|
||
notification = notificationResult[0]
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
|
||
// 同步发送 MeoW 通知
|
||
try {
|
||
await sendMeowNotificationToUser(userId, '重播申请已拒绝', message)
|
||
} catch (error) {
|
||
console.error('发送 MeoW 通知失败:', error)
|
||
}
|
||
|
||
// 同步发送邮件通知
|
||
try {
|
||
await sendEmailNotificationToUser(userId, '重播申请已拒绝', message, ipAddress)
|
||
} catch (error) {
|
||
console.error('发送邮件通知失败:', error)
|
||
}
|
||
|
||
return notification
|
||
} catch (err) {
|
||
console.error('创建重播申请拒绝通知失败:', err)
|
||
return null
|
||
}
|
||
}
|