微信模版推送之送女友小礼物

626 阅读4分钟

起因,正在寻求给对象的定亲礼物的时候,一脸苦恼(选择困难症,送花有感觉比较low)打开掘金时发现某大佬写了一个模版推送,作为前端的我,那么就开始动起来

微信服务号

我们发现服务号个人无法申请,所以申请了测试公众号(实现模版推送)

egg快速入门

eggjs.org/zh-cn/intro…

  • 创建项目(一顿拍回车)
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
  • 启动项目
$ npm run dev
$ open http://localhost:7001
创建服务(service)
  • 获取token
async getToken() {
  const {ctx, app} = this;
  const result = await ctx.curl(
    `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${app.config.appID}&secret=${app.config.appsecret}`,
    {
      dataType: "json",
      method: "GET",
    }
  );
  ctx.logger.info("getToken: %j", app.config.appID, app.config.appsecret);

  if (result.status === 200) {
    ctx.logger.info("getToken: %j", result.data);
    return result.data.access_token;
  }
  return result;
}
  • 发送消息到微信
async sendWeChat(token, openid, templateId, data) {
  const {ctx} = this;
  const result = await ctx.curl(
    `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`,
    {
      dataType: "json",
      headers: {
        "content-type": "application/json",
      },
      method: "POST",
      data: JSON.stringify({
        touser: openid,
        template_id: templateId,
        topcolor: "#FF0000",
        data,
      }),
    }
  );
  return result;
}
  • 初始化模版数据
async initTemplateData() {
  const dateTime = this.getDatetime();
  const loveDay = this.getLoveDay();
  const wageDay = this.getWageDay();
  const birthday = this.getbirthday();
  const oneSentence = await this.getOneSentence();
  const weather = await this.getWeather();
  return {
    dateTime: {
      value: dateTime + " " + weather.city,
      color: "#ef5b9c",
    },
    love: {
      value: loveDay,
      color: "#f15b6c",
    },
    wage: {
      value: wageDay,
      color: "#f8aba6",
    },
    birthday: {
      value: birthday,
      color: "#f69c9f",
    },
    message: {
      value: oneSentence,
      color: "#ca8687",
    },
    tem: {
      value: weather.tem,
      color: "#deab8a",
    },
    tem1: {
      value: weather.tem1,
      color: "#fedcbd",
    },
    tem2: {
      value: weather.tem2,
      color: "#d64f44",
    },
    win: {
      value: `${weather.win} ${weather.win_speed}`,
      color: "#444693",
    },
    air: {
      value: `${weather.air_level} ${weather.air_tips}`,
      color: "#2b4490",
    },
    chuanyi: {
      value: weather.zhishu.chuanyi.tips,
      color: "#2a5caa",
    },
    daisan: {
      value: weather.zhishu.daisan.tips,
      color: "#f58220",
    },
    ganmao: {
      value: weather.zhishu.ganmao.tips,
      color: "#843900",
    },
    ziwaixian: {
      value: weather.zhishu.ziwaixian.tips,
      color: "#6a6da9",
    },
  };
}
  • 初始化每日推荐数据
initRecommendData() {
  const dateTime = this.getDatetime();
  const food = this.getDayEat();
  const timeColors = ["#cc99090", "#9933cc", "#00cc99", "#990033", "#cc33cc"];
  const foodColors = [
    "#ff0066",
    "#cc66ff",
    "#9900ff",
    "#ffff33",
    "#ff3300",
    "#99ffff",
  ];
  return {
    dateTime: {
      value: dateTime,
      color: timeColors[Math.floor(Math.random() * timeColors.length)],
    },
    food: {
      value: food,
      color: foodColors[Math.floor(Math.random() * foodColors.length)],
    },
    emojiTop: {
      value: "🧡💛💚💙💜🖤🖤💜💙💚💛🧡",
    },
    emojiFooter: {
      value: "🍎🍏🍐🍑🍒🍓🍓🍒🍑🍐🍏🍎",
    },
  };
}
  • 初始化工资上交提醒数据
initWageTipsData() {
  const colors = [
    "#ff0066",
    "#cc66ff",
    "#9900ff",
    "#ffff33",
    "#ff3300",
    "#99ffff",
  ];
  const wageMessage = "宝贝,今天我发工资啦,请注意查收哟!!!";
  return {
    wageMessage: {
      value: wageMessage,
      color: colors[Math.floor(Math.random() * colors.length)],
    },
    emoji: {
      value: "🧡💛💚💙💜🖤🖤💜💙💚💛🧡",
    },
  };
}
  • 获取当前时间
