feat: 周报和日报迁移至本项目

This commit is contained in:
zhaoyingbo 2024-11-27 13:05:32 +00:00
parent c3b827787a
commit 754cecf35e
30 changed files with 521 additions and 88 deletions

View File

@ -12,4 +12,6 @@ LANGFUSE_BASE_URL=
# Map of apps such as '{"michat": { "app_id": "123", "app_secret": "456", "app_name": "Mi Chat" }}'
LARK_APP_MAP=
ATTACH_APP_SECRET=
ATTACH_APP_SECRET=
DATABASE_URL=

BIN
bun.lockb

Binary file not shown.

View File

@ -1,3 +0,0 @@
import { parseJsonString } from "@egg/hooks"
console.log(parseJsonString(Bun.env.LARK_APP_MAP, []), [])

13
constant/appMap.ts Normal file
View File

@ -0,0 +1,13 @@
import { parseJsonString } from "@egg/hooks"
export interface AppInfo {
app_id: string
app_secret: string
app_name: string
}
// 获取所有应用信息
export const appMap = parseJsonString(process.env.LARK_APP_MAP, {}) as Record<
string,
AppInfo
>

View File

@ -149,6 +149,29 @@ const resultReport = {
header: cardComponent.successHeader,
}
const autoReport = {
config: {
update_multi: true,
},
elements: [
{
tag: "markdown",
content: "${llmRes}",
},
{
tag: "hr",
},
cardComponent.commonNote,
],
header: {
template: "turquoise",
title: {
content: "${xIcon} ${xName} ${timeScope}",
tag: "plain_text",
},
},
}
export const functionOptionList = [
{
id: "summary-qwen-72b-instruct-int4",
@ -161,6 +184,7 @@ const cardMap = {
timeScopeSelector,
resultReport,
groupSelector,
autoReport,
}
export default cardMap

View File

@ -5,7 +5,7 @@ const functionMap = {
xIcon: "🍳",
},
groupAgent: {
xName: "Group Agent",
xName: "Mi Chat群聊助手",
xAuthor: "AI创新应用组",
xIcon: "🔥",
},

5
constant/message.ts Normal file
View File

@ -0,0 +1,5 @@
export enum RespMessage {
hasRegistered = "本群已订阅日报,周报",
registerSuccess = "周报、日报订阅成功",
cancelSuccess = "周报、日报订阅取消成功",
}

View File

@ -1,7 +1,8 @@
import { parseJsonString } from "@egg/hooks"
import { LarkEvent } from "@egg/lark-msg-tool"
import { Lark } from "@egg/net-tool"
import { Context, LarkServer } from "../../../types"
import { Context } from "../../types"
interface Message {
user: string
@ -163,7 +164,7 @@ const getChatHistory = async (
* @param chat -
* @returns
*/
const getText = (chat: LarkServer.MessageData) => {
const getText = (chat: Lark.MessageData) => {
let { text } = parseJsonString(chat.body.content, { text: "" }) as {
text: string
}
@ -187,7 +188,7 @@ const getChatHistory = async (
* @param chat -
* @returns post
*/
const getPost = (chat: LarkServer.MessageData) => {
const getPost = (chat: Lark.MessageData) => {
const content = parseJsonString(chat.body.content, null)
if (!content) return ""
return extractTextFromJson(content)

View File

@ -1,8 +1,8 @@
import { genCardOptions, LarkEvent } from "@egg/lark-msg-tool"
import { functionOptionList } from "../../../constant/card"
import { Context, LarkServer } from "../../../types"
import llm from "../../../utils/llm"
import { functionOptionList } from "../../constant/card"
import { Context, LarkServer } from "../../types"
import llm from "../../utils/llm"
import getChatHistory from "./chatHistory"
/**
@ -290,7 +290,7 @@ const manageEventMsg = async (ctx: Context.Data) => {
// 私聊发送正常的群组选择器
if (chatType === "p2p") {
logger.info("Send group selector to p2p chat")
sendCard(genGroupSelector(ctx, innerList, { mentions }))
await sendCard(genGroupSelector(ctx, innerList, { mentions }))
return
}
// 如果是群聊,获取群聊名称并发送功能
@ -298,7 +298,7 @@ const manageEventMsg = async (ctx: Context.Data) => {
data: { name: chatName },
} = await larkService.chat.getChatInfo(rawChatId)
logger.info(`Send function selector to group chat: ${chatName}`)
sendCard(genFunctionSelector(ctx, { chatName, mentions }))
await sendCard(genFunctionSelector(ctx, { chatName, mentions }))
return
}
@ -396,11 +396,17 @@ const manageActionMsg = async (ctx: Context.Data) => {
const groupAgent = async (ctx: Context.Data) => {
const {
larkBody: { isEvent, isAction },
logger,
} = ctx
// 如果是Event则解析自然语言并发送对应的卡片
if (isEvent) return manageEventMsg(ctx)
// 如果是Action则取出用户选的值并判断是否需要继续发送表单卡片或者开始大模型推理
if (isAction) return manageActionMsg(ctx)
try {
// 如果是Event则解析自然语言并发送对应的卡片
if (isEvent) return await manageEventMsg(ctx)
// 如果是Action则取出用户选的值并判断是否需要继续发送表单卡片或者开始大模型推理
if (isAction) return await manageActionMsg(ctx)
} catch (e: any) {
logger.error(`Group agent error: ${e.message}`)
return ctx.larkCard.child("groupAgent").genErrorCard("Group agent error")
}
}
export default groupAgent

View File

@ -0,0 +1,190 @@
import { LarkService } from "@egg/net-tool"
import { appMap } from "../../constant/appMap"
import prisma from "../../prisma"
import { Context } from "../../types"
import genContext from "../../utils/genContext"
import llm from "../../utils/llm"
import { getTimeRange } from "../../utils/time"
import getChatHistory from "./chatHistory"
interface Subscription {
id: bigint
chat_id: string
robot_id: string
initiator: string
terminator: string
created_at: Date
updated_at: Date
}
/**
*
* @param {Context.Data} ctx -
* @param {string} timeScope -
* @param {Subscription} subscription -
* @returns {Promise<void>}
*/
const genReport = async (
ctx: Context.Data,
timeScope: "daily" | "weekly",
subscription: Subscription
) => {
const { logger, requestId, larkCard } = ctx
const cardGender = larkCard.child("groupAgent")
try {
const { chat_id: chatId, robot_id: robotId } = subscription
// 获取接口信息
const appInfo = appMap[robotId]
if (!appInfo) {
logger.error(`Failed to get app info for ${robotId}`)
return
}
// 组织接口
const larkService = new LarkService({
appId: appInfo.app_id,
appSecret: appInfo.app_secret,
requestId,
})
// 获取时间范围
const { startTime, endTime } = getTimeRange(timeScope)
// 计时开始
const processStart = Date.now()
// 获取聊天记录
const chatHistory = await getChatHistory(
{ larkService, logger } as Context.Data,
{
chatId,
startTime,
endTime,
}
)
if (chatHistory.length === 0) {
logger.info(`No message in chat ${chatId}`)
return
}
// 使用大模型总结消息
const llmRes = await llm.invoke(
`${timeScope}Summary`,
{
chatHistory: JSON.stringify(chatHistory),
time: new Date().toLocaleString("zh-CN", {
timeZone: "Asia/Shanghai",
}),
},
requestId
)
// 计时结束
const processEnd = Date.now()
const processingTime = ((processEnd - processStart) / 1000).toFixed(2)
logger.info(
`LLM takes time: ${processingTime}s, see detail: http://langfuse.ai.srv/project/cm1j2tkj9001gukrgdvc1swuw/sessions/${requestId}`
)
// 发送卡片消息
await larkService.message.sendCard2Chat(
chatId,
cardGender.genCard("autoReport", {
llmRes,
timeScope: timeScope === "daily" ? "今日日报" : "本周周报",
})
)
// 记录发送的卡片
await prisma.chat_agent_message_log.create({
data: {
subscription_id: subscription.id,
initiator: subscription.initiator,
langfuse_link: `http://langfuse.ai.srv/project/cm1j2tkj9001gukrgdvc1swuw/sessions/${requestId}`,
},
})
} catch (error: any) {
logger.error(
`Failed to summarize chat ${subscription.chat_id}: ${error.message}`
)
}
}
/**
*
* @returns {Promise<void>}
*/
const genAllReport = async (timeScope: "daily" | "weekly" = "daily") => {
const ctx = await genContext(new Request(""))
const { logger } = ctx
try {
// 获取全部需要自动总结的群组
const subscriptionList =
await prisma.chat_agent_summary_subscription.findMany({
where: {
terminator: "",
},
})
if (subscriptionList.length === 0) {
logger.info("No group needs to be summarized")
return
}
// 一个一个群组的总结,避免触发频率限制
for (const subscription of subscriptionList) {
await genReport(ctx, timeScope, subscription)
}
} catch (e: any) {
logger.error(`Auto summary error: ${e.message}`)
}
}
/**
*
* @param {Context.Data} ctx -
* @returns {Promise<void>}
*/
const gen4Test = async (ctx: Context.Data, timeScope: "daily" | "weekly") => {
const {
logger,
larkCard,
larkService,
larkBody: { chatId },
} = ctx
try {
logger.info(`timeScope: ${timeScope}`)
// 获取需要总结的chatId
if (!chatId) {
logger.error("Invalid request body")
return
}
// 获取订阅信息
const subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: chatId,
terminator: "",
},
}
)
// 没有订阅信息
if (!subscription) {
logger.error(`No subscription found for chat ${chatId}`)
await larkService.message.sendCard2Chat(
chatId,
larkCard.genErrorCard("本群未订阅日报、周报")
)
return
}
// 总结
await genReport(ctx, timeScope, subscription)
} catch (error: any) {
logger.error(`Failed to summarize chat ${chatId}: ${error.message}`)
}
}
const report = {
genReport,
genAllReport,
gen4Test,
}
export default report

View File

@ -0,0 +1,120 @@
import { RespMessage } from "../../constant/message"
import prisma from "../../prisma"
import { Context } from "../../types"
/**
*
* @returns
*/
const subscribe = async ({
app,
larkService,
logger,
larkBody,
larkCard,
}: Context.Data) => {
try {
const cardGender = larkCard.child("groupAgent")
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 先查询是否已经存在订阅
const subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: larkBody.chatId,
terminator: "",
},
}
)
// 如果已经存在订阅,则返回已经注册过了
if (subscription) {
logger.info(`chatId: ${larkBody.chatId} has been registered`)
// 发送已经注册过的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.hasRegistered)
)
return
}
// 注册订阅
await prisma.chat_agent_summary_subscription.create({
data: {
chat_id: larkBody.chatId,
robot_id: app,
initiator: larkBody.userId,
},
})
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.registerSuccess)
)
} catch (e: any) {
logger.error(`Subscribe error: ${e.message}`)
}
}
/**
*
* @returns
*/
const unsubscribe = async ({
logger,
larkBody,
larkService,
larkCard,
}: Context.Data) => {
try {
const cardGender = larkCard.child("groupAgent")
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 查找现有的订阅
const subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: larkBody.chatId,
terminator: "",
},
}
)
// 如果没有找到订阅,则返回错误
if (!subscription) {
logger.info(`chatId: ${larkBody.chatId} has not been registered`)
// 发送已经取消订阅的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.cancelSuccess)
)
return
}
// 更新订阅,设置终止者和终止时间
await prisma.chat_agent_summary_subscription.update({
where: {
id: subscription.id,
},
data: {
terminator: larkBody.userId,
},
})
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.cancelSuccess)
)
} catch (e: any) {
logger.error(`Unsubscribe error: ${e.message}`)
}
}
const subscription = {
subscribe,
unsubscribe,
}
export default subscription

