feat: 优化app数据的存储方式
All checks were successful
Egg CI/CD / build-image (push) Successful in 29s
Egg CI/CD / deploy (push) Successful in 21s

This commit is contained in:
zhaoyingbo 2024-07-10 11:26:56 +00:00
parent 087e80d037
commit 490c6ea3e5
21 changed files with 96 additions and 226 deletions

View File

@ -1,3 +0,0 @@
import { DB } from "./types";
export const supportApp: DB.AppName[] = ["egg", "seek", "phone"];

View File

@ -1,29 +0,0 @@
import pbClient from "../pbClient";
import { managePb404 } from "../../utils/pbTools";
import { DB } from "../../types";
/**
*
* @param key
* @returns
*/
const get = async (key: string) => {
const config = await managePb404<DB.AppConfig>(
async () =>
await await pbClient.collection("config").getFirstListItem(`key='${key}'`)
);
return config;
};
const getVal = async (key: string) => {
const config = await get(key);
if (!config) return "";
return config.value;
};
const appConfig = {
get,
getVal,
};
export default appConfig;

39
db/appInfo/index.ts Normal file
View File

@ -0,0 +1,39 @@
import pbClient from "../pbClient";
import { managePb404 } from "../../utils/pbTools";
import { DB } from "../../types";
/**
*
* @param appName
* @returns
*/
const get = async (appName: string) =>
managePb404<DB.AppInfo>(() =>
pbClient.collection("app_info").getFirstListItem(`code_name='${appName}'`)
);
/**
*
* @returns
*/
const getFullList = () => pbClient.collection("app_info").getFullList();
/**
*
* @param appName
* @param key
* @returns
*/
const getVal = async (appName: string, key: string) => {
const config = await get(appName);
if (!config) return "";
return config[key] || "";
};
const appInfo = {
get,
getVal,
getFullList,
};
export default appInfo;

View File

@ -1,9 +1,9 @@
import messageGroup from "./messageGroup";
import tenantAccessToken from "./tenantAccessToken";
import appConfig from "./appConfig";
import appInfo from "./appInfo";
const db = {
appConfig,
appInfo,
messageGroup,
tenantAccessToken,
};

View File