getDatetime() {
  const week = {
    1: "星期一",
    2: "星期二",
    3: "星期三",
    4: "星期四",
    5: "星期五",
    6: "星期六",
    0: "星期日",
  };
  return (
    moment().format("YYYY年MM月DD日 h:mm:ss a") +
    " " +
    week[moment().weekday()]
  );
}
  • 获取我们相爱多少天
getLoveDay() {
  const {app} = this;
  const loveDay = app.config.loveDay;
  return moment(moment().format("YYYY-MM-DD")).diff(loveDay, "days");
}
  • 获取离发工资还有多少天
getWageDay() {
  const {app} = this;
  // 多少号发工资
  const wageDay = Number(app.config.wageDay);

  // 本月多少天
  const curentDays = Number(moment().daysInMonth());

  // 当前时间是本月的多少天

  const curentDay = Number(moment().date());

  // 发工资的日子减去当前的日子

  const flag = wageDay - curentDay;

  return flag >= 0 ? flag : curentDays - curentDay + wageDay;
}
  • 获取生日距离现在还有多少天
getbirthday() {
  const {app} = this;
  // 判断平年还是闰年
  // const isLeapYear = moment([moment().year()]).isLeapYear();
  // 如果今年的生日时间 减去当前时间 等于负数那么证明今年生日过了 那么就计算明年生日到今天的日期
  let day = Math.ceil(
    (moment(moment().year() + "-" + app.config.birthday).valueOf() -
     moment().valueOf()) /
    1000 /
    60 /
    60 /
    24
  );
  if (day < 0) {
    day = Math.ceil(
      (moment(moment().year() + 1 + "-" + app.config.birthday).valueOf() -
       moment().valueOf()) /
      1000 /
      60 /
      60 /
      24
    );
  }
  return day;
}
  • 获取每日一句
async getOneSentence() {
  const {ctx} = this;
  const result = await ctx.curl("https://v1.hitokoto.cn/", {
    method: "GET",
    dataType: "json",
    headers: {
      "content-type": "application/json",
    },
  });
  ctx.logger.info("getOneSentence %j:", result.data);

  if (result.status === 200) {
    return result.data.hitokoto;
  }

  return "今日只有我爱你!";
}
  • 获取天气
async getWeather() {
  const {ctx} = this;
  const result = await ctx.curl("https://v0.yiketianqi.com/api", {
    method: "GET",
    dataType: "json",
    headers: {
      "content-type": "application/json",
    },
    data: {
      appid: "***",
      appsecret: "***",
      cityid: "***",
      version: "***",
    },
  });
  ctx.logger.info(result);
  return result.data;
}
  • 随机每日吃什么
getDayEat() {
  const menus = [
    "麻辣烫",
    "麻辣香锅",
    "饺子",
    "黄焖鸡",
    "米线",
    "炸鸡",
    "炒饭",
    "麦当劳",
    "寿司",
    "螺蛳粉",
    "粥",
    "酸菜鱼",
    "冒菜",
    "凉皮",
    "馄饨",
    "烧烤",
    "肯德基",
    "酸辣粉",
    "披萨",
    "火锅",
    "拉面",
    "汉堡",
    "兰州拉面",
    "水饺",
    "重庆小面",
    "包子",
    "煎饼果子",
    "炸酱面",
    "鸡公煲",
    "烤肉拌饭",
    "泡面",
    "煲仔饭",
    "烤冷面",
    "KFC",
    "炒面",
    "肠粉",
    "盖浇饭",
    "石锅拌饭",
    "黄焖鸡米饭",
    "汉堡王",
    "炒菜",
    "小碗菜",
    "沙县小吃",
    "咖喱饭",
    "猪脚饭",
    "华莱士",
    "牛肉面",
    "烤肉饭",
    "牛肉汤",
    "麻辣拌",
  ];
  const random = Math.floor(Math.random() * menus.length);
  return menus[random];
}
  • 定时群发任务
sendAllWeChat(openids, templateId, data, token) {
  let sends = [];
  // 定时群发任务
  openids.forEach(async (openidItem) => {
    if (openidItem.status === 0) {
      sends.push(this.sendWeChat(token, openidItem.openid, templateId, data));
    }

  });
  return Promise.all(sends);
}
  • 每日推荐发送
async sendRecommend(token, openid) {
  const {ctx, app} = this;
  const data = this.initRecommendData();
  const result = await this.sendWeChat(
    token,
    openid,
    app.config.templateIds.recommend,
    data
  );
  ctx.logger.info("sendRecommend: %j", result);
  return result;
}
  • 信息默认发送