View File

@ -2,10 +2,16 @@ FROM micr.cloud.mioffice.cn/zhaoyingbo/bun:alpine-cn
WORKDIR /app
COPY . .
COPY package*.json ./
COPY bun.lockb ./
RUN bun install
COPY . .
RUN bunx prisma generate
EXPOSE 3000
CMD ["bun", "run", "start"]
CMD ["bun", "start"]

View File

@ -0,0 +1,16 @@
version: "3.8"
services:
mysql:
image: micr.cloud.mioffice.cn/zhaoyingbo/mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: testdb
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:

View File

@ -1,5 +1,6 @@
import logger from "@egg/logger"
import prisma from "./prisma"
import { manageBotReq } from "./routes/bot"
import { manageMessageReq } from "./routes/message"
import { manageMicroAppReq } from "./routes/microApp"
@ -43,7 +44,18 @@ const server = Bun.serve({
return genResp.serverError(error.message || "server error")
}
},
error(error) {
logger.error(`Error: ${error}`)
logger.error(`Stack: ${error.stack}`)
return new Response("Internal Error", { status: 500 })
},
port: 3000,
})
logger.info(`Listening on ${server.hostname}:${server.port}`)
// 关闭数据库连接
process.on("SIGINT", async () => {
await prisma.$disconnect()
process.exit(0)
})

