feat: 支持飞书表格insert操作 & 优化netTool
This commit is contained in:
parent
9821ae9610
commit
98dd30a572
3
index.ts
3
index.ts
@ -1,6 +1,7 @@
|
||||
import { manageBotReq } from "./routes/bot";
|
||||
import { manageMessageReq } from "./routes/message";
|
||||
import { manageMicroAppReq } from "./routes/microApp";
|
||||
import { manageSheetReq } from "./routes/sheet";
|
||||
import { initSchedule } from "./schedule";
|
||||
|
||||
initSchedule();
|
||||
@ -15,6 +16,8 @@ const server = Bun.serve({
|
||||
if (url.pathname === "/bot") return await manageBotReq(req);
|
||||
// 消息代理发送
|
||||
if (url.pathname === "/message") return await manageMessageReq(req);
|
||||
// 表格代理操作
|
||||
if (url.pathname === "/sheet") return await manageSheetReq(req);
|
||||
// 小程序
|
||||
if (url.pathname.startsWith("/micro_app"))
|
||||
return await manageMicroAppReq(req);
|
||||
|
57
routes/sheet/index.ts
Normal file
57
routes/sheet/index.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import db from "../../db";
|
||||
import service from "../../services";
|
||||
import netTool from "../../services/netTool";
|
||||
import { Sheet } from "../../types/sheet";
|
||||
|
||||
const validateSheetReq = async (body: Sheet.Body) => {
|
||||
if (!body.api_key) {
|
||||
return netTool.badRequest("api_key is required");
|
||||
}
|
||||
if (!body.sheet_token) {
|
||||
return netTool.badRequest("sheet_token is required");
|
||||
}
|
||||
if (!body.range) {
|
||||
return netTool.badRequest("range is required");
|
||||
}
|
||||
if (!body.values) {
|
||||
return netTool.badRequest("values is required");
|
||||
}
|
||||
|
||||
if (!Sheet.isType(body.type)) {
|
||||
return netTool.badRequest("type is invalid");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const manageSheetReq = async (req: Request) => {
|
||||
const body = (await req.json()) as Sheet.Body;
|
||||
// 校验参数
|
||||
const validateRes = await validateSheetReq(body);
|
||||
if (validateRes) return validateRes;
|
||||
|
||||
// 校验api_key
|
||||
const apiKeyInfo = await db.apiKey.getOne(body.api_key);
|
||||
if (!apiKeyInfo) {
|
||||
return netTool.notFound("api key not found");
|
||||
}
|
||||
|
||||
// 获取 app name
|
||||
const appName = apiKeyInfo.expand?.app?.name;
|
||||
if (!appName) {
|
||||
return netTool.notFound("app name not found");
|
||||
}
|
||||
|
||||
if (body.type === "insert") {
|
||||
// 插入行
|
||||
const insertRes = await service.lark.sheet.insterRows(appName)(
|
||||
body.sheet_token,
|
||||
body.range,
|
||||
body.values
|
||||
);
|
||||
|
||||
// 返回
|
||||
return netTool.ok(insertRes);
|
||||
}
|
||||
|
||||
return netTool.ok();
|
||||
};
|
@ -1,11 +1,13 @@
|
||||
import message from "./message";
|
||||
import user from "./user";
|
||||
import drive from "./drive";
|
||||
import sheet from "./sheet";
|
||||
|
||||
const lark = {
|
||||
message,
|
||||
user,
|
||||
drive,
|
||||
sheet,
|
||||
};
|
||||
|
||||
export default lark;
|
||||
|
@ -2,31 +2,42 @@ import db from "../../db";
|
||||
import { DB, LarkServer } from "../../types";
|
||||
import netTool from "../netTool";
|
||||
|
||||
/**
|
||||
* 发送网络请求并返回一个解析为响应数据的Promise。
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param method - 请求使用的HTTP方法。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @param appName - 应用名称,用于获取授权令牌。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
* @throws 如果网络响应不成功或存在解析错误,则抛出错误。
|
||||
*/
|
||||
const larkNetTool = async <T = LarkServer.BaseRes>({
|
||||
url,
|
||||
method,
|
||||
params,
|
||||
data,
|
||||
headers,
|
||||
queryParams,
|
||||
payload,
|
||||
additionalHeaders,
|
||||
appName = "egg",
|
||||
}: {
|
||||
url: string;
|
||||
method: string;
|
||||
params?: any;
|
||||
data?: any;
|
||||
headers?: any;
|
||||
queryParams?: any;
|
||||
payload?: any;
|
||||
additionalHeaders?: any;
|
||||
appName?: string;
|
||||
}): Promise<T> => {
|
||||
const headersWithAuth = {
|
||||
Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`,
|
||||
...headers,
|
||||
...additionalHeaders,
|
||||
};
|
||||
return netTool<T>({
|
||||
url,
|
||||
method,
|
||||
params,
|
||||
data,
|
||||
headers: headersWithAuth,
|
||||
queryParams,
|
||||
payload,
|
||||
additionalHeaders: headersWithAuth,
|
||||
}).catch((error) => {
|
||||
console.error("larkNetTool catch error: ", error);
|
||||
return {
|
||||
@ -37,33 +48,78 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送GET请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param appName - 应用名称,用于获取授权令牌。
|
||||
* @returns 一个函数,该函数接受URL、查询参数和附加头,并返回一个解析为响应数据的Promise。
|
||||
*/
|
||||
larkNetTool.get =
|
||||
(appName: string = "egg") =>
|
||||
(appName?: string) =>
|
||||
<T = LarkServer.BaseRes>(
|
||||
url: string,
|
||||
params?: any,
|
||||
headers?: any
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
larkNetTool({ url, method: "get", params, headers, appName });
|
||||
larkNetTool({
|
||||
url,
|
||||
method: "get",
|
||||
queryParams,
|
||||
additionalHeaders,
|
||||
appName,
|
||||
});
|
||||
|
||||
/**
|
||||
* 发送POST请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param appName - 应用名称,用于获取授权令牌。
|
||||
* @returns 一个函数,该函数接受URL、有效负载、查询参数和附加头,并返回一个解析为响应数据的Promise。
|
||||
*/
|
||||
larkNetTool.post =
|
||||
(appName: string = "egg") =>
|
||||
(appName?: string) =>
|
||||
<T = LarkServer.BaseRes>(
|
||||
url: string,
|
||||
data?: any,
|
||||
params?: any,
|
||||
headers?: any
|
||||
payload?: any,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
larkNetTool({ url, method: "post", data, params, headers, appName });
|
||||
larkNetTool({
|
||||
url,
|
||||
method: "post",
|
||||
payload,
|
||||
queryParams,
|
||||
additionalHeaders,
|
||||
appName,
|
||||
});
|
||||
|
||||
/**
|
||||
* 发送DELETE请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param appName - 应用名称,用于获取授权令牌。
|
||||
* @returns 一个函数,该函数接受URL、有效负载和附加头,并返回一个解析为响应数据的Promise。
|
||||
*/
|
||||
larkNetTool.del =
|
||||
(appName: string = "egg") =>
|
||||
<T = LarkServer.BaseRes>(url: string, data: any, headers?: any): Promise<T> =>
|
||||
larkNetTool({ url, method: "delete", data, headers, appName });
|
||||
(appName?: string) =>
|
||||
<T = LarkServer.BaseRes>(
|
||||
url: string,
|
||||
payload: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
larkNetTool({ url, method: "delete", payload, additionalHeaders, appName });
|
||||
|
||||
/**
|
||||
* 发送PATCH请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param appName - 应用名称,用于获取授权令牌。
|
||||
* @returns 一个函数,该函数接受URL、有效负载和附加头,并返回一个解析为响应数据的Promise。
|
||||
*/
|
||||
larkNetTool.patch =
|
||||
(appName: string = "egg") =>
|
||||
<T = LarkServer.BaseRes>(url: string, data: any, headers?: any): Promise<T> =>
|
||||
larkNetTool({ url, method: "patch", data, headers, appName });
|
||||
(appName?: string) =>
|
||||
<T = LarkServer.BaseRes>(
|
||||
url: string,
|
||||
payload: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
larkNetTool({ url, method: "patch", payload, additionalHeaders, appName });
|
||||
|
||||
export default larkNetTool;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { DB } from "../../types";
|
||||
import { LarkServer } from "../../types/larkServer";
|
||||
import larkNetTool from "./larkNetTool";
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { LarkServer } from "../../types/larkServer";
|
||||
import larkNetTool from "./larkNetTool";
|
||||
|
||||
/**
|
||||
* 向电子表格中插入行。
|
||||
* @param appName - 应用程序的名称(可选)。
|
||||
* @returns 一个函数,该函数接受表格令牌、范围和要插入的值。
|
||||
*/
|
||||
const insterRows =
|
||||
(appName?: string) =>
|
||||
async (sheetToken: string, range: string, values: string[][]) => {
|
||||
const URL = `https://open.f.mioffice.cn/open-apis/sheets/v2/spreadsheets/${sheetToken}/values_append?insertDataOption=INSERT_ROWS`;
|
||||
return larkNetTool.post(appName)<LarkServer.BaseRes>(URL, {
|
||||
valueRange: {
|
||||
range,
|
||||
values,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const sheet = {
|
||||
insterRows,
|
||||
};
|
||||
|
||||
export default sheet;
|
@ -1,29 +1,27 @@
|
||||
interface NetGetParams {
|
||||
interface NetRequestParams {
|
||||
url: string;
|
||||
method: string;
|
||||
params?: any;
|
||||
data?: any;
|
||||
headers?: any;
|
||||
queryParams?: any;
|
||||
payload?: any;
|
||||
additionalHeaders?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印请求日志
|
||||
* @param response
|
||||
* @param method
|
||||
* @param data
|
||||
* 记录响应详情并返回响应日志对象。
|
||||
* @param response - 响应对象。
|
||||
* @param method - 请求使用的HTTP方法。
|
||||
* @param headers - 请求头。
|
||||
* @param requestBody - 请求体。
|
||||
* @param responseBody - 响应体。
|
||||
* @returns 响应日志对象。
|
||||
*/
|
||||
const logResponse = async (
|
||||
const logResponse = (
|
||||
response: Response,
|
||||
method: string,
|
||||
data: any,
|
||||
headers: any
|
||||
headers: any,
|
||||
requestBody: any,
|
||||
responseBody: any
|
||||
) => {
|
||||
let responseData = null;
|
||||
try {
|
||||
responseData = await response.json();
|
||||
} catch (error) {
|
||||
responseData = "parse to json error";
|
||||
}
|
||||
const responseLog = {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
@ -32,76 +30,190 @@ const logResponse = async (
|
||||
method: method,
|
||||
requestHeaders: headers,
|
||||
responseHeaders: response.headers,
|
||||
requestBody: data,
|
||||
responseBody: responseData as any,
|
||||
requestBody,
|
||||
responseBody,
|
||||
};
|
||||
console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2));
|
||||
return responseLog;
|
||||
};
|
||||
|
||||
const netTool = <T = any>({
|
||||
/**
|
||||
* 发送网络请求并返回一个解析为响应数据的Promise。
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param method - 请求使用的HTTP方法。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
* @throws 如果网络响应不成功或存在解析错误,则抛出错误。
|
||||
*/
|
||||
const netTool = async <T = any>({
|
||||
url,
|
||||
method,
|
||||
params,
|
||||
data,
|
||||
headers,
|
||||
}: NetGetParams): Promise<T> => {
|
||||
queryParams,
|
||||
payload,
|
||||
additionalHeaders,
|
||||
}: NetRequestParams): Promise<T> => {
|
||||
// 拼接完整的URL
|
||||
let fullUrl = url;
|
||||
if (params) {
|
||||
if (typeof params === "string") {
|
||||
fullUrl = `${url}?${params}`;
|
||||
if (queryParams) {
|
||||
if (typeof queryParams === "string") {
|
||||
fullUrl = `${url}?${queryParams}`;
|
||||
} else {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
fullUrl = `${url}?${queryString}`;
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
if (queryString) fullUrl = `${url}?${queryString}`;
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(fullUrl, {
|
||||
// 设置请求头
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
...additionalHeaders,
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
const res = await fetch(fullUrl, {
|
||||
method,
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...headers,
|
||||
},
|
||||
})
|
||||
.then((response) => logResponse(response, method, data, headers))
|
||||
.then((responseLog) => {
|
||||
if (!responseLog.ok) {
|
||||
if (responseLog?.responseBody?.msg) {
|
||||
throw new Error(responseLog.responseBody.msg);
|
||||
}
|
||||
throw new Error("网络响应异常");
|
||||
}
|
||||
if (responseLog.responseBody === "parse to json error") {
|
||||
throw new Error("解析响应数据异常");
|
||||
}
|
||||
return responseLog.responseBody as T;
|
||||
});
|
||||
body: JSON.stringify(payload),
|
||||
headers,
|
||||
});
|
||||
// 获取响应数据
|
||||
let resData: any = null;
|
||||
try {
|
||||
resData = await res.json();
|
||||
} catch (error) {
|
||||
resData = "解析为JSON时出错";
|
||||
}
|
||||
|
||||
// 记录响应
|
||||
logResponse(res, method, headers, payload, resData);
|
||||
if (!res.ok) {
|
||||
if (resData?.msg) {
|
||||
throw new Error(resData.msg);
|
||||
}
|
||||
throw new Error("网络响应异常");
|
||||
}
|
||||
if (resData === "解析为JSON时出错") {
|
||||
throw new Error("解析响应数据异常");
|
||||
}
|
||||
return resData as T;
|
||||
};
|
||||
|
||||
netTool.get = <T = any>(url: string, params?: any, headers?: any): Promise<T> =>
|
||||
netTool({ url, method: "get", params, headers });
|
||||
/**
|
||||
* 发送GET请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
*/
|
||||
netTool.get = <T = any>(
|
||||
url: string,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
netTool({ url, method: "get", queryParams, additionalHeaders });
|
||||
|
||||
/**
|
||||
* 发送POST请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
*/
|
||||
netTool.post = <T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
params?: any,
|
||||
headers?: any
|
||||
): Promise<T> => netTool({ url, method: "post", data, params, headers });
|
||||
payload?: any,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
netTool({ url, method: "post", payload, queryParams, additionalHeaders });
|
||||
|
||||
netTool.del = <T = any>(url: string, data: any, headers?: any): Promise<T> =>
|
||||
netTool({ url, method: "delete", data, headers });
|
||||
/**
|
||||
* 发送PUT请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
*/
|
||||
netTool.put = <T = any>(
|
||||
url: string,
|
||||
payload: any,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
netTool({ url, method: "put", payload, queryParams, additionalHeaders });
|
||||
|
||||
netTool.patch = <T = any>(url: string, data: any, headers?: any): Promise<T> =>
|
||||
netTool({ url, method: "patch", data, headers });
|
||||
/**
|
||||
* 发送DELETE请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
*/
|
||||
netTool.del = <T = any>(
|
||||
url: string,
|
||||
payload: any,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
netTool({ url, method: "delete", payload, queryParams, additionalHeaders });
|
||||
|
||||
/**
|
||||
* 发送PATCH请求并返回一个解析为响应数据的Promise。
|
||||
*
|
||||
* @param url - 要发送请求的URL。
|
||||
* @param payload - 请求的有效负载数据。
|
||||
* @param queryParams - 要包含在URL中的查询参数。
|
||||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||||
* @returns 一个解析为响应数据的Promise。
|
||||
*/
|
||||
netTool.patch = <T = any>(
|
||||
url: string,
|
||||
payload: any,
|
||||
queryParams?: any,
|
||||
additionalHeaders?: any
|
||||
): Promise<T> =>
|
||||
netTool({ url, method: "patch", payload, queryParams, additionalHeaders });
|
||||
|
||||
/**
|
||||
* 创建一个表示400 Bad Request的响应对象。
|
||||
*
|
||||
* @param msg - 错误消息。
|
||||
* @returns 一个表示400 Bad Request的响应对象。
|
||||
*/
|
||||
netTool.badRequest = (msg: string) => new Response(msg, { status: 400 });
|
||||
|
||||
/**
|
||||
* 创建一个表示404 Not Found的响应对象。
|
||||
*
|
||||
* @param msg - 错误消息。
|
||||
* @returns 一个表示404 Not Found的响应对象。
|
||||
*/
|
||||
netTool.notFound = (msg: string) => new Response(msg, { status: 404 });
|
||||
|
||||
/**
|
||||
* 创建一个表示500 Internal Server Error的响应对象。
|
||||
*
|
||||
* @param msg - 错误消息。
|
||||
* @param data - 错误数据。
|
||||
* @returns 一个表示500 Internal Server Error的响应对象。
|
||||
*/
|
||||
netTool.serverError = (msg: string, data: any) =>
|
||||
Response.json({ code: 500, msg, data }, { status: 500 });
|
||||
|
||||
netTool.ok = (data: any) => Response.json({ code: 200, msg: "ok", data });
|
||||
/**
|
||||
* 创建一个表示200 OK的响应对象。
|
||||
*
|
||||
* @param data - 响应数据。
|
||||
* @returns 一个表示200 OK的响应对象。
|
||||
*/
|
||||
netTool.ok = (data?: any) => Response.json({ code: 200, msg: "ok", data });
|
||||
|
||||
export default netTool;
|
||||
|
15
test/insertSheet.ts
Normal file
15
test/insertSheet.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// const URL = "https://egg.imoaix.cn/sheet";
|
||||
const URL = "http://localhost:3000/sheet";
|
||||
|
||||
const res = await fetch(URL, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
api_key: "uwnpzb9hvoft28h",
|
||||
sheet_token: "shtk48HuiHQOUSTAZ0t0DFplNJc",
|
||||
type: "insert",
|
||||
range: "a2YJxa",
|
||||
values: [["hello", "world"]],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(await res.text()));
|
18
types/sheet.ts
Normal file
18
types/sheet.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export namespace Sheet {
|
||||
export enum Type {
|
||||
Insert = "insert",
|
||||
}
|
||||
|
||||
export interface Body {
|
||||
api_key: string;
|
||||
sheet_token: string;
|
||||
type: "insert";
|
||||
range: string;
|
||||
values: string[][]; // 二维数组
|
||||
}
|
||||
|
||||
// 判断一个值是否是枚举中的值
|
||||
export const isType = (value: any): value is Type => {
|
||||
return Object.values(Type).includes(value);
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user