起因,正在寻求给对象的定亲礼物的时候,一脸苦恼(选择困难症,送花有感觉比较low)打开掘金时发现某大佬写了一个模版推送,作为前端的我,那么就开始动起来
微信服务号
我们发现服务号个人无法申请,所以申请了测试公众号(实现模版推送)
egg快速入门
- 创建项目(一顿拍回车)
$ 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}}