You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Node.js、Express、TypeORM实现随机时间推送通知?

实现基于用户时区的随机通知推送(类似BeReal功能)

一、数据库模型设计(TypeORM)

首先在用户表中存储时区、通知状态及上次通知时间,用TypeORM实体定义如下:

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  // IANA标准时区,比如 "Asia/Shanghai"、"America/Los_Angeles"
  @Column({ type: "varchar", length: 50 })
  timezone: string;

  // 是否开启通知
  @Column({ default: true })
  isNotificationEnabled: boolean;

  // 上次发送通知的UTC时间
  @Column({ type: "timestamp", nullable: true })
  lastNotificationAt: Date;
}

二、核心:生成用户时区的随机通知时间

在用户当地时间的指定窗口(比如10:00-22:00)内生成随机时间,再转成UTC时间用于调度。用date-fns-tz处理时区转换:

import { zonedTimeToUtc, utcToZonedTime, getTimezones } from "date-fns-tz";
import { addDays, setHours, setMinutes, randomInt } from "date-fns";

// 生成用户次日的随机通知UTC时间
export function generateRandomNotificationTime(user: User): Date {
  if (!user.isNotificationEnabled) throw new Error("用户已关闭通知");
  if (!getTimezones().includes(user.timezone)) throw new Error("无效时区");

  // 目标窗口:用户当地时间10:00 - 21:59
  const nextDay = addDays(new Date(), 1);
  const localMinTime = setHours(setMinutes(nextDay, 0), 10);
  const localMaxTime = setHours(setMinutes(nextDay, 59), 21);

  // 转换为用户时区的时间,计算随机分钟数
  const zonedMin = utcToZonedTime(localMinTime, user.timezone);
  const zonedMax = utcToZonedTime(localMaxTime, user.timezone);
  const randomMinuteOffset = randomInt(0, (zonedMax.getTime() - zonedMin.getTime()) / (1000 * 60));
  
  const randomZonedTime = new Date(zonedMin.getTime() + randomMinuteOffset * 60 * 1000);
  // 转回UTC时间返回
  return zonedTimeToUtc(randomZonedTime, user.timezone);
}

三、定时任务调度(node-schedule)

用node-schedule每日批量生成次日的通知任务,或者每分钟扫描待通知用户(大用户量推荐后者):

import schedule from "node-schedule";
import { getRepository } from "typeorm";
import { User } from "./entities/User";
import { sendNotification } from "./notification-service";

// 每日凌晨1点(UTC)为所有用户生成次日通知任务
schedule.scheduleJob("0 0 1 * * *", async () => {
  const userRepo = getRepository(User);
  const users = await userRepo.find({ where: { isNotificationEnabled: true } });

  for (const user of users) {
    try {
      const notifyTime = generateRandomNotificationTime(user);
      // 为该用户安排通知任务
      schedule.scheduleJob(notifyTime, async () => {
        await sendNotification(user.id, "该发布帖子啦!");
        await userRepo.update(user.id, { lastNotificationAt: new Date() });
      });
    } catch (err) {
      console.error(`用户${user.id}任务创建失败:`, err);
    }
  }
});

四、通知推送实现

示例推送函数,实际可对接FCM/APNS等服务:

// notification-service.ts
export async function sendNotification(userId: number, message: string): Promise<void> {
  // 替换为实际推送逻辑,比如调用FCM API
  console.log(`向用户${userId}推送通知: ${message}`);
  // 示例FCM调用:
  // await fetch("https://fcm.googleapis.com/fcm/send", {
  //   method: "POST",
  //   headers: { 
  //     "Authorization": "key=YOUR_FCM_SECRET", 
  //     "Content-Type": "application/json" 
  //   },
  //   body: JSON.stringify({ 
  //     to: userDeviceToken, 
  //     notification: { title: "发布提醒", body: message } 
  //   })
  // });
}

五、Express接口:用户设置时区

提供接口让用户更新时区:

import express from "express";
import { getRepository } from "typeorm";
import { User } from "./entities/User";
import { getTimezones } from "date-fns-tz";

const router = express.Router();

router.post("/api/user/timezone", async (req, res) => {
  const { userId, timezone } = req.body;
  const userRepo = getRepository(User);

  try {
    if (!getTimezones().includes(timezone)) {
      return res.status(400).json({ error: "无效时区格式" });
    }

    await userRepo.update(userId, { timezone });
    res.json({ success: true, message: "时区更新成功" });
  } catch (err) {
    res.status(500).json({ error: "更新失败" });
  }
});

export default router;

关键注意事项

  • 时区校验:必须用IANA标准时区,避免转换错误。
  • 服务重启恢复:重启后需重新生成未执行的任务,可通过lastNotificationAt判断是否已发送当日通知。
  • 大用户量优化:用户数较多时,建议改用每分钟扫描一次UTC时间匹配的用户,避免创建大量定时任务。

内容的提问来源于stack exchange,提问作者jakek05

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 19:05:15