async send(type) {
  const {ctx, app} = this;
  const {openids, templateIds} = app.config;
  const token = await this.getToken();

  // 如果参数为sendAll 则群发每日提醒 如果参数为 sendRecommend 则群发每日推荐美食
  if (type === "sendAll") {
    const data = await this.initTemplateData();
    ctx.logger.info("initTemplateData %j:", data);

    const result = await this.sendAllWeChat(
      openids,
      templateIds.dailyReminder,
      data,
      token
    );
    ctx.logger.info("sendAllWeChatAll %j:", result);
    // 如果发工资的倒计时为0推送发工资提醒
    if (data.wage.value === 0) {
      const wageTipsData = this.initWageTipsData();
      const result = await this.sendAllWeChat(
        openids,
        templateIds.wageTips,
        wageTipsData,
        token
      );
      ctx.logger.info("sendAllWeChatWageTips %j:", result);

      return result;
    }
    return result;
  } else if (type === "sendRecommend") {
    const data = await this.initRecommendData();
    ctx.logger.info("initRecommendData %j:", data);
    const result = await this.sendAllWeChat(
      openids,
      templateIds.recommend,
      data,
      token
    );
    ctx.logger.info("sendAllWeChatRecommend %j:", result);
    return result;
  }
  return "error";
}
定时任务

egg的定时任务必须在app下创建schedule文件夹

  • 每日早晨推送
const Subscription = require("egg").Subscription;

class DailyTask extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      cron: "0 30 5 * * *", // 每天的7点30分0秒执行
      // interval: 3000, // 1 分钟间隔
      type: "all", // 指定所有的 worker 都需要执行
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
    const {ctx} = this;
    const result = await ctx.service.sendmsg.send("sendAll");
    ctx.logger.info("定时任务执行消息提醒 每日提醒 结果: %j", result);
  }
}

module.exports = DailyTask;
  • 每日晚餐推送
const Subscription = require("egg").Subscription;

class DailyRecommend extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      cron: "0 0 10 * * *", // 每天的16点30分0秒执行
      // interval: 30000, // 1 分钟间隔
      type: "all", // 指定所有的 worker 都需要执行
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
    const {ctx} = this;
    const result = await ctx.service.sendmsg.send("sendRecommend");
    ctx.logger.info("定时任务执行消息提醒 每日推荐 结果: %j", result);
  }
}

module.exports = DailyRecommend;

配置添加

在egg目录中cofig配置配置自己所用数据

  const userConfig = {
    // 恋爱的日子
    loveDay: "0000-00-00",
    // 发工资的日子
    wageDay: moment().endOf("month").format("DD"),

    // 生日
    birthday: "00-00",
    openids: [
      {
        openid: "openid",
        name: "长安",
        status: 0, // 0为推送 1为不推送
      },
      {
        openid: "openid",
        name: "故里",
        status: 1,
      },
      {
        openid: "openid",
        name: "AmessZH.	",
        status: 1,
      },
    ],
    templateIds: {
      dailyReminder: "9GHyNIXKmZwOOL9Z4uQIKbLRBzMSrVIHSvhAmdcCUgk", // 每日提醒
      oneSentence: "4e1nCdyUQ7ihTlmSrsZGP9vKbCvfWwbxP-5zEa4X4hk", // 每日一句
      recommend: "oW5Apy-Z1vwvC5DU6lvv0JkZceiGkqyxFwVamp7CN9A", // 每日菜单推荐
      wageTips: "28pgPk2-NXMBHmgD-PoxeK36tK6s1zG9FBeZq4-Pdpc", // 工资上交提醒
    },
  };
  • 单独环境配置
module.exports = (appInfo) => {
  return {
    appID: "appID",
    appsecret: "appsecret",
  };
};
模版添加

微信公众号模版添加

  • 工资上交提醒
{{emoji.DATA}} {{wageMessage.DATA}} {{emoji.DATA}}
  • 每日提醒
{{dateTime.DATA}} 今日是我们相恋{{love.DATA}}天 距离工资上交还用{{wage.DATA}}天 距离你的生日还有{{birthday.DATA}}天 当前温度{{tem.DATA}} 今日最低温度{{tem2.DATA}} 今日最高温度{{tem1.DATA}} {{win.DATA}} 空气质量{{air.DATA}} 穿衣{{chuanyi.DATA}} 带伞 {{daisan.DATA}} 感冒 {{ganmao.DATA}} 紫外线 {{ziwaixian.DATA}} 每日一句 {{message.DATA}}
  • 今日美食推荐
{{emojiTop.DATA}} 快到吃饭时间啦!!! {{dateTime.DATA}} 记得吃饭哟!!! 本次推荐菜单 {{ food.DATA }} {{emojiFooter.DATA}}
应用部署

eggjs.org/zh-cn/core/…