View File

@ -37,12 +37,13 @@
},
"dependencies": {
"@egg/hooks": "^1.2.0",
"@egg/lark-msg-tool": "^1.15.1",
"@egg/lark-msg-tool": "^1.17.0",
"@egg/logger": "^1.6.0",
"@egg/net-tool": "^1.16.1",
"@egg/path-tool": "^1.4.1",
"@langchain/core": "^0.3.19",
"@langchain/openai": "^0.3.14",
"@prisma/client": "^5.22.0",
"joi": "^17.13.3",
"langfuse-langchain": "^3.31.0",
"node-schedule": "^2.1.1",

5
prisma/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export default prisma

31
prisma/schema.prisma Normal file
View File

@ -0,0 +1,31 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model chat_agent_summary_subscription {
id BigInt @id @default(autoincrement()) // 摘要订阅 ID
chat_id String @default("") // 关联的聊天 ID
robot_id String @default("") // 机器人 ID
initiator String @default("") // 发起者 ID
terminator String @default("") // 终止者 ID
created_at DateTime @default(now()) // 创建时间
updated_at DateTime @updatedAt // 更新时间
}
model chat_agent_message_log {
id BigInt @id @default(autoincrement()) // 消息日志 ID
subscription_id BigInt @default(0) // 关联的摘要订阅 ID
initiator String @default("") // 发起者 ID
langfuse_link String @default("") // Langfuse 日志
}

View File

@ -1,5 +1,5 @@
import groupAgent from "../../controller/groupAgent"
import { Context } from "../../types"
import groupAgent from "./groupAgent"
const GROUP_MAP = {
groupAgent,

View File

@ -1,24 +1,9 @@
import { LarkBody } from "@egg/lark-msg-tool"
import tempMap from "../../constant/template"
import groupAgent from "../../controller/groupAgent"
import report from "../../controller/groupAgent/report"
import subscription from "../../controller/groupAgent/subscription"
import { Context } from "../../types"
import createKVTemp from "../sheet/createKVTemp"
import groupAgent from "./groupAgent"
/**
* P2P或者群聊并且艾特了机器人
* @param larkBody
*/
const getIsP2pOrGroupAtBot = async (
larkBody: LarkBody,
appInfo: Context.APP_INFO
): Promise<boolean> => {
const isP2p = larkBody.chatType === "p2p"
const isAtBot = larkBody.mentions?.some?.(
(mention) => mention.name === appInfo.app_name
)
return Boolean(isP2p || isAtBot)
}
/**
*
@ -38,7 +23,7 @@ const filterIllegalMsg = async ({
if (!chatId) return true
// 非私聊和群聊中艾特机器人的消息不处理
if (!(await getIsP2pOrGroupAtBot(larkBody, appInfo))) {
if (!larkBody.isP2P && !larkBody.isAtBot(appInfo.app_name)) {
return true
}
@ -114,7 +99,7 @@ const manageCMDMsg = (ctx: Context.Data) => {
logger,
larkService,
attachService,
larkBody: { msgText, chatId, chatType },
larkBody: { msgText, chatId, isInGroup },
} = ctx
logger.info(`bot req text: ${msgText}`)
@ -126,29 +111,29 @@ const manageCMDMsg = (ctx: Context.Data) => {
}
// 仅限群组功能
if (chatType === "group") {
if (isInGroup) {
// 注册群组
if (msgText === "开启日报、周报") {
logger.info(`bot command is register, chatId: ${chatId}`)
attachService.groupAgent(app, body, "register")
subscription.subscribe(ctx)
return
}
// 注销群组
if (msgText === "关闭日报、周报") {
logger.info(`bot command is unregister, chatId: ${chatId}`)
attachService.groupAgent(app, body, "unregister")
subscription.unsubscribe(ctx)
return
}
// 立即发送日简报
if (msgText === "总结日报") {
logger.info(`bot command is summary, chatId: ${chatId}`)
attachService.groupAgent(app, body, "summary", "daily")
report.gen4Test(ctx, "daily")
return
}
// 立即发送周简报
if (msgText === "总结周报") {
logger.info(`bot command is summary, chatId: ${chatId}`)
attachService.groupAgent(app, body, "summary", "weekly")
report.gen4Test(ctx, "weekly")
return
}
}

View File

@ -1,6 +1,7 @@
import { stringifyJson } from "@egg/hooks"
import { LarkService } from "@egg/net-tool"
import { appMap } from "../../constant/appMap"
import db from "../../db"
import { Context, DB, LarkServer, MsgProxy } from "../../types"
@ -41,7 +42,7 @@ const validateMessageReq = ({
export const manageMessageReq = async (
ctx: Context.Data
): Promise<Response> => {
const { body: rawBody, genResp, appMap, requestId } = ctx
const { body: rawBody, genResp, requestId } = ctx
const body = rawBody as MsgProxy.Body
// 校验参数

View File

@ -1,5 +1,6 @@
import { LarkService } from "@egg/net-tool"
import { appMap } from "../../constant/appMap"
import { Context } from "../../types"
/**
@ -8,7 +9,7 @@ import { Context } from "../../types"
* @returns
*/
const manageLogin = async (ctx: Context.Data) => {
const { req, genResp, logger, appMap, requestId } = ctx
const { req, genResp, logger, requestId } = ctx
logger.info("micro app login")
const url = new URL(req.url)
const code = url.searchParams.get("code")
@ -52,7 +53,7 @@ const manageLogin = async (ctx: Context.Data) => {
* @returns
*/
const manageBatchUser = async (ctx: Context.Data) => {
const { body, genResp, logger, appMap, requestId } = ctx
const { body, genResp, logger, requestId } = ctx
logger.info("batch get user info")
if (!body) return genResp.badRequest("req body is empty")
const { user_ids, user_id_type, app_name } = body

View File

@ -1,6 +1,7 @@
import { LarkService } from "@egg/net-tool"
import Joi from "joi"
import { appMap } from "../../constant/appMap"
import db from "../../db"
import { Context } from "../../types"
import { SheetProxy } from "../../types/sheetProxy"
@ -64,7 +65,7 @@ const validateSheetReq = async ({
* @returns {Promise<Response>}
*/
export const manageSheetReq = async (ctx: Context.Data): Promise<Response> => {
const { body: rawBody, genResp, appMap, requestId } = ctx
const { body: rawBody, genResp, requestId } = ctx
const body = rawBody as SheetProxy.InsertData
// 校验参数

View File

@ -1,6 +1,11 @@
import schedule from "node-schedule"
import report from "../controller/groupAgent/report"
export const initSchedule = async () => {
// // 定时任务每15分钟刷新一次token
// schedule.scheduleJob("*/15 * * * *", resetAccessToken)
// // 立即执行一次
// resetAccessToken()
// 定时任务,每周一到周四晚上 8 点 0 分 0 秒执行
schedule.scheduleJob("0 20 * * 1-4", () => report.genAllReport("daily"))
// 定时任务,每周五晚上 8 点 0 分 0 秒执行
schedule.scheduleJob("0 20 * * 5", () => report.genAllReport("weekly"))
}

View File

@ -41,25 +41,6 @@ class AttachService extends NetToolBase {
await this.post(URL, body).catch(() => "")
}
}
/**
* MiChat事件
* @param {LarkEvent.Data} body -
* @returns {Promise<void>}
*/
async groupAgent(
app: string,
body: LarkEvent.Data,
func: "register" | "unregister" | "summary",
timeScope?: "daily" | "weekly"
) {
const URL = `${this.hostMap[Bun.env.NODE_ENV!]}/chat_agent/${func}`
return this.post(URL, body, {
timeScope,
app,
secret: Bun.env.ATTACH_APP_SECRET,
}).catch(() => "")
}
}
export default AttachService

View File

@ -0,0 +1,4 @@
POST http://localhost:3000/bot?app=egg HTTP/1.1
content-type: application/json
{"schema":"2.0","header":{"event_id":"c94518fbcb9d66cc93b4144cc69e4f0c","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732612280153","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 开启日报、周报\"}","create_time":"1732612279943","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_4ab57cb6e889eeb81ca061b137238189","message_type":"text","update_time":"1732612279943"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}

View File

@ -0,0 +1,4 @@
POST http://localhost:3000/bot?app=egg HTTP/1.1
content-type: application/json
{"schema":"2.0","header":{"event_id":"ad5a706e2175e4bc539da53fd54b6fa0","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732619538646","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 总结日报\"}","create_time":"1732619538450","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_988659d8dcb79a54a02016ea1884e3df","message_type":"text","update_time":"1732619538450"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}

View File

@ -0,0 +1,4 @@
POST http://localhost:3000/bot?app=egg HTTP/1.1
content-type: application/json
{"schema":"2.0","header":{"event_id":"6d212cfdfa9e5bc91e008eabef0c6288","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732613767405","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 关闭日报、周报\"}","create_time":"1732613767202","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_344daf29eed184d808f52a73a1c2644d","message_type":"text","update_time":"1732613767202"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}

View File

@ -3,17 +3,13 @@ import { LarkService, NetTool } from "@egg/net-tool"
import { PathCheckTool } from "@egg/path-tool"
import { Logger } from "winston"
import { AppInfo } from "../constant/appMap"
import cardMap from "../constant/card"
import functionMap from "../constant/function"
import tempMap from "../constant/template"
import { AttachService } from "../services"
export namespace Context {
export interface APP_INFO {
app_id: string
app_secret: string
app_name: string
}
export interface Data {
req: Request
requestId: string
@ -28,7 +24,6 @@ export namespace Context {
path: PathCheckTool
searchParams: URLSearchParams
app: "michat" | "egg" | string
appInfo: APP_INFO
appMap: Record<string, APP_INFO>
appInfo: AppInfo
}
}

View File

@ -1,22 +1,16 @@
import { parseJsonString } from "@egg/hooks"
import { LarkBody, LarkCard } from "@egg/lark-msg-tool"
import loggerIns from "@egg/logger"
import { LarkService, NetTool } from "@egg/net-tool"
import { PathCheckTool } from "@egg/path-tool"
import { v4 as uuid } from "uuid"
import { appMap } from "../constant/appMap"
import cardMap from "../constant/card"
import functionMap from "../constant/function"
import tempMap from "../constant/template"
import { AttachService } from "../services"
import { Context } from "../types"
// 获取所有应用信息
const appMap = parseJsonString(process.env.LARK_APP_MAP, {}) as Record<
string,
Context.APP_INFO
>
/**
* requestId
*
@ -83,7 +77,6 @@ const genContext = async (req: Request) => {
searchParams,
app,
appInfo,
appMap,
} as Context.Data
}

30
utils/time.ts Normal file
View File

@ -0,0 +1,30 @@
/**
*
* @param {("daily" | "weekly")} timeScope - "daily" "weekly"
* @returns {{ startTime: string, endTime: string }} -
*/
export const getTimeRange = (timeScope: "daily" | "weekly") => {
const formatDate = (date: Date) => {
const pad = (num: number) => (num < 10 ? "0" + num : num)
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
const now = new Date()
const startTime = new Date(now)
if (timeScope === "daily") {
startTime.setHours(0, 0, 0, 0)
} else {
const day = now.getDay()
const diff = day === 0 ? 6 : day - 1 // adjust when day is sunday
startTime.setDate(now.getDate() - diff)
startTime.setHours(0, 0, 0, 0)
}
const endTime = new Date(now)
endTime.setHours(23, 59, 59, 999)
return {
startTime: formatDate(startTime),
endTime: formatDate(endTime),
}
}