feat: 完成定时卡片发送逻辑
This commit is contained in:
parent
9bf37ce9d3
commit
dddb66cc64
10
README.md
10
README.md
@ -19,7 +19,9 @@
|
||||
|
||||
[ ] 用药提醒的卡片模板,确认、取消、延迟
|
||||
|
||||
[ ] 看看提醒统计的卡片需要什么数据,应该是需要单建统计数据表,看看能不能融合进Remind表,加个needRepeat字段
|
||||
[ ] 看看提醒统计的卡片需要什么数据,应该是需要单建统计数据表,看看能不能融合进Remind表,加个needReply字段
|
||||
|
||||
[ ] 通过getInfo指令,在对话里返回请求的数据
|
||||
|
||||
## 待定TODO
|
||||
|
||||
@ -48,7 +50,7 @@ interface Remind {
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 所有者信息,绑定用户表的open_id
|
||||
* 所有者信息,绑定用户表的id
|
||||
*/
|
||||
owner: string;
|
||||
/**
|
||||
@ -68,6 +70,10 @@ interface Remind {
|
||||
* 是否需要回复,不需要回复的也不会重复提醒
|
||||
*/
|
||||
needReply: boolean;
|
||||
/**
|
||||
* 延迟时间
|
||||
*/
|
||||
delayTime: number;
|
||||
/**
|
||||
* 卡片信息,用于绘制初始卡片、确认卡片、取消卡片、延迟卡片
|
||||
*/
|
||||
|
@ -1,9 +1,14 @@
|
||||
|
||||
const schedule = require('node-schedule');
|
||||
const { resetAccessToken } = require('./accessToken');
|
||||
const { sendCurrTimeReminds } = require('../schedule/remind');
|
||||
|
||||
exports.initSchedule = async () => {
|
||||
// 定时任务,每15分钟刷新一次token
|
||||
schedule.scheduleJob('*/15 * * * *', resetAccessToken);
|
||||
// 定时任务,每分钟检查一次是否有需要提醒的卡片
|
||||
schedule.scheduleJob('* * * * *', sendCurrTimeReminds);
|
||||
// 立即执行一次
|
||||
resetAccessToken()
|
||||
sendCurrTimeReminds()
|
||||
}
|
||||
|
@ -1,30 +1,59 @@
|
||||
const { genCard } = require("../utils/genCard")
|
||||
const { getCurrRemind } = require("../utils/pb")
|
||||
const { sendCard } = require("../utils/sendCard")
|
||||
|
||||
const { getDelayedRemindTime, getNextRemindTime } = require("../utils/nextRemindTime")
|
||||
const { getCurrRemind, updateNextRemindTime, getPendingRemindRecord, updateRemindRecord, createRemindRecord } = require("../utils/pb")
|
||||
const { sendCard, updateCard } = require("../utils/sendCard")
|
||||
const { getNowStr } = require("../utils/time")
|
||||
/**
|
||||
* 更新上一个pending状态的卡片至delayed
|
||||
*/
|
||||
const updateLastPendingCardToDelayed = async (remind) => {
|
||||
const record = await getPendingRemindRecord(remind.id)
|
||||
if (!record) return
|
||||
// 当前时间即为回复时间
|
||||
const interactTime = getNowStr()
|
||||
// 创建delayed状态的卡片
|
||||
const card = genCard(remind, 'delayed', null, interactTime)
|
||||
// 更新飞书的卡片
|
||||
await updateCard(record.messageId, card)
|
||||
// 更新remindRecord
|
||||
const newRecord = {
|
||||
...record,
|
||||
status: 'delayed',
|
||||
interactTime,
|
||||
}
|
||||
await updateRemindRecord(record.id, newRecord)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理提醒。包括:创建卡片、更新下一次提醒时间,更新上一个padding状态的卡片至delayed
|
||||
* 处理提醒。包括:创建卡片、更新下一次提醒时间,更新上一个pending状态的卡片至delayed
|
||||
* @param {Remind} remind 提醒信息
|
||||
*/
|
||||
const manageRemind = async (remind) =>{
|
||||
const manageRemind = async (remind) => {
|
||||
// 更新上一个pending状态的卡片至delayed
|
||||
await updateLastPendingCardToDelayed(remind)
|
||||
// 生成卡片
|
||||
const card = genCard(remind)
|
||||
const card = genCard(remind, 'pending', null, null)
|
||||
// 发送卡片
|
||||
await sendCard(card)
|
||||
const messageId = await sendCard(remind.subscriber.type, remind.subscriber.id, 'interactive', card)
|
||||
// 创建remindRecord
|
||||
await createRemindRecord(remind.id, messageId)
|
||||
// 获取下一次提醒时间,不需要回复的直接时下一次提醒时间,需要回复的则是当前时间延后10min
|
||||
const nextRemindTime = remind.needReply ? getDelayedRemindTime(remind.delayTime) : getNextRemindTime(remind)
|
||||
// 更新下一次提醒时间
|
||||
await updateNextRemindTime(remind)
|
||||
// 更新上一个padding状态的卡片至delayed
|
||||
await updatePrevPaddingCard(remind)
|
||||
await updateNextRemindTime(remind.id, nextRemindTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理当前分钟需要处理的卡片
|
||||
* 发送提醒、更新上一个padding状态的卡片至delayed
|
||||
* 发送提醒、更新上一个pending状态的卡片至delayed
|
||||
*/
|
||||
const sendCurrTimeReminds = async () => {
|
||||
module.exports.sendCurrTimeReminds = async () => {
|
||||
const remindList = await getCurrRemind()
|
||||
// 没有需要提醒的卡片
|
||||
if (!remindList.length) return
|
||||
// 处理提醒
|
||||
for (const remind of remindList) {
|
||||
await manageRemind(remind)
|
||||
}
|
||||
}
|
8
typings.d.ts
vendored
8
typings.d.ts
vendored
@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
/**
|
||||
* 提醒列表
|
||||
*/
|
||||
@ -7,7 +9,7 @@ interface Remind {
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 所有者信息,绑定用户表的open_id
|
||||
* 所有者信息,绑定用户表的id
|
||||
*/
|
||||
owner: string;
|
||||
/**
|
||||
@ -27,6 +29,10 @@ interface Remind {
|
||||
* 是否需要回复,不需要回复的也不会重复提醒
|
||||
*/
|
||||
needReply: boolean;
|
||||
/**
|
||||
* 延迟时间
|
||||
*/
|
||||
delayTime: number;
|
||||
/**
|
||||
* 卡片信息,用于绘制初始卡片、确认卡片、取消卡片、延迟卡片
|
||||
*/
|
||||
|
147
utils/genCard.js
147
utils/genCard.js
@ -160,7 +160,7 @@ const genRemindInfo = (
|
||||
* @param {*} interactInfo 交互信息
|
||||
* @param {*} interactTime 交互时间,在上层已经转成 yyyy-MM-dd HH:mm 格式了
|
||||
*/
|
||||
const genCard = (
|
||||
module.exports.genCard = (
|
||||
remindInfo,
|
||||
cardType,
|
||||
interactInfo,
|
||||
@ -277,151 +277,8 @@ const genCard = (
|
||||
if (remindInfoDom) {
|
||||
elements.push(remindInfoDom);
|
||||
}
|
||||
return {
|
||||
return JSON.stringify({
|
||||
elements,
|
||||
header,
|
||||
};
|
||||
};
|
||||
|
||||
const testGenRemindTimeText = () => {
|
||||
const testCases = [
|
||||
{
|
||||
frequency: "single",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "05-01",
|
||||
expect: "05-01 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "daily",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "",
|
||||
expect: "每天 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "weekly",
|
||||
time: "10:00",
|
||||
dayOfWeek: 1,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "",
|
||||
expect: "周一 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "monthly",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 1,
|
||||
dayOfYear: "",
|
||||
expect: "每月1日 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "yearly",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "05-03",
|
||||
expect: "每年05-03 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "workday",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "",
|
||||
expect: "工作日 10:00",
|
||||
},
|
||||
{
|
||||
frequency: "holiday",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "",
|
||||
expect: "节假日 10:00",
|
||||
},
|
||||
];
|
||||
testCases.forEach((testCase) => {
|
||||
const { frequency, time, dayOfWeek, dayOfMonth, dayOfYear, expect } =
|
||||
testCase;
|
||||
const result = genRemindTimeText(
|
||||
frequency,
|
||||
time,
|
||||
dayOfWeek,
|
||||
dayOfMonth,
|
||||
dayOfYear
|
||||
);
|
||||
if (result !== expect) {
|
||||
console.log(
|
||||
`genRemindTimeText failed, expect: ${expect}, result: ${result}`
|
||||
);
|
||||
} else {
|
||||
console.log("genRemindTimeText success");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const testGenCard = () => {
|
||||
const remindInfo = {
|
||||
cardInfo: {
|
||||
title: "🎉 帝君喊你吃药!",
|
||||
imageKey: "img_v2_a74b2092-9f61-44d2-ab4c-45f9c4e083el",
|
||||
content: "**本次包含药品:**\n美沙拉嗪 \\* 1\n复方谷氨酰胺 \\* 4",
|
||||
confirmText: "我吃完了",
|
||||
delayText: "稍后提醒",
|
||||
cancelText: "淦,忘吃了",
|
||||
},
|
||||
templateInfo: null,
|
||||
needReply: true,
|
||||
frequency: "single",
|
||||
time: "10:00",
|
||||
dayOfWeek: 0,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "05-01",
|
||||
}
|
||||
const cardType = "pending";
|
||||
const interactInfo = null;
|
||||
const interactTime = null;
|
||||
const expect = {"elements":[{"alt":{"content":"","tag":"plain_text"},"img_key":"img_v2_a74b2092-9f61-44d2-ab4c-45f9c4e083el","tag":"img"},{"tag":"div","text":{"content":"**本次包含药品:**\n美沙拉嗪 \\* 1\n复方谷氨酰胺 \\* 4","tag":"lark_md"}},{"tag":"action","actions":[{"tag":"button","text":{"tag":"plain_text","content":"我吃完了"},"type":"primary","value":{"result":"confirmed","text":"我吃完了"}},{"tag":"button","text":{"tag":"plain_text","content":"稍后提醒"},"type":"default","value":{"result":"delayed","text":"稍后提醒"}},{"tag":"button","text":{"tag":"plain_text","content":"淦,忘吃了"},"type":"danger","value":{"result":"canceled","text":"淦,忘吃了"}}]}],"header":{"title":{"content":"🎉 帝君喊你吃药!","tag":"plain_text"},"template":"blue"}}
|
||||
const result = genCard(remindInfo, cardType, interactInfo, interactTime);
|
||||
if (JSON.stringify(result) !== JSON.stringify(expect)) {
|
||||
console.log(`genCard failed, expect: ${JSON.stringify(expect)}, result: ${JSON.stringify(result)}`);
|
||||
} else {
|
||||
console.log("genCard success");
|
||||
}
|
||||
|
||||
const remindInfo2 = {
|
||||
cardInfo: {
|
||||
title: "🎉 帝君喊你吃药!",
|
||||
imageKey: "img_v2_a74b2092-9f61-44d2-ab4c-45f9c4e083el",
|
||||
content: "**本次包含药品:**\n美沙拉嗪 \\* 1\n复方谷氨酰胺 \\* 4",
|
||||
confirmText: "我吃完了",
|
||||
delayText: "稍后提醒",
|
||||
cancelText: "淦,忘吃了",
|
||||
},
|
||||
templateInfo: null,
|
||||
needReply: true,
|
||||
frequency: "weekly",
|
||||
time: "10:00",
|
||||
dayOfWeek: 1,
|
||||
dayOfMonth: 0,
|
||||
dayOfYear: "",
|
||||
}
|
||||
const cardType2 = "confirmed";
|
||||
const interactInfo2 = {
|
||||
result: "confirmed",
|
||||
text: "我吃完了",
|
||||
}
|
||||
const interactTime2 = "05-01 10:00";
|
||||
console.log(JSON.stringify(genCard(remindInfo2, cardType2, interactInfo2, interactTime2)));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
genCard,
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
testGenRemindTimeText();
|
||||
testGenCard();
|
||||
}
|
@ -57,7 +57,7 @@ const getNextday = (isWorkday = true) => {
|
||||
* @param {Remind} remind 提醒信息
|
||||
* @returns {string} 下次提醒时间, 格式为yyyy-MM-dd HH:mm
|
||||
*/
|
||||
const getNextRemindTime = (remind) => {
|
||||
module.exports.getNextRemindTime = (remind) => {
|
||||
const { frequency, time, dayOfWeek, dayOfMonth, dayOfYear } = remind;
|
||||
const now = new Date();
|
||||
const nowDay = now.getDay();
|
||||
@ -66,7 +66,6 @@ const getNextRemindTime = (remind) => {
|
||||
const nowYear = now.getFullYear();
|
||||
const nowHour = now.getHours();
|
||||
const nowMinute = now.getMinutes();
|
||||
console.log(nowDay, nowDate, nowMonth, nowYear, nowHour, nowMinute)
|
||||
|
||||
if (frequency === 'daily') {
|
||||
// 判断当前时间是否已经过了今天的提醒时间
|
||||
@ -134,3 +133,18 @@ const getNextRemindTime = (remind) => {
|
||||
return `${getNextday(false)} ${time}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取delayTime分钟delay之后的提醒时间
|
||||
* @param {number} delayTime 延迟时间, 单位为分钟, 1 <= delayTime
|
||||
* @returns {string} delay的提醒时间, 格式为yyyy-MM-dd HH:mm
|
||||
*/
|
||||
module.exports.getDelayedRemindTime = (delayTime) => {
|
||||
const nextRemindTime = new Date(new Date().getTime() + delayTime * 60 * 1000)
|
||||
const nextRemindYear = nextRemindTime.getFullYear();
|
||||
const nextRemindMonth = nextRemindTime.getMonth() + 1;
|
||||
const nextRemindDate = nextRemindTime.getDate();
|
||||
const nextRemindHour = nextRemindTime.getHours();
|
||||
const nextRemindMinute = nextRemindTime.getMinutes();
|
||||
return `${nextRemindYear}-${nextRemindMonth}-${nextRemindDate} ${nextRemindHour}:${nextRemindMinute}`
|
||||
}
|
47
utils/pb.js
47
utils/pb.js
@ -32,4 +32,49 @@ module.exports.getCurrRemind = async () => {
|
||||
filter: `nextRemindTime = "${nowStr}" && enabled = true`,
|
||||
})
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定Remind的下次提醒时间
|
||||
* @param {Remind} remindId 提醒信息Id
|
||||
* @param {string} nextRemindTime 下次提醒时间
|
||||
*/
|
||||
module.exports.updateNextRemindTime = async (remindId, nextRemindTime) => {
|
||||
await pb.collection('remind').update(remindId, { nextRemindTime })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定remind的pending状态的remindRecord
|
||||
* @param {string} remindId remind的id
|
||||
* @returns {RemindRecord | null} remindRecord
|
||||
*/
|
||||
module.exports.getPendingRemindRecord = async (remindId) => {
|
||||
const record = await pb.collection('remindRecord').getFirstListItem(
|
||||
`remindId = "${remindId}" && status = "pending"`
|
||||
)
|
||||
return record
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建remindRecord
|
||||
*/
|
||||
module.exports.createRemindRecord = async (remindId, messageId) => {
|
||||
const remindRecord = {
|
||||
remindId,
|
||||
messageId,
|
||||
status: 'pending',
|
||||
remindTime: getNowStr(),
|
||||
interactTime: '',
|
||||
result: '',
|
||||
}
|
||||
await pb.collection('remindRecord').create(remindRecord)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改指定remindRecord
|
||||
* @param {string} id remindRecord的id
|
||||
* @param {RemindRecord} record remindRecord的信息
|
||||
*/
|
||||
module.exports.updateRemindRecord = async (id, record) => {
|
||||
await pb.collection('remindRecord').update(id, record)
|
||||
}
|
||||
|
@ -3,6 +3,14 @@ const {
|
||||
getTenantAccessToken
|
||||
} = require('./pb');
|
||||
|
||||
/**
|
||||
* 发送卡片
|
||||
* @param {string} receive_id_type 消息接收者id类型 open_id/user_id/union_id/email/chat_id
|
||||
* @param {string} receive_id 消息接收者的ID,ID类型应与查询参数receive_id_type 对应
|
||||
* @param {string} msg_type 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user
|
||||
* @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容
|
||||
* @returns {string} 消息id
|
||||
*/
|
||||
module.exports.sendCard = async (receive_id_type, receive_id, msg_type, content) => {
|
||||
const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages?receive_id_type=${receive_id_type}`
|
||||
const tenant_access_token = await getTenantAccessToken();
|
||||
@ -18,4 +26,27 @@ module.exports.sendCard = async (receive_id_type, receive_id, msg_type, content)
|
||||
})
|
||||
const data = await res.json();
|
||||
console.log('sendCard success', data);
|
||||
return data.data.message_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新卡片
|
||||
* @param {string} message_id 消息id
|
||||
* @param {*} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容
|
||||
*/
|
||||
module.exports.updateCard = async (message_id, content) => {
|
||||
const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`
|
||||
const tenant_access_token = await getTenantAccessToken();
|
||||
const header = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Authorization': `Bearer ${tenant_access_token}`
|
||||
}
|
||||
const body = { content }
|
||||
const res = await fetch(URL, {
|
||||
method: 'PATCH',
|
||||
headers: header,
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
const data = await res.json();
|
||||
console.log('updateCard success', data);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user