@ -3,8 +3,8 @@ import { managePb404 } from "../../utils/pbTools";
import pbClient from "../pbClient";
const getOne = (groupId: string) =>
managePb404<DB.MessageGroup>(
async () => await pbClient.collection("message_group").getOne(groupId)
managePb404<DB.MessageGroup>(() =>
pbClient.collection("message_group").getOne(groupId)
);
const messageGroup = {

View File

@ -1,35 +1,33 @@
import { DB } from "../../types";
import appConfig from "../appConfig";
import appInfo from "../appInfo";
import pbClient from "../pbClient";
const tokenCache = {} as Record<DB.AppName, string>;
const tokenCache = {} as Record<string, string>;
/**
* token
* @param {DB.AppName} appName
* @param {string} id id
* @param {string} appName
* @param {string} value token
*/
const update = async (appName: DB.AppName, value: string) => {
const config = await appConfig.get(`${appName}_tenant_access_token`);
if (!config) {
await pbClient.collection("config").create({
key: `${appName}_tenant_access_token`,
value,
});
} else {
await pbClient.collection("config").update(config.id, { value });
}
const update = async (id: string, appName: string, value: string) => {
try {
await pbClient
.collection("app_info")
.update(id, { tenant_access_token: value });
} catch {}
tokenCache[appName] = value;
console.log("reset egg access token success", value);
console.log(`reset ${appName} access token success`, value);
};
/**
* token
* @param {string} appName
* @returns {string} token
*/
const get = async (appName: DB.AppName) => {
const get = async (appName: string) => {
if (tokenCache[appName]) return tokenCache[appName];
const config = await appConfig.getVal(`${appName}_tenant_access_token`);
const config = await appInfo.getVal(appName, "tenant_access_token");
tokenCache[appName] = config;
return config;
};

View File

@ -1,10 +0,0 @@
FROM node:18.17.1-alpine3.18
# 更换国内源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
# 设置时区
RUN apk update \
&& apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone

View File

@ -1,2 +0,0 @@
docker build -t micr.cloud.mioffice.cn/zhaoyingbo/node:18.17.1-alpine3.18 .
docker push micr.cloud.mioffice.cn/zhaoyingbo/node:18.17.1-alpine3.18

View File

@ -1,4 +1,3 @@
import sendRank from "../../schedule/rank";
import service from "../../services";
import { LarkEvent } from "../../types";
import {
@ -101,10 +100,6 @@ const manageCMDMsg = (body: LarkEvent.Data) => {
service.attach.ciMonitor(chatId);
return true;
}
if (text.trim() === "/rank") {
sendRank();
return true;
}
if (text.includes("share") && text.includes("简报")) {
service.attach.reportCollector(body);
// 这个用时比较久,先发一条提醒用户收到了请求

View File

@ -1,4 +1,3 @@
import { supportApp } from "../../constant";
import db from "../../db";
import service from "../../services";
import { LarkServer, MsgProxy } from "../../types";
@ -16,9 +15,6 @@ const validateMessageReq = (body: MsgProxy.Body) => {
if (!body.content) {
return new Response("content is required", { status: 400 });
}
if (body.app_name && !supportApp.includes(body.app_name)) {
return new Response("app_name is invalid", { status: 400 });
}
return false;
};

View File

@ -1,6 +1,4 @@
import { supportApp } from "../../constant";
import service from "../../services";
import { DB } from "../../types";
import { trimPathPrefix } from "../../utils/pathTools";
/**
@ -11,11 +9,7 @@ import { trimPathPrefix } from "../../utils/pathTools";
const manageLogin = async (req: Request) => {
const url = new URL(req.url);
const code = url.searchParams.get("code");
const appName =
(url.searchParams.get("app_name") as DB.AppName | null) || undefined;
if (appName && !supportApp.includes(appName)) {
return new Response("app_name is invalid", { status: 400 });
}
const appName = url.searchParams.get("app_name") || undefined;
if (!code) {
return new Response("code not found", { status: 400 });
}
@ -57,9 +51,6 @@ const manageBatchUser = async (req: Request) => {
if (!user_id_type) {
return new Response("user_id_type not found", { status: 400 });
}
if (app_name && !supportApp.includes(app_name)) {
return new Response("app_name is invalid", { status: 400 });
}
const { code, data, msg } = await service.lark.user.batchGet(app_name)(
user_ids,

View File

@ -1,23 +1,24 @@
import { supportApp } from "../constant";
import db from "../db";
import netTool from "../services/netTool";
import { DB } from "../types";
const URL =
"https://open.f.mioffice.cn/open-apis/auth/v3/tenant_access_token/internal";
const refreshAccessToken = async (appName: DB.AppName) => {
const appId = await db.appConfig.getVal(`${appName}_app_id`);
const appSecret = await db.appConfig.getVal(`${appName}_app_secret`);
const { tenant_access_token } = await netTool.post(URL, {
app_id: appId,
app_secret: appSecret,
});
db.tenantAccessToken.update(appName, tenant_access_token);
};
export const resetAccessToken = async () => {
for (const appName of supportApp) {
await refreshAccessToken(appName);
try {
const appList = await db.appInfo.getFullList();
for (const app of appList) {
const { tenant_access_token } = await netTool.post(URL, {
app_id: app.app_id,
app_secret: app.app_secret,
});
await db.tenantAccessToken.update(
app.id,
app.code_name,
tenant_access_token
);
}
} catch (error) {
console.error("🚀 ~ resetAccessToken ~ error", error);
}
};

View File

@ -1,12 +1,9 @@
import { resetAccessToken } from "./accessToken";
import schedule from "node-schedule";
import sendRank from "./rank";
export const initSchedule = async () => {
// 定时任务每15分钟刷新一次token
schedule.scheduleJob("*/15 * * * *", resetAccessToken);
// 每天的每天晚上七点发送一次消息
schedule.scheduleJob("0 19 * * *", sendRank);
// 立即执行一次
resetAccessToken();
};

View File

@ -1,99 +0,0 @@
import service from "../services";
const sendRank = async () => {
try {
const res = await fetch(
"https://hackathon.tech.xiaomi.com/api/hackathon/2024/vote/list?eventId=33",
{
headers: {
accept: "*/*",
"accept-language": "zh-CN,zh;q=0.9",
baggage:
"sentry-environment=production,sentry-public_key=5b89f1d1d10446f8aca80e4abb1d1024,sentry-trace_id=1a42557958254192ace288caf6a3a0fd,sentry-sample_rate=1,sentry-sampled=true",
priority: "u=1, i",
"sec-ch-ua":
'"Not/A)Brand";v="8", "Chromium";v="126", "Microsoft Edge";v="126"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"sentry-trace": "1a42557958254192ace288caf6a3a0fd-b7ada044143eb872-1",
cookie:
"_aegis_cas=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MTg1Mzc0MjYsImRlcGlkIjoiK1x1MDAxYkx1JVx1MDAwMWd3aV1WYkNcdTAwMTVcXG9wXHUwMDA0aV1WakRcdTAwMWIyeFx1MDAwM1x1MDAwNGldXmEiLCJhdWQiOiJoYWNrYXRob24udGVjaC54aWFvbWkuY29tIiwiYyI6MCwiZGV0YWlsIjoixuw1XHLVr96TnXR4-es_QGRccrRB6rJcdTAwMTEyNrt3OfOQI-epXHK8x0fJX-NRjtVRUdNPJm_oQ1xm85OLkIA_V9ZQyvGd5u2Hxpp0x7lzcozY0ttZy35cL1wi3v-gXHUwMDFikppyuztEx1x1MDAxZaJcdTAwMWJKXHUwMDFjOi02fjH74sxXXGJcdTAwMTNIeTtpXHUwMDA0sdlcdTAwMTFaTyXIXHUwMDA20Vx1MDAxZVxyw6Y1lm_a8pJccimWXGZcdTAwMTFMXHUwMDBmacBcdTAwMDZl3ZTxuEhL5Vx1MDAxZVx1MDAxOFx1MDAwYofjYIHLh0Ggg3eNbLTv6HBcYlx1MDAxNLmKZd25wEQ5XHUwMDA3Jlx1MDAxOFxyJJSSXCJbdjxcdTAwMDPp3ZKvKHXXXHUwMDE5ei43NHNzS4orXHUwMDFlsJFrra2KllxcTu-ucoRRXCJH_mHqj-wy0ZmRROlcdTAwMGWMpLd8U2W6tyaEcFx1MDAxYapMrTRKqLyHqfJ4dTdcYvYycKt4N1x1MDAwMkSZxlwio9YoR7bmiWKpyOuIXHUwMDAzp9XrQrOJ7Lx4tiNcdTAwMDdcIlx1MDAwYmg0k82Dr__vnbVNdfSEkoL19Ow-LV9DNimXUpW9sEpqwLvbXHUwMDA3V-UzTTNZXHLwtn6iPnfcdjlaoCpN2KRcdTAwMGZcdTAwMWaqk43C4Uh6wmJgUWhmSu9Go33msVx1MDAxMWoyvWPgTfq-h8HzXHUwMDExs1wvRYxvt-09XG6r1_JcZqGUo3j2dVeGXHUwMDdmujb59_xlMaDeToNcdTAwMDE3KrggT4CmifFcdTAwMTKyVVx1MDAxOWFD5Fx1MDAxOIOdTCb17cjTsUh6NofpScrnJ0T7iej0XHUwMDE4mlx1MDAxNuSchHFR5mK31Fx0Vlx1MDAwNMRZpcnI14vfkmfJ3PXe1kSs81x1MDAwYu6z0tW294NH31x1MDAxMv7z0yQ6KetGQP0-4c6A5quU48lcdTAwMDbLSK5cdTAwMTjVg1x1MDAxMMd3co5FQip2bfNaa2fQg37-W1xmi1x1MDAxZsOmb9i1XHUwMDE1oU6O7pRcbtk0TjPLXHUwMDA2REzqXCLW5pra1ilfPpfgZF1KvMdkXHUwMDA3J_ddZutdTLxVlHt0KFW7Plx1MDAwM1x1MDAxNven-_dqIyIsInN1YiI6InpoYW95aW5nYm8iLCJ0IjoiZmFsc2UiLCJ1dCI6Ilx1MDAwMz9cdTAwMDZNXHRCVlEiLCJleHAiOjE3MTg4MDAyMjYsImQiOiI2NjRlZjRjNDhjZmJhM2JlMzUyMTdkYjA3Njg3YWJkOSIsImlzcyI6Ik1JLUlORk9TRUMiLCJsIjoiJVx1MDAxYThcdTAwMTFWXHUwMDBmIiwidHlwIjoiY2FzIn0.SNSaKLW9pz9dclMTxcgXChp_j-9i3Vr4C89MqtJp4ING2Y1F64NZaag2wrWL_Vr6MXBdxcY08Q1nPT-4BfidXg",
Referer: "https://hackathon.tech.xiaomi.com/2024",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
body: null,
method: "GET",
}
);
const data = (await res.json()) as any;
const list = data.data.votedInfoList as any[];
const noNeedTeam = [
"福尔摩丝与花生",
"赛博疾风",
"滴滴滴",
"大爱老师",
"愿世界没有调休",
"萌宠守卫队",
"说花就花我都队",
"公路合唱团",
"APTX4869",
"su7666",
"三等奖",
"你好我有一个帽衫",
"暖暖的很贴心",
"AI舞团",
"人像智绘师",
"刚刚好",
"PromptsEngineer",
"快码加编",
"驭魔智控",
"Magicians",
];
const overHandred = list.filter(
(v) => v.voteCount >= 100 && !noNeedTeam.includes(v.teamName)
);
// 排序
overHandred.sort((a: any, b: any) => b.voteCount - a.voteCount);
const index = overHandred.findIndex((v) => v.teamName === "聚光灯");
const diff = overHandred[0].voteCount - overHandred[index].voteCount;
const selfContent = `当前票数:${overHandred[index].voteCount},排名:${
index + 1
}${diff}`;
const overHandredContent = overHandred
.map((v) => `**${v.voteCount}**${v.subjectDesc}`)
.join("\n");
service.lark.message.send("egg")(
"chat_id",
"oc_bafb83413e933e25994dd313f5d76c7e",
"interactive",
JSON.stringify({
elements: [
{
tag: "markdown",
content: selfContent,
},
{ tag: "markdown", content: "**票数超过100 & 未在前20的队伍**" },
{
tag: "markdown",
content: overHandredContent,
},
],
header: {
template: "green",
title: { content: "寻TA票数监控", tag: "plain_text" },
},
})
);
} catch (e) {
console.error(e);
}
};
export default sendRank;
sendRank();

View File

@ -1,8 +1,8 @@
import { DB, LarkServer } from "../../types";
import { LarkServer } from "../../types";
import larkNetTool from "./larkNetTool";
const batchGetMeta =
(appName?: DB.AppName) =>
(appName?: string) =>
async (docTokens: string[], doc_type = "doc", user_id_type = "user_id") => {
const URL =
"https://open.f.mioffice.cn/open-apis/drive/v1/metas/batch_query";

View File

@ -15,7 +15,7 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
params?: any;
data?: any;
headers?: any;
appName?: DB.AppName;
appName?: string;
}): Promise<T> => {
const headersWithAuth = {
Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`,
@ -38,7 +38,7 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
};
larkNetTool.get =
(appName: DB.AppName = "egg") =>
(appName: string = "egg") =>
<T = LarkServer.BaseRes>(
url: string,
params?: any,
@ -47,7 +47,7 @@ larkNetTool.get =
larkNetTool({ url, method: "get", params, headers, appName });
larkNetTool.post =
(appName: DB.AppName = "egg") =>
(appName: string = "egg") =>
<T = LarkServer.BaseRes>(
url: string,
data?: any,
@ -57,12 +57,12 @@ larkNetTool.post =
larkNetTool({ url, method: "post", data, params, headers, appName });
larkNetTool.del =
(appName: DB.AppName = "egg") =>
(appName: string = "egg") =>
<T = LarkServer.BaseRes>(url: string, data: any, headers?: any): Promise<T> =>
larkNetTool({ url, method: "delete", data, headers, appName });
larkNetTool.patch =
(appName: DB.AppName = "egg") =>
(appName: string = "egg") =>
<T = LarkServer.BaseRes>(url: string, data: any, headers?: any): Promise<T> =>
larkNetTool({ url, method: "patch", data, headers, appName });

View File

@ -10,7 +10,7 @@ import larkNetTool from "./larkNetTool";
* @param {string} content JSON结构序列化后的字符串msg_type对应不同内容
*/
const send =
(appName?: DB.AppName) =>
(appName?: string) =>
async (
receive_id_type: LarkServer.ReceiveIDType,
receive_id: string,
@ -34,7 +34,7 @@ const send =
* @param {string} content JSON结构序列化后的字符串msg_type对应不同内容
*/
const update =
(appName?: DB.AppName) => async (message_id: string, content: string) => {
(appName?: string) => async (message_id: string, content: string) => {
const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`;
return larkNetTool.patch(appName)<LarkServer.BaseRes>(URL, { content });
};

View File

@ -8,7 +8,7 @@ import larkNetTool from "./larkNetTool";
* @param code
* @returns
*/
const code2Login = (appName?: DB.AppName) => async (code: string) => {
const code2Login = (appName?: string) => async (code: string) => {
const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`;
return larkNetTool.post(appName)<LarkServer.UserSessionRes>(URL, { code });
};
@ -19,7 +19,7 @@ const code2Login = (appName?: DB.AppName) => async (code: string) => {
* @returns
*/
const get =
(appName?: DB.AppName) => async (user_id: string, user_id_type: string) => {
(appName?: string) => async (user_id: string, user_id_type: string) => {
const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/${user_id}`;
return larkNetTool.get(appName)<LarkServer.UserInfoRes>(URL, {
user_id_type,
@ -32,7 +32,7 @@ const get =
* @returns
*/
const batchGet =
(appName?: DB.AppName) =>
(appName?: string) =>
async (user_ids: string[], user_id_type: "open_id" | "user_id") => {
const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/batch`;

View File

@ -1,14 +1,17 @@
import { RecordModel } from "pocketbase";
export namespace DB {
export interface AppConfig extends RecordModel {
value: string;
export interface AppInfo extends RecordModel {
code_name: string;
app_id: string;
app_secret: string;
app_name: string;
avatar_url: string;
description: string;
tenant_access_token: string;
}
export interface MessageGroup extends RecordModel {
collectionId: string;
collectionName: string;
updated: string;
created: string;
desc: string;
id: string;
name: string;
@ -18,10 +21,4 @@ export namespace DB {
union_id?: string[];
user_id?: string[];
}
export type AppName = "egg" | "seek" | "phone";
export enum AppNameEnum {
egg = "egg",
seek = "seek",
phone = "phone",
}
}

View File

@ -1,11 +1,10 @@
import { DB } from "./db";
import { LarkServer } from "./larkServer";
export namespace MsgProxy {
export interface BaseBody {
msg_type: LarkServer.MsgType;
content: string;
app_name?: DB.AppName;
app_name?: string;
}
export interface GroupBody extends BaseBody {
group_id: string;