重构:将 "lobster" 重命名为 "claw" 并添加国际化支持 (i18n)

This commit is contained in:
richarjiang
2026-03-13 12:07:28 +08:00
parent fa4c458eda
commit 9e30771180
38 changed files with 1003 additions and 344 deletions

View File

@@ -1,6 +1,6 @@
import crypto from "crypto";
import { db } from "@/lib/db";
import { lobsters } from "@/lib/db/schema";
import { claws } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { redis } from "@/lib/redis";
@@ -12,34 +12,34 @@ export function generateApiKey(): string {
export async function validateApiKey(apiKey: string) {
try {
const cacheKey = `lobster:key:${apiKey}`;
const cachedLobsterId = await redis.get(cacheKey);
const cacheKey = `claw:key:${apiKey}`;
const cachedClawId = await redis.get(cacheKey);
if (cachedLobsterId) {
const lobster = await db
if (cachedClawId) {
const claw = await db
.select()
.from(lobsters)
.where(eq(lobsters.id, cachedLobsterId))
.from(claws)
.where(eq(claws.id, cachedClawId))
.limit(1);
if (lobster.length > 0) {
return lobster[0];
if (claw.length > 0) {
return claw[0];
}
}
const lobster = await db
const claw = await db
.select()
.from(lobsters)
.where(eq(lobsters.apiKey, apiKey))
.from(claws)
.where(eq(claws.apiKey, apiKey))
.limit(1);
if (lobster.length === 0) {
if (claw.length === 0) {
return null;
}
await redis.set(cacheKey, lobster[0].id, "EX", API_KEY_CACHE_TTL);
await redis.set(cacheKey, claw[0].id, "EX", API_KEY_CACHE_TTL);
return lobster[0];
return claw[0];
} catch (error) {
console.error("Failed to validate API key:", error);
return null;

View File

@@ -10,7 +10,7 @@ import {
} from "drizzle-orm/mysql-core";
import { sql } from "drizzle-orm";
export const lobsters = mysqlTable("lobsters", {
export const claws = mysqlTable("claws", {
id: varchar("id", { length: 21 }).primaryKey(),
apiKey: varchar("api_key", { length: 64 }).notNull().unique(),
name: varchar("name", { length: 100 }).notNull(),
@@ -33,12 +33,12 @@ export const heartbeats = mysqlTable(
"heartbeats",
{
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
lobsterId: varchar("lobster_id", { length: 21 }).notNull(),
clawId: varchar("claw_id", { length: 21 }).notNull(),
ip: varchar("ip", { length: 45 }),
timestamp: datetime("timestamp").default(sql`NOW()`),
},
(table) => [
index("heartbeats_lobster_id_idx").on(table.lobsterId),
index("heartbeats_claw_id_idx").on(table.clawId),
index("heartbeats_timestamp_idx").on(table.timestamp),
]
);
@@ -47,7 +47,7 @@ export const tasks = mysqlTable(
"tasks",
{
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
lobsterId: varchar("lobster_id", { length: 21 }).notNull(),
clawId: varchar("claw_id", { length: 21 }).notNull(),
summary: varchar("summary", { length: 500 }),
durationMs: int("duration_ms"),
model: varchar("model", { length: 50 }),
@@ -55,7 +55,7 @@ export const tasks = mysqlTable(
timestamp: datetime("timestamp").default(sql`NOW()`),
},
(table) => [
index("tasks_lobster_id_idx").on(table.lobsterId),
index("tasks_claw_id_idx").on(table.clawId),
index("tasks_timestamp_idx").on(table.timestamp),
]
);

View File

@@ -24,33 +24,33 @@ if (process.env.NODE_ENV !== "production") {
}
const CHANNEL_REALTIME = "channel:realtime";
const ACTIVE_LOBSTERS_KEY = "active:lobsters";
const ACTIVE_CLAWS_KEY = "active:claws";
const STATS_GLOBAL_KEY = "stats:global";
const STATS_REGION_KEY = "stats:region";
const HEATMAP_CACHE_KEY = "cache:heatmap";
const HOURLY_ACTIVITY_KEY = "stats:hourly";
export async function setLobsterOnline(
lobsterId: string,
export async function setClawOnline(
clawId: string,
ip: string
): Promise<void> {
await redis.set(`lobster:online:${lobsterId}`, ip, "EX", 300);
await redis.set(`claw:online:${clawId}`, ip, "EX", 300);
}
export async function isLobsterOnline(lobsterId: string): Promise<boolean> {
const result = await redis.exists(`lobster:online:${lobsterId}`);
export async function isClawOnline(clawId: string): Promise<boolean> {
const result = await redis.exists(`claw:online:${clawId}`);
return result === 1;
}
export async function updateActiveLobsters(lobsterId: string): Promise<void> {
export async function updateActiveClaws(clawId: string): Promise<void> {
const now = Date.now();
await redis.zadd(ACTIVE_LOBSTERS_KEY, now, lobsterId);
await redis.zadd(ACTIVE_CLAWS_KEY, now, clawId);
}
export async function getActiveLobsterIds(
export async function getActiveClawIds(
limit: number = 100
): Promise<string[]> {
return redis.zrevrange(ACTIVE_LOBSTERS_KEY, 0, limit - 1);
return redis.zrevrange(ACTIVE_CLAWS_KEY, 0, limit - 1);
}
export async function incrementRegionCount(region: string): Promise<void> {