forked from bai/curriculum-project-hub
feat: add sender profile cache with LRU eviction
- SenderNameCache: TTL 10min, LRU max 2048 entries, getOrFetch pattern - resolveSenderName: API call with cache integration - Trigger uses cache for approval card resolutions and audit metadata - Add unit tests (9) for cache lifecycle, LRU, expiry, and null handling
This commit is contained in:
@@ -10,6 +10,7 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||||
import { dirname } from "node:path";
|
||||
import type { FastifyBaseLogger } from "fastify";
|
||||
import { DEFAULT_MAX_MESSAGE_LENGTH, splitAtBoundary } from "./textStream.js";
|
||||
import type { SenderNameCache } from "./senderCache.js";
|
||||
|
||||
export interface FeishuConfig {
|
||||
readonly appId: string;
|
||||
@@ -95,6 +96,14 @@ export interface CardActionEvent {
|
||||
readonly token?: string;
|
||||
}
|
||||
|
||||
type ContactUserResponse = {
|
||||
readonly data?: {
|
||||
readonly user?: {
|
||||
readonly name?: string | undefined;
|
||||
} | undefined;
|
||||
} | undefined;
|
||||
} | null;
|
||||
|
||||
// --- Message helpers ---
|
||||
|
||||
export function buildOutboundPayload(content: string): OutboundPayload {
|
||||
@@ -356,6 +365,35 @@ export async function removeReaction(rt: FeishuRuntime, messageId: string, react
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve a Feishu sender display name from open_id, optionally using a cache. */
|
||||
export async function resolveSenderName(
|
||||
rt: FeishuRuntime,
|
||||
openId: string,
|
||||
cache?: SenderNameCache,
|
||||
): Promise<string | null> {
|
||||
if (openId === "") return null;
|
||||
|
||||
const fetchName = async (id: string): Promise<string | null> => {
|
||||
try {
|
||||
const res = await rt.client.request({
|
||||
method: "GET",
|
||||
url: `/open-apis/contact/v3/users/${encodeURIComponent(id)}`,
|
||||
params: { user_id_type: "open_id" },
|
||||
}) as ContactUserResponse;
|
||||
const name = res?.data?.user?.name;
|
||||
return typeof name === "string" && name !== "" ? name : null;
|
||||
} catch (e) {
|
||||
rt.logger.warn({ openId: id, err: e instanceof Error ? e.message : String(e) }, "resolveSenderName failed");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (cache !== undefined) {
|
||||
return cache.getOrFetch(openId, fetchName);
|
||||
}
|
||||
return fetchName(openId);
|
||||
}
|
||||
|
||||
// --- File helpers ---
|
||||
|
||||
/** Download a file/image from a Feishu message to a local path. */
|
||||
|
||||
Reference in New Issue
Block a user