Compare commits

...

16 Commits

Author SHA1 Message Date
王性驊 ef6069fb85 update dashboard 2026-06-25 22:46:12 +08:00
王性驊 1a7a67f2db update dashboard 2026-06-25 22:45:10 +08:00
王性驊 e8e2dbbf96 update dashboard 2026-06-25 22:44:15 +08:00
王性驊 4f35b7dad4 update dashboard 2026-06-25 22:42:25 +08:00
王性驊 a9482fa646 add prod docker 2026-06-25 17:52:17 +08:00
王性驊 e820bc0368 add prod docker 2026-06-25 17:49:25 +08:00
王性驊 b7125abeac fix find post 2026-06-25 17:34:28 +08:00
王性驊 d0da1a1103 fix find post 2026-06-25 16:20:03 +08:00
王性驊 a66a3d81ee update dashboard 2026-06-25 01:30:47 +08:00
王性驊 66ef6b3d4a update dashboard 2026-06-25 00:48:56 +08:00
王性驊 e2dc98d426 fix all 2026-06-24 18:02:42 +08:00
王性驊 7e58bdba45 fix dockerfile unhealth problem 2026-06-24 14:04:54 +08:00
王性驊 f2736ced32 update dashboard 2026-06-24 00:57:51 +08:00
王性驊 28388d8210 update dashboard 2026-06-24 00:55:10 +08:00
王性驊 413d5f0b10 fix dockerfile unhealth problem 2026-06-23 18:10:22 +08:00
王性驊 4cd221af5e fix dockerfile unhealth problem 2026-06-23 17:54:27 +08:00
843 changed files with 83630 additions and 99 deletions

View File

@ -1,23 +1,66 @@
# 巡樓 Threads Session 同步Chrome 擴充)
從你已登入的 Chrome Threads 帳號,一鍵把 session 傳到遠端 Linux server不需要本機腳本
從你已登入的 Chrome Threads 帳號,一鍵把 session 傳到巡樓 Go 後端(`:8890`
## 安裝
**方式 A推薦**:巡樓「設定」頁 → **下載擴充套件ZIP** → 解壓後載入。
**方式 B**:直接選擇 repo 資料夾 `extension/haixun-threads-sync`
1. Chrome 打開 `chrome://extensions`
2. 開啟「開發人員模式」
3. 點「載入未封裝項目」
4. 選擇此資料夾:`extension/haixun-threads-sync`
4. 選擇解壓後的 `haixun-threads-sync` 資料夾
## 使用(多帳號)
## 必要條件
1. 在 Chrome 正常登入 threads.com要同步哪個帳號就登入哪個
2. 開啟巡樓網頁並登入
3. 在側欄**切換到目標經營帳號**
4. 到「設定」頁按 **「從 Chrome 同步」**,或點擴充功能圖示 →「同步到巡樓」
- 後端 API 在跑:`make run`(預設 `http://127.0.0.1:8890`
- 前端在跑:`make web-dev`(預設 `http://localhost:5173`
- 已在 Chrome 登入 `threads.com``threads.net`
- 已在巡樓網頁登入JWT 存在 localStorage
## 使用方式 A連線設定頁推薦
1. 開啟 `http://localhost:5173` 並登入
2. 頂部切換目標經營帳號
3. 到「帳號 → 連線設定」
4. 切換到 **開發模式(爬蟲)**
5. 確認顯示「擴充已偵測」
6. 按 **「從 Chrome 同步到目前帳號」**
> 關閉開發模式 = 全部走 Threads API不需要同步 Chrome session。
## 使用方式 B擴充圖示
1. 先開著巡樓分頁並登入
2. 點擴充圖示 →「同步到巡樓」
3. 擴充會自動讀取巡樓分頁的 JWT 與目前帳號
## 設定 server 網址
擴充功能選項填入你的 Linux server例如 `https://haixun.example.com`
擴充選項填入**前端網址**(不是 8890例如
首次同步會詢問是否允許存取該網站(讀取 `haixun_session` cookie
- 本機:`http://localhost:5173` 或 `http://127.0.0.1:5173`
- 遠端:`https://haixun.example.com`
儲存後會自動注入網頁橋接腳本。API 在本機 dev 時會自動改打 `:8890`
## 常見問題
| 現象 | 原因與處理 |
|------|------------|
| 尚未偵測擴充 | 擴充安裝/更新後頁面不會自動注入腳本 → `chrome://extensions` 重新載入擴充,再 **F5 刷新巡樓頁** |
| 等待擴充逾時 | 巡樓網址與擴充選項不一致(`localhost` vs `127.0.0.1`)→ 統一用同一個,或在擴充選項重新儲存 |
| 找不到登入狀態 / JWT | 開錯網站:要用 **新版** `http://localhost:5173`Go 後台),不是舊版 Next `:3000` |
| 無法連線後端 :8890 | 只開了 `make web-dev` 沒開 API → 另開終端執行 `cd haixun-backend && make run` |
| invalid access token | JWT 過期15 分鐘)→ 在巡樓頁重新整理或重新登入後再同步 |
| 找不到 cookies | 先在 Chrome 開 threads.com / threads.net 完成登入,再同步 |
| 缺少帳號 ID | 在巡樓頂部切換經營帳號,或打開 `/threads/:id/connections` |
| 開發模式未開啟 | 連線頁底部「開發工具」要先 **開啟開發模式**,同步按鈕才會出現 |
## 技術說明
- 網頁透過 `postMessage``content-haixun.js` 溝通
- 擴充 background 收集 Threads/Instagram cookies組成 Playwright `storageState`
- 呼叫 `POST /api/v1/threads-accounts/:id/session/import`Bearer JWT

View File

@ -1,39 +1,57 @@
window.addEventListener("message", (event) => {
if (event.source !== window) return;
(() => {
if (globalThis.__HAIXUN_THREADS_BRIDGE__) return;
globalThis.__HAIXUN_THREADS_BRIDGE__ = true;
if (event.data?.type === "HAIXUN_PING_EXTENSION") {
window.postMessage({ type: "HAIXUN_EXTENSION_READY" }, "*");
return;
const ROOT = document.documentElement;
ROOT.dataset.haixunExtension = "1";
ROOT.dataset.haixunExtensionVersion = "2";
function announceReady() {
window.postMessage({ type: "HAIXUN_EXTENSION_READY", version: 2 }, "*");
}
if (event.data?.type !== "HAIXUN_REQUEST_THREADS_SYNC") return;
window.addEventListener("message", (event) => {
if (event.source !== window) return;
chrome.runtime
.sendMessage({
action: "sync",
serverUrl: event.data.serverUrl ?? window.location.origin,
accountId: event.data.accountId,
})
.then((result) => {
window.postMessage(
{
type: "HAIXUN_THREADS_SYNC_RESULT",
...result,
},
"*"
);
})
.catch((error) => {
window.postMessage(
{
type: "HAIXUN_THREADS_SYNC_RESULT",
success: false,
valid: false,
message: error instanceof Error ? error.message : "同步失敗",
},
"*"
);
});
});
if (event.data?.type === "HAIXUN_PING_EXTENSION") {
announceReady();
return;
}
window.postMessage({ type: "HAIXUN_EXTENSION_READY" }, "*");
if (event.data?.type !== "HAIXUN_REQUEST_THREADS_SYNC") return;
chrome.runtime
.sendMessage({
action: "sync",
serverUrl: event.data.serverUrl ?? window.location.origin,
accountId: event.data.accountId,
accessToken: event.data.accessToken,
apiVersion: event.data.apiVersion ?? "go-v1",
})
.then((result) => {
window.postMessage(
{
type: "HAIXUN_THREADS_SYNC_RESULT",
...(result ?? {}),
},
"*"
);
})
.catch((error) => {
const message =
chrome.runtime.lastError?.message ??
(error instanceof Error ? error.message : "同步失敗");
window.postMessage(
{
type: "HAIXUN_THREADS_SYNC_RESULT",
success: false,
valid: false,
message,
},
"*"
);
});
});
announceReady();
})();

View File

@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "巡樓 Threads Session 同步",
"version": "1.0.0",
"version": "1.1.1",
"description": "從 Chrome 已登入的 Threads 一鍵同步 session 到巡樓 server",
"permissions": ["cookies", "storage", "tabs", "scripting"],
"host_permissions": [
@ -12,7 +12,13 @@
"https://instagram.com/*",
"https://*.facebook.com/*",
"http://localhost:3000/*",
"http://127.0.0.1:3000/*"
"http://127.0.0.1:3000/*",
"http://localhost:4173/*",
"http://127.0.0.1:4173/*",
"http://localhost:5173/*",
"http://127.0.0.1:5173/*",
"http://localhost:8890/*",
"http://127.0.0.1:8890/*"
],
"optional_host_permissions": ["http://*/*", "https://*/*"],
"action": {
@ -23,5 +29,27 @@
"service_worker": "service-worker.js",
"type": "module"
},
"content_scripts": [
{
"matches": [
"http://localhost:3000/*",
"http://127.0.0.1:3000/*",
"http://localhost:4173/*",
"http://127.0.0.1:4173/*",
"http://localhost:5173/*",
"http://127.0.0.1:5173/*",
"http://localhost:8890/*",
"http://127.0.0.1:8890/*"
],
"js": ["content-haixun.js"],
"run_at": "document_start"
}
],
"web_accessible_resources": [
{
"resources": ["content-haixun.js"],
"matches": ["http://*/*", "https://*/*"]
}
],
"options_page": "options.html"
}

View File

@ -3,7 +3,7 @@ const saveBtn = document.getElementById("save");
const savedEl = document.getElementById("saved");
chrome.storage.sync.get(["serverUrl"], ({ serverUrl }) => {
input.value = serverUrl ?? "http://localhost:3000";
input.value = serverUrl ?? "http://localhost:5173";
});
saveBtn.addEventListener("click", async () => {
@ -21,7 +21,16 @@ saveBtn.addEventListener("click", async () => {
await chrome.storage.sync.set({ serverUrl: origin });
const granted = await chrome.permissions.request({ origins: [`${origin}/*`] });
const url = new URL(origin);
const port = url.port ? `:${url.port}` : "";
const origins = new Set([`${origin}/*`]);
if (url.hostname === "localhost") {
origins.add(`${url.protocol}//127.0.0.1${port}/*`);
} else if (url.hostname === "127.0.0.1") {
origins.add(`${url.protocol}//localhost${port}/*`);
}
const granted = await chrome.permissions.request({ origins: [...origins] });
if (granted) {
try {
await chrome.scripting.unregisterContentScripts({ ids: ["haixun-bridge"] });
@ -31,12 +40,29 @@ saveBtn.addEventListener("click", async () => {
await chrome.scripting.registerContentScripts([
{
id: "haixun-bridge",
matches: [`${origin}/*`],
matches: [...origins].map((item) => item.replace(/\/\*$/, "/*")),
js: ["content-haixun.js"],
runAt: "document_idle",
runAt: "document_start",
},
]);
savedEl.textContent = `已儲存:${origin}(已啟用網頁同步)`;
const tabQueries = [...origins].map((pattern) => chrome.tabs.query({ url: pattern }));
await Promise.all(tabQueries).then(async (groups) => {
const tabs = groups.flat();
const seen = new Set();
for (const tab of tabs) {
if (!tab.id || seen.has(tab.id)) continue;
seen.add(tab.id);
try {
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-haixun.js"],
});
} catch {
// ignore
}
}
});
savedEl.textContent = `已儲存:${origin}(已注入網頁橋接)`;
savedEl.style.color = "#166534";
} else {
savedEl.textContent = "已儲存網址,但未授權存取該網站(同步時會再詢問)";

View File

@ -51,8 +51,8 @@
<h1>同步 Threads Session</h1>
<p>
1. 在 Chrome 登入 threads.com<br />
2. 在巡樓側欄切換到目標帳號<br />
3. 按下方按鈕同步到 server
2. 開啟巡樓網頁並登入、切換帳號<br />
3. 按下方按鈕(會自動讀取巡樓 JWT
</p>
<button id="sync">同步到巡樓</button>
<div id="status"></div>

View File

@ -15,16 +15,24 @@ syncBtn.addEventListener("click", async () => {
const response = await chrome.runtime.sendMessage({
action: "sync",
serverUrl,
apiVersion: "go-v1",
});
if (response?.valid) {
if (!response) {
throw new Error(
chrome.runtime.lastError?.message ??
"擴充背景程序無回應。請到 chrome://extensions 重新載入擴充"
);
}
if (response.success !== false && response.valid !== false) {
setStatus(
response.username
? `成功:@${response.username}\n${response.message ?? ""}`
: response.message ?? "同步成功"
);
} else {
setStatus(response?.message ?? "同步失敗", true);
setStatus(response.message ?? "同步失敗", true);
}
} catch (error) {
setStatus(error instanceof Error ? error.message : "同步失敗", true);

View File

@ -1,17 +1,69 @@
import { buildStorageState } from "./storage-state.js";
const CONTENT_SCRIPT_ID = "haixun-bridge";
const GO_API_SUCCESS_CODE = 102000;
const DEV_WEB_PORTS = new Set(["3000", "4173", "5173"]);
async function getHaixunSessionToken(serverOrigin) {
const cookie = await chrome.cookies.get({
url: serverOrigin,
name: "haixun_session",
});
return cookie?.value ?? null;
function unwrapGoApiResponse(raw) {
if (raw && typeof raw === "object" && "code" in raw) {
if (raw.code !== GO_API_SUCCESS_CODE) {
throw new Error(raw.message || `API error ${raw.code}`);
}
return raw.data && typeof raw.data === "object" ? raw.data : {};
}
return raw && typeof raw === "object" ? raw : {};
}
function parseApiError(raw, status) {
if (raw && typeof raw === "object") {
if (typeof raw.message === "string" && raw.message.trim()) return raw.message;
if (typeof raw.error === "string" && raw.error.trim()) return raw.error;
}
return `匯入失敗HTTP ${status}`;
}
function equivalentOrigins(origin) {
const url = new URL(origin);
const port = url.port ? `:${url.port}` : "";
const origins = new Set([url.origin]);
if (url.hostname === "localhost") {
origins.add(`${url.protocol}//127.0.0.1${port}`);
} else if (url.hostname === "127.0.0.1") {
origins.add(`${url.protocol}//localhost${port}`);
}
return [...origins];
}
function resolveApiBase(serverUrl) {
const url = new URL(serverUrl);
if (DEV_WEB_PORTS.has(url.port)) {
// Prefer 127.0.0.1 to match backend default bind address.
const host = url.hostname === "localhost" ? "127.0.0.1" : url.hostname;
return `${url.protocol}//${host}:8890`;
}
return url.origin;
}
async function requestHostPermission(serverOrigin) {
const origins = equivalentOrigins(serverOrigin).map((item) => `${item}/*`);
const granted = await chrome.permissions.contains({ origins });
if (granted) return true;
return chrome.permissions.request({ origins });
}
async function queryHaixunTabs(serverOrigin) {
const tabsById = new Map();
for (const origin of equivalentOrigins(serverOrigin)) {
const tabs = await chrome.tabs.query({ url: `${origin}/*` });
for (const tab of tabs) {
if (tab.id != null) tabsById.set(tab.id, tab);
}
}
return [...tabsById.values()];
}
async function ensureContentScript(serverOrigin) {
const pattern = `${serverOrigin}/*`;
const patterns = equivalentOrigins(serverOrigin).map((item) => `${item}/*`);
const existing = await chrome.scripting.getRegisteredContentScripts();
if (existing.some((script) => script.id === CONTENT_SCRIPT_ID)) {
return;
@ -20,49 +72,108 @@ async function ensureContentScript(serverOrigin) {
await chrome.scripting.registerContentScripts([
{
id: CONTENT_SCRIPT_ID,
matches: [pattern],
matches: patterns,
js: ["content-haixun.js"],
runAt: "document_idle",
runAt: "document_start",
},
]);
}
async function requestHostPermission(serverOrigin) {
const granted = await chrome.permissions.contains({
origins: [`${serverOrigin}/*`],
});
if (granted) return true;
return chrome.permissions.request({ origins: [`${serverOrigin}/*`] });
async function injectBridgeIntoTabs(serverOrigin) {
const tabs = await queryHaixunTabs(serverOrigin);
for (const tab of tabs) {
if (!tab.id) continue;
try {
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-haixun.js"],
});
} catch {
// Ignore duplicate injection / restricted pages.
}
}
}
export async function syncThreadsSession(serverUrl, accountId) {
async function readAuthFromHaixunTab(serverOrigin) {
const tabs = await queryHaixunTabs(serverOrigin);
for (const tab of tabs) {
if (!tab.id) continue;
const [result] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
const pathname = location.pathname ?? "";
const fromPath = pathname.match(/\/threads\/([^/]+)/)?.[1] ?? "";
return {
accessToken: localStorage.getItem("haixun.access_token") ?? "",
accountId:
fromPath || localStorage.getItem("haixun.active_threads_account_id") || "",
origin: location.origin,
};
},
});
const payload = result?.result;
if (payload?.accessToken) {
return payload;
}
}
return null;
}
export async function syncThreadsSessionToGo(serverUrl, accountId, accessToken) {
const origin = new URL(serverUrl).origin;
const sessionToken = await getHaixunSessionToken(origin);
const apiBase = resolveApiBase(serverUrl);
if (!sessionToken) {
throw new Error(`請先在 Chrome 開啟並登入 ${origin}`);
if (!accountId) {
throw new Error(
"缺少經營帳號 ID。請在巡樓頂部切換帳號或開啟 /threads/:id/connections 後再同步"
);
}
if (!accessToken) {
const hint = equivalentOrigins(origin).join(" 或 ");
throw new Error(
`找不到巡樓登入狀態。請在 Chrome 開啟並登入 ${hint}(需為新版巡樓 :5173不是舊版 :3000`
);
}
const storageState = await buildStorageState();
let storageState;
try {
storageState = await buildStorageState();
} catch (error) {
throw error;
}
const res = await fetch(`${origin}/api/session/import`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `haixun_session=${sessionToken}`,
},
body: JSON.stringify({
storageState,
...(accountId ? { accountId } : {}),
}),
});
let res;
try {
res = await fetch(
`${apiBase}/api/v1/threads-accounts/${encodeURIComponent(accountId)}/session/import`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ storageState }),
}
);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
if (/fetch|network|failed/i.test(msg)) {
throw new Error(
`無法連線後端 ${apiBase}。請確認已執行 make run或 backend:8d且 :8890 有在跑`
);
}
throw error;
}
const data = await res.json().catch(() => ({}));
const raw = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(data.error ?? data.message ?? `匯入失敗HTTP ${res.status}`);
throw new Error(parseApiError(raw, res.status));
}
const data = unwrapGoApiResponse(raw);
if (data.valid === false) {
throw new Error(data.message || "Session 驗證失敗");
}
return data;
}
@ -74,15 +185,19 @@ async function resolveServerUrl(partial) {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const activeUrl = tabs[0]?.url;
if (activeUrl && !activeUrl.includes("threads.com") && !activeUrl.includes("threads.net")) {
if (activeUrl) {
try {
return new URL(activeUrl).origin;
const origin = new URL(activeUrl).origin;
const port = new URL(activeUrl).port;
if (DEV_WEB_PORTS.has(port) || activeUrl.includes("/threads/")) {
return origin;
}
} catch {
// ignore
}
}
throw new Error("請在擴充功能選項設定巡樓網址,或先開啟巡樓分頁");
throw new Error("請在擴充功能選項設定巡樓網址(例如 http://localhost:5173,或先開啟巡樓分頁");
}
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
@ -97,8 +212,27 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
}
await ensureContentScript(serverUrl);
const result = await syncThreadsSession(serverUrl, message.accountId);
sendResponse({ success: true, ...result });
await injectBridgeIntoTabs(serverUrl);
let accountId = message.accountId ?? "";
let accessToken = message.accessToken ?? "";
if (!accountId || !accessToken) {
const auth = await readAuthFromHaixunTab(serverUrl);
if (auth) {
accountId = accountId || auth.accountId;
accessToken = accessToken || auth.accessToken;
}
}
const result = await syncThreadsSessionToGo(serverUrl, accountId, accessToken);
sendResponse({
success: true,
valid: result.valid !== false,
synced: result.synced ?? true,
account_id: result.account_id ?? result.accountId,
username: result.username,
message: result.message || "Chrome session 已同步",
});
} catch (error) {
sendResponse({
success: false,
@ -114,6 +248,6 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
chrome.runtime.onInstalled.addListener(async () => {
const { serverUrl } = await chrome.storage.sync.get(["serverUrl"]);
if (!serverUrl) {
await chrome.storage.sync.set({ serverUrl: "http://localhost:3000" });
await chrome.storage.sync.set({ serverUrl: "http://localhost:5173" });
}
});

View File

@ -1,6 +1,18 @@
/** @typedef {{ name: string; value: string; domain: string; path: string; expires: number; httpOnly: boolean; secure: boolean; sameSite: string }} PlaywrightCookie */
const COOKIE_DOMAINS = ["threads.com", "instagram.com", "facebook.com"];
const COOKIE_DOMAINS = [
"threads.com",
"threads.net",
"instagram.com",
"facebook.com",
];
const THREADS_TAB_URLS = [
"https://www.threads.com/*",
"https://threads.com/*",
"https://www.threads.net/*",
"https://threads.net/*",
];
function mapSameSite(sameSite) {
if (sameSite === "no_restriction") return "None";
@ -38,13 +50,27 @@ export async function collectThreadsCookies() {
}
}
// Some Chrome builds store Meta login cookies only on exact host URLs.
const urls = [
"https://www.threads.com/",
"https://www.threads.net/",
"https://www.instagram.com/",
];
for (const url of urls) {
const batch = await chrome.cookies.getAll({ url });
for (const cookie of batch) {
const key = `${cookie.domain}|${cookie.path}|${cookie.name}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(toPlaywrightCookie(cookie));
}
}
return merged;
}
export async function collectThreadsLocalStorage() {
const tabs = await chrome.tabs.query({
url: ["https://www.threads.com/*", "https://threads.com/*", "https://www.threads.net/*"],
});
const tabs = await chrome.tabs.query({ url: THREADS_TAB_URLS });
if (!tabs.length || tabs[0].id == null) {
return [];
@ -76,7 +102,9 @@ export async function buildStorageState() {
const origins = await collectThreadsLocalStorage();
if (!cookies.length) {
throw new Error("找不到 Threads / Instagram cookies。請先在 Chrome 登入 threads.com");
throw new Error(
"找不到 Threads / Instagram cookies。請先在 Chrome 開啟 threads.com 或 threads.net 並完成登入"
);
}
return JSON.stringify({ cookies, origins });

View File

@ -0,0 +1,9 @@
.run
.git
web/node_modules
# web/dist 保留給 deploy/Dockerfile.web.static本機 make web-build 後 COPY
worker/node_modules
**/*_test.go
**/.DS_Store
*.md
!deploy/**

10
haixun-backend/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="userId" value="73c9beac:19eed3331be:-7ebe" />
</MTProjectMetadataState>
</option>
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/haixun-backend.iml" filepath="$PROJECT_DIR$/.idea/haixun-backend.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -0,0 +1 @@
51754

View File

@ -0,0 +1,578 @@
2026/06/25 22:36:59 job scheduler started: holder=wangxinghuadeMacBook-Pro-204.local-scheduler interval=1m0s
Starting backend backend at 0.0.0.0:8890...
2026/06/25 22:36:59 job reaper started: interval=30s
2026/06/25 22:36:59 job worker started: id=wangxinghuadeMacBook-Pro-204.local-go-worker type=go
{"@timestamp":"2026-06-25T22:37:00.639+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/health - 127.0.0.1:57390 - curl/8.7.1","duration":"0.4ms","level":"info","span":"4bffe3c2c4ba35e6","trace":"7a1ff398e837e440d1d5a97a25b10a00"}
{"@timestamp":"2026-06-25T22:37:00.652+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/health - 127.0.0.1:57391 - curl/8.7.1","duration":"0.1ms","level":"info","span":"ee43f9c9a42a7994","trace":"469fec644754be58fcec69b2c6505762"}
{"@timestamp":"2026-06-25T22:37:03.742+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2090.9ms)","duration":"2090.9ms","level":"slow","span":"5a141581fbabb8e6","trace":"f950f7f310a306bbda8bc9a7c9a76193"}
{"@timestamp":"2026-06-25T22:37:03.742+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2090.9ms","level":"info","span":"5a141581fbabb8e6","trace":"f950f7f310a306bbda8bc9a7c9a76193"}
{"@timestamp":"2026-06-25T22:37:08.860+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2105.8ms)","duration":"2105.8ms","level":"slow","span":"dca65c27e7bc6d2b","trace":"be3bdf975b23f09e43d7579140808a9f"}
{"@timestamp":"2026-06-25T22:37:08.860+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2105.8ms","level":"info","span":"dca65c27e7bc6d2b","trace":"be3bdf975b23f09e43d7579140808a9f"}
{"@timestamp":"2026-06-25T22:37:13.967+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2100.6ms)","duration":"2100.6ms","level":"slow","span":"38ea73334ad018ce","trace":"42a9f02bfc3a088c1c24babd75b7f620"}
{"@timestamp":"2026-06-25T22:37:13.968+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2100.6ms","level":"info","span":"38ea73334ad018ce","trace":"42a9f02bfc3a088c1c24babd75b7f620"}
{"@timestamp":"2026-06-25T22:37:18.276+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 401 - GET /api/v1/members/me - 127.0.0.1:57429 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.2ms","level":"info","span":"02035f2b81cbf5ee","trace":"ece698b63e662c0ea14f9be1bd20cca1"}
{"@timestamp":"2026-06-25T22:37:18.278+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 401 - GET /api/v1/members/me - 127.0.0.1:57430 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.0ms","level":"info","span":"6b6407cdd6bec16f","trace":"98112ba467ecd4f841c7d75b41dc4605"}
{"@timestamp":"2026-06-25T22:37:18.284+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/auth/refresh - 127.0.0.1:57431 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.1ms","level":"info","span":"ac3c542579a7e710","trace":"3b3ca79fb6b18c0e33215a2c4f407494"}
{"@timestamp":"2026-06-25T22:37:18.290+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 404 - GET /api/v1/members/me - 127.0.0.1:57433 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"6502676d5f8f142e","trace":"29c27f53a4d0f2b3316ad8f86d8ad6ae"}
{"@timestamp":"2026-06-25T22:37:18.295+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 404 - GET /api/v1/members/me - 127.0.0.1:57434 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"f6e9f734564c161f","trace":"8dd65cde984f3324f9032dbb2c616946"}
{"@timestamp":"2026-06-25T22:37:19.065+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2090.3ms)","duration":"2090.3ms","level":"slow","span":"831c4b877926f605","trace":"078f5be4edb0e8537cda154a613c7e07"}
{"@timestamp":"2026-06-25T22:37:19.065+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2090.3ms","level":"info","span":"831c4b877926f605","trace":"078f5be4edb0e8537cda154a613c7e07"}
{"@timestamp":"2026-06-25T22:37:24.156+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2085.4ms)","duration":"2085.4ms","level":"slow","span":"b0487145d7c69a06","trace":"c0d9d7506009e7585e5e979be00d34ed"}
{"@timestamp":"2026-06-25T22:37:24.157+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2085.4ms","level":"info","span":"b0487145d7c69a06","trace":"c0d9d7506009e7585e5e979be00d34ed"}
{"@timestamp":"2026-06-25T22:37:28.886+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 401 - POST /api/v1/auth/login - 127.0.0.1:57456 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.5ms","level":"info","span":"23e0fd735a1d1ea4","trace":"e65c91afb19a6770475b22cceea95276"}
{"@timestamp":"2026-06-25T22:37:29.187+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2022.9ms)","duration":"2022.9ms","level":"slow","span":"5432e8a2f1c4d659","trace":"4b020be2d44a8196625c39905eef367b"}
{"@timestamp":"2026-06-25T22:37:29.187+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2022.9ms","level":"info","span":"5432e8a2f1c4d659","trace":"4b020be2d44a8196625c39905eef367b"}
{"@timestamp":"2026-06-25T22:37:34.202+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2008.0ms)","duration":"2008.0ms","level":"slow","span":"be6b96cfe3ca2712","trace":"d020ac09e28a3eac243097684539434c"}
{"@timestamp":"2026-06-25T22:37:34.202+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2008.0ms","level":"info","span":"be6b96cfe3ca2712","trace":"d020ac09e28a3eac243097684539434c"}
{"@timestamp":"2026-06-25T22:37:39.310+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2101.8ms)","duration":"2101.8ms","level":"slow","span":"216c3bfdec94636f","trace":"4234cf277dab232fd5eaea649cb0b001"}
{"@timestamp":"2026-06-25T22:37:39.310+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2101.8ms","level":"info","span":"216c3bfdec94636f","trace":"4234cf277dab232fd5eaea649cb0b001"}
{"@timestamp":"2026-06-25T22:37:44.328+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2010.6ms)","duration":"2010.6ms","level":"slow","span":"3a35cfda78f7d970","trace":"aae042cc33f8205396332c567ad423cc"}
{"@timestamp":"2026-06-25T22:37:44.328+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2010.6ms","level":"info","span":"3a35cfda78f7d970","trace":"aae042cc33f8205396332c567ad423cc"}
{"@timestamp":"2026-06-25T22:37:49.416+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2079.2ms)","duration":"2079.2ms","level":"slow","span":"7393aae253f93046","trace":"18654a60e0f292d87c386b0074cb48b3"}
{"@timestamp":"2026-06-25T22:37:49.417+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2079.2ms","level":"info","span":"7393aae253f93046","trace":"18654a60e0f292d87c386b0074cb48b3"}
{"@timestamp":"2026-06-25T22:37:54.488+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2065.0ms)","duration":"2065.0ms","level":"slow","span":"ce36d225c27e3ed5","trace":"48d9a88457a2d651de53e5b061483d44"}
{"@timestamp":"2026-06-25T22:37:54.488+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2065.0ms","level":"info","span":"ce36d225c27e3ed5","trace":"48d9a88457a2d651de53e5b061483d44"}
{"@timestamp":"2026-06-25T22:37:57.804+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=2.9Mi, TotalAlloc=4.0Mi, Sys=18.2Mi, NumGC=2","level":"stat"}
{"@timestamp":"2026-06-25T22:37:59.600+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2105.4ms)","duration":"2105.4ms","level":"slow","span":"1ca5fe8c60e0bd46","trace":"e294a83309bbc95e8f7bfde6ff01377a"}
{"@timestamp":"2026-06-25T22:37:59.600+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2105.4ms","level":"info","span":"1ca5fe8c60e0bd46","trace":"e294a83309bbc95e8f7bfde6ff01377a"}
{"@timestamp":"2026-06-25T22:37:59.872+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 20, pass: 20, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:38:00.641+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.3/s, drops: 0, avg time: 1243.7ms, med: 2022.8ms, 90th: 2105.3ms, 99th: 2105.6ms, 99.9th: 2105.6ms","level":"stat"}
{"@timestamp":"2026-06-25T22:38:04.618+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2013.1ms)","duration":"2013.1ms","level":"slow","span":"44b7b40ed1490e96","trace":"90e43d88861d3793e792812d163358a1"}
{"@timestamp":"2026-06-25T22:38:04.618+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2013.1ms","level":"info","span":"44b7b40ed1490e96","trace":"90e43d88861d3793e792812d163358a1"}
{"@timestamp":"2026-06-25T22:38:09.710+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2086.5ms)","duration":"2086.5ms","level":"slow","span":"ced1f0bbfe3b862a","trace":"61cdd114f1d91e9909c955c9be9e1fa3"}
{"@timestamp":"2026-06-25T22:38:09.711+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2086.5ms","level":"info","span":"ced1f0bbfe3b862a","trace":"61cdd114f1d91e9909c955c9be9e1fa3"}
{"@timestamp":"2026-06-25T22:38:14.728+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2013.2ms)","duration":"2013.2ms","level":"slow","span":"3c2ae0da8849f1ad","trace":"4b8bff95997e89c0336006ee3e6b7238"}
{"@timestamp":"2026-06-25T22:38:14.728+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2013.2ms","level":"info","span":"3c2ae0da8849f1ad","trace":"4b8bff95997e89c0336006ee3e6b7238"}
{"@timestamp":"2026-06-25T22:38:19.815+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2081.7ms)","duration":"2081.7ms","level":"slow","span":"4731d9a763a17760","trace":"d128fd1a16cdf6b72f98c30eb5d3d716"}
{"@timestamp":"2026-06-25T22:38:19.815+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2081.7ms","level":"info","span":"4731d9a763a17760","trace":"d128fd1a16cdf6b72f98c30eb5d3d716"}
{"@timestamp":"2026-06-25T22:38:24.916+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2095.4ms)","duration":"2095.4ms","level":"slow","span":"85c6508651c2dbc7","trace":"6909bd790c3ac705bdc37505a335bd29"}
{"@timestamp":"2026-06-25T22:38:24.916+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2095.4ms","level":"info","span":"85c6508651c2dbc7","trace":"6909bd790c3ac705bdc37505a335bd29"}
{"@timestamp":"2026-06-25T22:38:30.014+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2093.6ms)","duration":"2093.6ms","level":"slow","span":"2412c64c38e707d2","trace":"6cb17b812df432a52206f44509beb790"}
{"@timestamp":"2026-06-25T22:38:30.014+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2093.6ms","level":"info","span":"2412c64c38e707d2","trace":"6cb17b812df432a52206f44509beb790"}
{"@timestamp":"2026-06-25T22:38:35.119+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2101.0ms)","duration":"2101.0ms","level":"slow","span":"d86c8541c2444c13","trace":"b4f2951a4442de552a7dcfd8e9287a5f"}
{"@timestamp":"2026-06-25T22:38:35.119+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2101.0ms","level":"info","span":"d86c8541c2444c13","trace":"b4f2951a4442de552a7dcfd8e9287a5f"}
{"@timestamp":"2026-06-25T22:38:40.191+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2070.2ms)","duration":"2070.2ms","level":"slow","span":"f7259a4f6b8bbf8d","trace":"03d2373d8296f80a1b76545392c2ec3d"}
{"@timestamp":"2026-06-25T22:38:40.191+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2070.2ms","level":"info","span":"f7259a4f6b8bbf8d","trace":"03d2373d8296f80a1b76545392c2ec3d"}
{"@timestamp":"2026-06-25T22:38:45.296+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2101.6ms)","duration":"2101.6ms","level":"slow","span":"dca526a01941dbd6","trace":"659e6bb8dce652ef9bd976729f4209c3"}
{"@timestamp":"2026-06-25T22:38:45.296+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2101.6ms","level":"info","span":"dca526a01941dbd6","trace":"659e6bb8dce652ef9bd976729f4209c3"}
{"@timestamp":"2026-06-25T22:38:50.300+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2001.3ms)","duration":"2001.3ms","level":"slow","span":"cff7ae1fdf972587","trace":"acdeaf3c4ce9c16db2d7ef2241083b42"}
{"@timestamp":"2026-06-25T22:38:50.300+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2001.3ms","level":"info","span":"cff7ae1fdf972587","trace":"acdeaf3c4ce9c16db2d7ef2241083b42"}
{"@timestamp":"2026-06-25T22:38:55.309+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2006.3ms)","duration":"2006.3ms","level":"slow","span":"5026218dc41dd620","trace":"f39f49dc1a2ed6b572ae0308f4df7cc4"}
{"@timestamp":"2026-06-25T22:38:55.309+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2006.3ms","level":"info","span":"5026218dc41dd620","trace":"f39f49dc1a2ed6b572ae0308f4df7cc4"}
{"@timestamp":"2026-06-25T22:38:57.805+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.1Mi, TotalAlloc=4.2Mi, Sys=18.2Mi, NumGC=2","level":"stat"}
{"@timestamp":"2026-06-25T22:38:59.872+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 12, pass: 11, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:39:00.403+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2091.9ms)","duration":"2091.9ms","level":"slow","span":"60664a8afbd2ec75","trace":"1e634d893ab4d3344a4242197811c247"}
{"@timestamp":"2026-06-25T22:39:00.403+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2091.9ms","level":"info","span":"60664a8afbd2ec75","trace":"1e634d893ab4d3344a4242197811c247"}
{"@timestamp":"2026-06-25T22:39:00.642+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.2/s, drops: 0, avg time: 2062.8ms, med: 2086.3ms, 90th: 2101.5ms, 99th: 2101.5ms, 99.9th: 2101.5ms","level":"stat"}
{"@timestamp":"2026-06-25T22:39:05.421+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2016.0ms)","duration":"2016.0ms","level":"slow","span":"20be21a9dfb9ea82","trace":"b471de21032da8cff8f0963b8269750f"}
{"@timestamp":"2026-06-25T22:39:05.421+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2016.0ms","level":"info","span":"20be21a9dfb9ea82","trace":"b471de21032da8cff8f0963b8269750f"}
{"@timestamp":"2026-06-25T22:39:10.512+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2088.3ms)","duration":"2088.3ms","level":"slow","span":"e8a0745c45bc9efa","trace":"f03584de91681a76cc2199e7449a0d7f"}
{"@timestamp":"2026-06-25T22:39:10.512+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2088.3ms","level":"info","span":"e8a0745c45bc9efa","trace":"f03584de91681a76cc2199e7449a0d7f"}
{"@timestamp":"2026-06-25T22:39:15.605+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2088.8ms)","duration":"2088.8ms","level":"slow","span":"d67885f2bde649e4","trace":"00a3299b4db2f018f0a45e701d1021d5"}
{"@timestamp":"2026-06-25T22:39:15.605+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2088.8ms","level":"info","span":"d67885f2bde649e4","trace":"00a3299b4db2f018f0a45e701d1021d5"}
{"@timestamp":"2026-06-25T22:39:20.687+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2076.1ms)","duration":"2076.1ms","level":"slow","span":"4db6a91effb47afc","trace":"57561dc4736af3db2edae81c61407f2a"}
{"@timestamp":"2026-06-25T22:39:20.687+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2076.1ms","level":"info","span":"4db6a91effb47afc","trace":"57561dc4736af3db2edae81c61407f2a"}
{"@timestamp":"2026-06-25T22:39:20.842+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 401 - POST /api/v1/auth/login - 127.0.0.1:57632 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"a162b0f922b37a76","trace":"c63495c764a5b38dbf0f67f4c940b6cc"}
{"@timestamp":"2026-06-25T22:39:25.757+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2068.3ms)","duration":"2068.3ms","level":"slow","span":"e39aca56e802d025","trace":"9b018a57de3f55efa8490e6df604807f"}
{"@timestamp":"2026-06-25T22:39:25.758+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2068.3ms","level":"info","span":"e39aca56e802d025","trace":"9b018a57de3f55efa8490e6df604807f"}
{"@timestamp":"2026-06-25T22:39:30.831+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2072.2ms)","duration":"2072.2ms","level":"slow","span":"e03bcc45c26f574f","trace":"d5b30244c2126da93ad00394c1345024"}
{"@timestamp":"2026-06-25T22:39:30.831+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2072.2ms","level":"info","span":"e03bcc45c26f574f","trace":"d5b30244c2126da93ad00394c1345024"}
{"@timestamp":"2026-06-25T22:39:31.729+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 401 - POST /api/v1/auth/login - 127.0.0.1:57649 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"8.5ms","level":"info","span":"2895942d151cf12c","trace":"e092ea478791f7a48282bb19300e8f3a"}
2026/06/25 22:39:35 job worker claim error: dial tcp 127.0.0.1:6379: connect: connection refused
{"@timestamp":"2026-06-25T22:39:35.553+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 500 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(1719.6ms)","duration":"1719.6ms","level":"slow","span":"77379f56f93dca72","trace":"6f4670671b7e7db327a043e041226050"}
{"@timestamp":"2026-06-25T22:39:35.553+08:00","caller":"handler/loghandler.go:169","content":"[HTTP] 500 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node\nPOST /api/v1/internal/workers/jobs/claim HTTP/1.1\r\nHost: 127.0.0.1:8890\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: *\r\nConnection: keep-alive\r\nContent-Length: 62\r\nContent-Type: application/json\r\nSec-Fetch-Mode: cors\r\nUser-Agent: node\r\n\r\n{\"worker_type\":\"node\",\"worker_id\":\"local-style-8d-node-51888\"}","duration":"1719.6ms","level":"error","span":"77379f56f93dca72","trace":"6f4670671b7e7db327a043e041226050"}
{"@timestamp":"2026-06-25T22:39:40.620+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2060.9ms)","duration":"2060.9ms","level":"slow","span":"8edb295e8ef97416","trace":"d8b4c3b589dbca9f6bb154b4b810747a"}
{"@timestamp":"2026-06-25T22:39:40.620+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2060.9ms","level":"info","span":"8edb295e8ef97416","trace":"d8b4c3b589dbca9f6bb154b4b810747a"}
{"@timestamp":"2026-06-25T22:39:45.720+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2097.5ms)","duration":"2097.5ms","level":"slow","span":"0813261c0378f2cf","trace":"74e4587c50f98e894a2c84353f6a9448"}
{"@timestamp":"2026-06-25T22:39:45.720+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2097.5ms","level":"info","span":"0813261c0378f2cf","trace":"74e4587c50f98e894a2c84353f6a9448"}
{"@timestamp":"2026-06-25T22:39:50.805+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2082.5ms)","duration":"2082.5ms","level":"slow","span":"ce911cd6632ebc4f","trace":"07ff8a1a0a0f81b44960c32fb9555e3e"}
{"@timestamp":"2026-06-25T22:39:50.805+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2082.5ms","level":"info","span":"ce911cd6632ebc4f","trace":"07ff8a1a0a0f81b44960c32fb9555e3e"}
{"@timestamp":"2026-06-25T22:39:53.134+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/auth/login - 127.0.0.1:57702 - curl/8.7.1","duration":"49.6ms","level":"info","span":"f66272c787c550bc","trace":"d4370226b2e82fc8c709a12323fa3ba1"}
{"@timestamp":"2026-06-25T22:39:55.868+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2058.8ms)","duration":"2058.8ms","level":"slow","span":"4f7f26b998c2a9c1","trace":"59cb83493ae4282158523fde974e0c25"}
{"@timestamp":"2026-06-25T22:39:55.868+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2058.8ms","level":"info","span":"4f7f26b998c2a9c1","trace":"59cb83493ae4282158523fde974e0c25"}
{"@timestamp":"2026-06-25T22:39:57.805+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.3Mi, TotalAlloc=5.3Mi, Sys=18.2Mi, NumGC=3","level":"stat"}
{"@timestamp":"2026-06-25T22:39:59.873+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 15, pass: 15, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:40:00.642+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.2/s, drops: 0, avg time: 1606.2ms, med: 2068.3ms, 90th: 2097.5ms, 99th: 2097.5ms, 99.9th: 2097.5ms","level":"stat"}
{"@timestamp":"2026-06-25T22:40:00.893+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2022.1ms)","duration":"2022.1ms","level":"slow","span":"7cdc995209691407","trace":"f972425d3d4d13b4d962b68e16d2d6e7"}
{"@timestamp":"2026-06-25T22:40:00.893+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2022.1ms","level":"info","span":"7cdc995209691407","trace":"f972425d3d4d13b4d962b68e16d2d6e7"}
{"@timestamp":"2026-06-25T22:40:05.928+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2031.7ms)","duration":"2031.7ms","level":"slow","span":"1662b5951eeee78f","trace":"28d64fd50a3527876d2d1c9f2cc78cd0"}
{"@timestamp":"2026-06-25T22:40:05.928+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2031.7ms","level":"info","span":"1662b5951eeee78f","trace":"28d64fd50a3527876d2d1c9f2cc78cd0"}
{"@timestamp":"2026-06-25T22:40:10.956+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.4ms)","duration":"2025.4ms","level":"slow","span":"4a575980ccc45452","trace":"163db7e95df19957d54725f55877d4ba"}
{"@timestamp":"2026-06-25T22:40:10.956+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.4ms","level":"info","span":"4a575980ccc45452","trace":"163db7e95df19957d54725f55877d4ba"}
{"@timestamp":"2026-06-25T22:40:13.741+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/health - 127.0.0.1:57732 - curl/8.7.1","duration":"0.1ms","level":"info","span":"6aebbdee2f6e7938","trace":"bc0a12eb8c5cb0af9a4e72a8eee986f4"}
{"@timestamp":"2026-06-25T22:40:13.802+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/health - 127.0.0.1:57734 - curl/8.7.1","duration":"0.0ms","level":"info","span":"154560de0c2bd763","trace":"7c8660da08e13a01733e9b730e2af501"}
{"@timestamp":"2026-06-25T22:40:15.963+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2003.8ms)","duration":"2003.8ms","level":"slow","span":"4375bc8a77ee0787","trace":"ffca5616bf5dd1f9344f74d204edad1e"}
{"@timestamp":"2026-06-25T22:40:15.963+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2003.8ms","level":"info","span":"4375bc8a77ee0787","trace":"ffca5616bf5dd1f9344f74d204edad1e"}
{"@timestamp":"2026-06-25T22:40:21.009+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2042.7ms)","duration":"2042.7ms","level":"slow","span":"e0b17f02ab997730","trace":"bc7e0d6d18288b7154098384df3402aa"}
{"@timestamp":"2026-06-25T22:40:21.009+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2042.7ms","level":"info","span":"e0b17f02ab997730","trace":"bc7e0d6d18288b7154098384df3402aa"}
{"@timestamp":"2026-06-25T22:40:22.845+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/auth/login - 127.0.0.1:57757 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"74.5ms","level":"info","span":"2334163fc22e6e94","trace":"e6f4a4289ba57ebbda9a05042d2bb8d0"}
{"@timestamp":"2026-06-25T22:40:22.882+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:57759 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.7ms","level":"info","span":"f580c26c2c665f7d","trace":"1a304ed9ebc528444be7f2a4af3d81d6"}
{"@timestamp":"2026-06-25T22:40:22.901+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57768 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"47e86e6a4324d9cc","trace":"f65dcadff39442abfaedd61f7bc1f143"}
{"@timestamp":"2026-06-25T22:40:22.901+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/job/schedules?page=1&pageSize=100 - 127.0.0.1:57766 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.7ms","level":"info","span":"e6dcaaf5ee2f6810","trace":"2908339ad793013ac3ef4a05baae3013"}
{"@timestamp":"2026-06-25T22:40:22.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=50 - 127.0.0.1:57765 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"e1baf172f63e5ad7","trace":"e1076fb7cf7fa5796b83895cf4d69397"}
{"@timestamp":"2026-06-25T22:40:22.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57767 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"0751aa99f059b310","trace":"ada9f3f02a3fa65f22cb6d4503e5ac5a"}
{"@timestamp":"2026-06-25T22:40:22.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57769 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"70f7e697d07477c3","trace":"b6bc47c6501f10fb88b6ddaf0ec3081b"}
{"@timestamp":"2026-06-25T22:40:22.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57777 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"93bdf9365b9e4128","trace":"463044a469176fc505cd4b836fae8e96"}
{"@timestamp":"2026-06-25T22:40:22.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/job/schedules?page=1&pageSize=100 - 127.0.0.1:57778 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"c5bf2c2141770be6","trace":"64852c24cc86b378ce486942bcdc3157"}
{"@timestamp":"2026-06-25T22:40:22.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57781 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"08c4c5bb7dcb03f2","trace":"e684e8f5b3111988947877d3e86ee49e"}
{"@timestamp":"2026-06-25T22:40:22.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57780 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"5084859471430a10","trace":"b3ce01438c1b0573a34eee82f611646f"}
{"@timestamp":"2026-06-25T22:40:22.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=50 - 127.0.0.1:57779 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.0ms","level":"info","span":"d9ef75b9cf4b6e12","trace":"f93f26100e019470ae57c1cc147c332c"}
{"@timestamp":"2026-06-25T22:40:22.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57784 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.5ms","level":"info","span":"2a2cfb7bc80ecdd6","trace":"bf4e9325fdac6ca3a8737c708ea7734e"}
{"@timestamp":"2026-06-25T22:40:22.913+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57786 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.0ms","level":"info","span":"7426dda6d11304d5","trace":"73bc0ff4de0f9a7bd57aad7169054b99"}
{"@timestamp":"2026-06-25T22:40:23.012+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57793 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"8219d45f3c30b841","trace":"5158dbf50ff8fe8d2d15b9454aa5bacd"}
{"@timestamp":"2026-06-25T22:40:24.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57794 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"56a388bf7624b77b","trace":"82834b47c813d94a07685d33f7d52872"}
{"@timestamp":"2026-06-25T22:40:26.014+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2004.2ms)","duration":"2004.2ms","level":"slow","span":"77a90aca043ee11a","trace":"ed7a7dbd9868b55f5a888043b97eee78"}
{"@timestamp":"2026-06-25T22:40:26.014+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2004.2ms","level":"info","span":"77a90aca043ee11a","trace":"ed7a7dbd9868b55f5a888043b97eee78"}
{"@timestamp":"2026-06-25T22:40:26.903+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57805 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"e91d52648e54869b","trace":"29033a93164952c8d302e747c48dfb89"}
{"@timestamp":"2026-06-25T22:40:27.402+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/auth/login - 127.0.0.1:57806 - curl/8.7.1","duration":"50.1ms","level":"info","span":"bd2b876839d5a720","trace":"9818ddf251c31638158e88eeac157cad"}
{"@timestamp":"2026-06-25T22:40:28.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57808 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"0a05ff93ea07c5b8","trace":"2033280ffa68711c89416e025db45d8f"}
{"@timestamp":"2026-06-25T22:40:30.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57811 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"ccb04891c369afef","trace":"6e474e07f1e4fdc5c405d93eaf73b279"}
{"@timestamp":"2026-06-25T22:40:31.041+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.2ms)","duration":"2024.2ms","level":"slow","span":"8e33a72625fd49e7","trace":"6164e0ca36b508b391b0c17d217560ef"}
{"@timestamp":"2026-06-25T22:40:31.041+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.2ms","level":"info","span":"8e33a72625fd49e7","trace":"6164e0ca36b508b391b0c17d217560ef"}
{"@timestamp":"2026-06-25T22:40:32.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57814 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"4969574f87c35014","trace":"4bfd008d23c028b1ba166fcd45ea73e2"}
{"@timestamp":"2026-06-25T22:40:34.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57822 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"a65da3d81a0b9aec","trace":"9e65ae4a5ed5e686612c6d804eb12fba"}
{"@timestamp":"2026-06-25T22:40:36.065+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2020.6ms)","duration":"2020.6ms","level":"slow","span":"54d9b67e9b97fc25","trace":"09da0bf9ddc04891856b99f9971602b8"}
{"@timestamp":"2026-06-25T22:40:36.065+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2020.6ms","level":"info","span":"54d9b67e9b97fc25","trace":"09da0bf9ddc04891856b99f9971602b8"}
{"@timestamp":"2026-06-25T22:40:36.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57824 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"dce1ca1974ce12a6","trace":"ece1b0dea0f438859c70b5ffecfc938b"}
{"@timestamp":"2026-06-25T22:40:38.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57832 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"5d5e12190e71cf83","trace":"eb21781629963dcefe79b259afa3471e"}
{"@timestamp":"2026-06-25T22:40:40.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57834 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.4ms","level":"info","span":"3cffcff0c78c68c9","trace":"accd546df06451b87b8978e1ca37dcc0"}
{"@timestamp":"2026-06-25T22:40:41.091+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.3ms)","duration":"2024.3ms","level":"slow","span":"38abd0835a87fb75","trace":"134edad7e84e81a6dad260d97c65b4f0"}
{"@timestamp":"2026-06-25T22:40:41.092+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.3ms","level":"info","span":"38abd0835a87fb75","trace":"134edad7e84e81a6dad260d97c65b4f0"}
{"@timestamp":"2026-06-25T22:40:42.909+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57837 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.6ms","level":"info","span":"97ac610b6af4b890","trace":"7b1274ca62f06ff26f926f9325890818"}
{"@timestamp":"2026-06-25T22:40:44.257+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57842 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"ce31902d730f7057","trace":"29909ea21ae3cd5ed2cb40587d5b8009"}
{"@timestamp":"2026-06-25T22:40:44.259+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57843 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.7ms","level":"info","span":"7c9a15669b51d37b","trace":"2519a38084940b6cba80bbc3dd0aa0d5"}
{"@timestamp":"2026-06-25T22:40:44.261+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57844 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.8ms","level":"info","span":"706ead99ffb6b824","trace":"c67e22b138db4b4ebedef425eebc13c5"}
{"@timestamp":"2026-06-25T22:40:44.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57845 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"05539bf99b701723","trace":"e1cd030d5e48222f57dfd388ca37364d"}
{"@timestamp":"2026-06-25T22:40:45.243+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/job/schedules?page=1&pageSize=100 - 127.0.0.1:57850 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"e34e9e19d005ab9d","trace":"4a05a8552cb1983e3f7b3151172a4924"}
{"@timestamp":"2026-06-25T22:40:45.243+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=50 - 127.0.0.1:57849 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.3ms","level":"info","span":"9f4f0b18c160a86b","trace":"43bbf7e41dfabafe91b073cf651b128c"}
{"@timestamp":"2026-06-25T22:40:45.244+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57851 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"f1d0e34bbe64eb89","trace":"da7bc551a236a6ec07e62b4bd3d3535a"}
{"@timestamp":"2026-06-25T22:40:45.247+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/job/schedules?page=1&pageSize=100 - 127.0.0.1:57855 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"b824986aa13a2ccc","trace":"b941a2047bdf36ff94911ca0c3836597"}
{"@timestamp":"2026-06-25T22:40:45.247+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=50 - 127.0.0.1:57856 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"5b6342a0ef51d614","trace":"df6ffe8045f9d533c952119b22b51e6b"}
{"@timestamp":"2026-06-25T22:40:46.118+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2022.3ms)","duration":"2022.3ms","level":"slow","span":"bc4d901593a0008d","trace":"c476d87790c35860bab28ab9ae55c6b9"}
{"@timestamp":"2026-06-25T22:40:46.118+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2022.3ms","level":"info","span":"bc4d901593a0008d","trace":"c476d87790c35860bab28ab9ae55c6b9"}
{"@timestamp":"2026-06-25T22:40:46.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57863 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"5a3c108562e6f27d","trace":"8561ce3f98fb18432b574853c3d38059"}
{"@timestamp":"2026-06-25T22:40:48.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57865 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"f68cd10918d9f42f","trace":"c7567ecfc024c65c5e4950998276f5cd"}
{"@timestamp":"2026-06-25T22:40:50.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57871 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"a10d87c4b2afddcf","trace":"7616e4b144ddf135d71840f48c9e524c"}
{"@timestamp":"2026-06-25T22:40:51.151+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2030.0ms)","duration":"2030.0ms","level":"slow","span":"9a91ce6dce362688","trace":"aa2c6319f734188ecd3b8b2439f451ad"}
{"@timestamp":"2026-06-25T22:40:51.151+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2030.0ms","level":"info","span":"9a91ce6dce362688","trace":"aa2c6319f734188ecd3b8b2439f451ad"}
{"@timestamp":"2026-06-25T22:40:52.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57873 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"b27942304affa968","trace":"70362b5b0299504614aa6b8c47aa5dd2"}
{"@timestamp":"2026-06-25T22:40:54.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57876 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"43b9aeb1dc1e60fb","trace":"47ebff3e4c5a690ef75e31d77008c4ca"}
{"@timestamp":"2026-06-25T22:40:56.181+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.4ms)","duration":"2026.4ms","level":"slow","span":"754313b3b22050f7","trace":"4336d00f1e70afb4e53a9640fa0ffc9c"}
{"@timestamp":"2026-06-25T22:40:56.181+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.4ms","level":"info","span":"754313b3b22050f7","trace":"4336d00f1e70afb4e53a9640fa0ffc9c"}
{"@timestamp":"2026-06-25T22:40:56.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57882 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"5bce3f1394cc50a4","trace":"1b631d71115536e51e6bd359f83f14aa"}
{"@timestamp":"2026-06-25T22:40:57.806+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.1Mi, TotalAlloc=8.3Mi, Sys=22.8Mi, NumGC=5","level":"stat"}
{"@timestamp":"2026-06-25T22:40:58.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57886 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"aa17c5fa79d16e15","trace":"c3d186ac5ec0e52534f979b5520e497b"}
{"@timestamp":"2026-06-25T22:40:59.874+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 56, pass: 56, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:41:00.643+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.9/s, drops: 0, avg time: 437.6ms, med: 3.2ms, 90th: 2025.3ms, 99th: 2042.6ms, 99.9th: 2042.6ms","level":"stat"}
{"@timestamp":"2026-06-25T22:41:00.914+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57889 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"11.1ms","level":"info","span":"ce18a82d4838db03","trace":"e92f93cf7d42a424532896c64211ef4c"}
{"@timestamp":"2026-06-25T22:41:01.210+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.2ms)","duration":"2026.2ms","level":"slow","span":"b1a575db68903cd3","trace":"a29307f75d22d16d49da40ad27dddda4"}
{"@timestamp":"2026-06-25T22:41:01.210+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.2ms","level":"info","span":"b1a575db68903cd3","trace":"a29307f75d22d16d49da40ad27dddda4"}
{"@timestamp":"2026-06-25T22:41:02.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57896 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"f13a8f52dd1a8a0d","trace":"e42ba8c9ceda6c8b8a34d6c2b35d36ad"}
{"@timestamp":"2026-06-25T22:41:04.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57899 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"9b63b255f7817653","trace":"9a7c465b6e5e9e3125e8f6b11ebb3fe3"}
{"@timestamp":"2026-06-25T22:41:06.241+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.2ms)","duration":"2026.2ms","level":"slow","span":"cf45498b30ce5c67","trace":"62a257a9bf5931af49d8101871991514"}
{"@timestamp":"2026-06-25T22:41:06.242+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.2ms","level":"info","span":"cf45498b30ce5c67","trace":"62a257a9bf5931af49d8101871991514"}
{"@timestamp":"2026-06-25T22:41:06.914+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57906 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"62aa5357bc208fc8","trace":"ef4aa6e2f738217f468f3535e4ca885d"}
{"@timestamp":"2026-06-25T22:41:08.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57912 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"e2c4d9ea4ca5bdc8","trace":"27319ea76279fd37d2e33dcde707af5b"}
{"@timestamp":"2026-06-25T22:41:10.914+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57916 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"10.0ms","level":"info","span":"ef3a15118abd56d7","trace":"6ded41f9dd548e0f323013c5380f76d9"}
{"@timestamp":"2026-06-25T22:41:11.271+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.7ms)","duration":"2024.7ms","level":"slow","span":"9f44c626d771824e","trace":"8ff1e51dee5d1506a332a4da45be2c5c"}
{"@timestamp":"2026-06-25T22:41:11.271+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.7ms","level":"info","span":"9f44c626d771824e","trace":"8ff1e51dee5d1506a332a4da45be2c5c"}
{"@timestamp":"2026-06-25T22:41:12.526+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/threads-accounts - 127.0.0.1:57919 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"21.0ms","level":"info","span":"0120f0d1049d805e","trace":"7668a436c05ec8121323c9f77e248a08"}
{"@timestamp":"2026-06-25T22:41:12.532+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57921 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"a1ca93ef4107a6e7","trace":"5daf795ddd766c82670701a03e87908c"}
{"@timestamp":"2026-06-25T22:41:12.533+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57923 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"8cd440474ab289d7","trace":"74012894f49e951e57a8bd66637f5877"}
{"@timestamp":"2026-06-25T22:41:12.535+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57925 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.5ms","level":"info","span":"138c6f968be08abb","trace":"21e51463ea3b90ddc29c977c6eb61fd9"}
{"@timestamp":"2026-06-25T22:41:12.536+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57927 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.4ms","level":"info","span":"1f1e8fa9f455ae17","trace":"2e43cad84e741226040b886e28d0bd44"}
{"@timestamp":"2026-06-25T22:41:12.557+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57935 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"f4c0fd1e003ca90f","trace":"6b6746afcea39508434e8bc4a235823f"}
{"@timestamp":"2026-06-25T22:41:12.557+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me/placement-settings - 127.0.0.1:57933 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"115160d91f7b613e","trace":"41f0484efd688c8c0dc816038e625eb3"}
{"@timestamp":"2026-06-25T22:41:12.558+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57932 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"044cd0572b4a06f6","trace":"0203319b8a6db9a782168c9ffafed9d3"}
{"@timestamp":"2026-06-25T22:41:12.558+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/ai-settings - 127.0.0.1:57934 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"1371b4e90dd3c0e8","trace":"9bb253431915a4b6d8bbb74e37eebfcb"}
{"@timestamp":"2026-06-25T22:41:12.559+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me/placement-settings - 127.0.0.1:57937 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.8ms","level":"info","span":"3c1a1bab2efb4d31","trace":"533bc17ace598394ce4ed13e947a3659"}
{"@timestamp":"2026-06-25T22:41:12.561+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57940 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.7ms","level":"info","span":"aeee7c2b38ebc393","trace":"ea1b7a700752b0df3683677527cdc0d1"}
{"@timestamp":"2026-06-25T22:41:12.561+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/ai-settings - 127.0.0.1:57941 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"7eafbca51f77924c","trace":"6dd25406e3d21d526eb15346f5ed31d0"}
{"@timestamp":"2026-06-25T22:41:12.563+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57943 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"b9a10aeb9ebd6c76","trace":"8f04566f8f2b2d4726dcd518917ee637"}
{"@timestamp":"2026-06-25T22:41:12.566+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57945 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"967ee58a80633514","trace":"aa45c81a410dbf5bcc01cdbeead7a4b2"}
{"@timestamp":"2026-06-25T22:41:12.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57947 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"f15ecd88be06c297","trace":"4d3cff8e0a415792c84ead8a530b203c"}
{"@timestamp":"2026-06-25T22:41:14.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57955 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"f13cff5ff2d44987","trace":"aaed610be3bde47e83d49a63585d41bf"}
{"@timestamp":"2026-06-25T22:41:16.304+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2030.4ms)","duration":"2030.4ms","level":"slow","span":"c6f7f3b4ca3c0513","trace":"87b583385fc64d4452d2d7ace468951f"}
{"@timestamp":"2026-06-25T22:41:16.304+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2030.4ms","level":"info","span":"c6f7f3b4ca3c0513","trace":"87b583385fc64d4452d2d7ace468951f"}
{"@timestamp":"2026-06-25T22:41:16.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:57957 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"bb24e86d5aa1383a","trace":"51c9e36b76178eb2232feb5077703bf7"}
{"@timestamp":"2026-06-25T22:41:16.924+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/activate - 127.0.0.1:57965 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.2ms","level":"info","span":"e9aedddb324f2807","trace":"4c00f5fa217010b7d8e1525324b52c79"}
{"@timestamp":"2026-06-25T22:41:16.924+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57963 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"5323bd85ef3d7c3f","trace":"87caa4597e1ebf278eed945126c7d7a3"}
{"@timestamp":"2026-06-25T22:41:16.924+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57967 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.2ms","level":"info","span":"c91dcb776d6193b6","trace":"920b7b701b9e96098a205107f60596c7"}
{"@timestamp":"2026-06-25T22:41:16.925+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57966 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"4f43b78761dd658c","trace":"6284e9ed44b528cbc0b64fa9823725c6"}
{"@timestamp":"2026-06-25T22:41:16.925+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/activate - 127.0.0.1:57968 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.7ms","level":"info","span":"59caa79f2ef2644f","trace":"b6bf0c3b604794865f1775a17ffa6cd5"}
{"@timestamp":"2026-06-25T22:41:16.927+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57973 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"be78af8276364edc","trace":"d99f29575e31d6ac0a7358e2a5f36c19"}
{"@timestamp":"2026-06-25T22:41:16.928+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57970 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"4e495c3a8b660e87","trace":"1716b11461e7304ad744dde705b5322a"}
{"@timestamp":"2026-06-25T22:41:16.928+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57972 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"b1171d29b974d2b2","trace":"fc8be902d373f197137b87ac7d7645e1"}
{"@timestamp":"2026-06-25T22:41:16.931+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57977 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.7ms","level":"info","span":"efae7642b9e9420d","trace":"9e1000a4b1aecb42edd29ec8c9ff5e37"}
{"@timestamp":"2026-06-25T22:41:16.931+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57975 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.1ms","level":"info","span":"d0923e077a6933c9","trace":"0b6ef85567e023ab0b572acc53830119"}
{"@timestamp":"2026-06-25T22:41:16.931+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57979 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.7ms","level":"info","span":"233dc401154d0712","trace":"449e028b16355eeb865eb5e4a8f499ac"}
{"@timestamp":"2026-06-25T22:41:16.934+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57985 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"31ffbffe0aed5642","trace":"48e9865fc8b02f81440d0f14feafb26c"}
{"@timestamp":"2026-06-25T22:41:16.934+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:57981 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"39a1348d25d08671","trace":"d91fcaeee441b65cb9c5ed47b4d3e833"}
{"@timestamp":"2026-06-25T22:41:16.934+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57984 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"d009f520ce2de9d5","trace":"bb619f783965c0d095c11112f25e4b1f"}
{"@timestamp":"2026-06-25T22:41:16.936+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57990 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"8760edd432c15ee0","trace":"706fae9521cc2731ea20d27b65aaca90"}
{"@timestamp":"2026-06-25T22:41:16.936+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee - 127.0.0.1:57988 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"c0c9a41c9af62031","trace":"06f3743be89f0108d8bd288809d0a71e"}
{"@timestamp":"2026-06-25T22:41:16.936+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57991 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"252cbe1de3c7bba4","trace":"dec0db5413cd45b16546a0dabced9a09"}
{"@timestamp":"2026-06-25T22:41:16.937+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57994 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"3474208a4365695d","trace":"d6782f942175a315288b835cbf0ce7cd"}
{"@timestamp":"2026-06-25T22:41:16.938+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee - 127.0.0.1:57995 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"152766a05b8e67a2","trace":"23fd14a80f76c39bffbfd45326dcca5b"}
{"@timestamp":"2026-06-25T22:41:16.939+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:57997 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"962eea50cc8e9098","trace":"b46681c4abae33b2ebbfd023c21c35a3"}
{"@timestamp":"2026-06-25T22:41:16.939+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:57999 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.5ms","level":"info","span":"7cc74f62f9aa0645","trace":"a69b499f80bae060f8498df3581cc04f"}
{"@timestamp":"2026-06-25T22:41:16.941+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58001 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"dff2e0aeb51e0eb9","trace":"c396b18b6a7d03d2579c87024f715f21"}
{"@timestamp":"2026-06-25T22:41:16.943+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58003 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"64dfc7930c8d1354","trace":"cd31d28db1584fe2da0aaf42afaf34e7"}
{"@timestamp":"2026-06-25T22:41:16.946+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58005 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"c48388d2b415277e","trace":"b8c7d699f3d47140f3a20813965dbf50"}
{"@timestamp":"2026-06-25T22:41:18.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58009 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"0de703198b819fb8","trace":"bef1cd5c1b0dfbe9cfc8c06fc65a390e"}
{"@timestamp":"2026-06-25T22:41:20.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58014 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.4ms","level":"info","span":"8f52e7f8de9c3ea8","trace":"cd8311114c1873c885ad43ca4d3a6161"}
{"@timestamp":"2026-06-25T22:41:21.333+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.7ms)","duration":"2026.7ms","level":"slow","span":"5905a93a055a698e","trace":"8c7340c77a93b19a64ff3f21eb702983"}
{"@timestamp":"2026-06-25T22:41:21.333+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.7ms","level":"info","span":"5905a93a055a698e","trace":"8c7340c77a93b19a64ff3f21eb702983"}
{"@timestamp":"2026-06-25T22:41:22.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58017 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"3037cacd4ebd4b45","trace":"5fe2cecddff56184de352496fcc46566"}
{"@timestamp":"2026-06-25T22:41:22.966+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - PATCH /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58019 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.0ms","level":"info","span":"c9741c741a96aacc","trace":"5d766a66af0420a04ded1e20e590ba29"}
{"@timestamp":"2026-06-25T22:41:22.981+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58023 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.7ms","level":"info","span":"b85d2a3a5b27f158","trace":"c77ee53a15f2e8d9871ae5db20a67fcf"}
{"@timestamp":"2026-06-25T22:41:22.982+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58022 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"002903f6bc9b4f89","trace":"5f3f331619f01179ebe06fe0594d5c9e"}
{"@timestamp":"2026-06-25T22:41:22.985+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58025 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"366dc2029bee1397","trace":"04cbb7cd4a9115096c86c4cb0fbd6c9d"}
{"@timestamp":"2026-06-25T22:41:24.582+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58031 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"3ca0a39d48656bd1","trace":"fd41f679249d7552f9e78c366e40e95f"}
{"@timestamp":"2026-06-25T22:41:24.713+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/session/import - 127.0.0.1:58032 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"18.5ms","level":"info","span":"ff9584a21cb98768","trace":"dd371c09e94b0398abdd3dd87b3419b8"}
{"@timestamp":"2026-06-25T22:41:24.718+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58034 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"a8b8422c2f059c5a","trace":"85b186716bc4bb4bcdcc20c062573023"}
{"@timestamp":"2026-06-25T22:41:24.721+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58038 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"543f7a20f2706572","trace":"6c97b29032ce96b9932d6cc856379b8c"}
{"@timestamp":"2026-06-25T22:41:24.721+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58037 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.7ms","level":"info","span":"172776c26059abe8","trace":"1d6a319c26650cf68f4aad0e84bd31de"}
{"@timestamp":"2026-06-25T22:41:24.722+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58040 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.7ms","level":"info","span":"c905f5c158648d1f","trace":"37432480eecfbad57a78391b7dccf571"}
{"@timestamp":"2026-06-25T22:41:24.723+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58042 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.4ms","level":"info","span":"70596e9c9d6ca4f7","trace":"4af3300d88afd6dac43887779db0be76"}
{"@timestamp":"2026-06-25T22:41:24.725+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58045 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.5ms","level":"info","span":"15ae761fc0589c77","trace":"a1fb3b4c17bab36e170859503b502683"}
{"@timestamp":"2026-06-25T22:41:24.725+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58046 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"d6d78c59a02659fe","trace":"93ec355d56f2f12204fa3b765caa63df"}
{"@timestamp":"2026-06-25T22:41:24.728+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58048 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"b2d307af45728b1f","trace":"102f2900e1ad7d77024cb2f5eb7d5c8b"}
{"@timestamp":"2026-06-25T22:41:24.731+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58050 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"ee6a21a2a92dd9d3","trace":"0284abf6093b6c571590df5d2755ee16"}
{"@timestamp":"2026-06-25T22:41:24.903+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58053 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.7ms","level":"info","span":"c1496abd7a785b15","trace":"6a4cec107888cd47e5db04761b06edee"}
{"@timestamp":"2026-06-25T22:41:26.344+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2008.5ms)","duration":"2008.5ms","level":"slow","span":"4991cd210c145aad","trace":"c5f4c26878a4c497d8d3514f237c41e8"}
{"@timestamp":"2026-06-25T22:41:26.344+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2008.5ms","level":"info","span":"4991cd210c145aad","trace":"c5f4c26878a4c497d8d3514f237c41e8"}
{"@timestamp":"2026-06-25T22:41:26.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58059 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"664d7e61af28aef2","trace":"0040009026c4913274736610f0c8b616"}
{"@timestamp":"2026-06-25T22:41:28.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58066 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"45f5e57fc1455a6d","trace":"1c5bb161ba7df88ae3fdd8c48f3365a9"}
{"@timestamp":"2026-06-25T22:41:30.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58069 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"361b0f0233746e1b","trace":"f6e5ab3a43460a97b91306f479cf194d"}
{"@timestamp":"2026-06-25T22:41:31.183+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58075 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"4dee8245240e3970","trace":"21dddfe67443f1339c0fa01cd216489f"}
{"@timestamp":"2026-06-25T22:41:31.186+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58077 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"f6778526d63191d6","trace":"f32a2768fcd46d4a74883da6c629a37d"}
{"@timestamp":"2026-06-25T22:41:31.188+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58080 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"716353bbe7cce2a1","trace":"519295d7d76fcd4b6d83eaeb5d90244e"}
{"@timestamp":"2026-06-25T22:41:31.190+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58081 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"b43fe1d1551b3e62","trace":"24bc5e423a45b6b2a3e11e348fe4cc98"}
{"@timestamp":"2026-06-25T22:41:31.348+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2002.0ms)","duration":"2002.0ms","level":"slow","span":"d704caed27f0cb9a","trace":"f99bec96761c37689c6eb13b287731a9"}
{"@timestamp":"2026-06-25T22:41:31.348+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2002.0ms","level":"info","span":"d704caed27f0cb9a","trace":"f99bec96761c37689c6eb13b287731a9"}
{"@timestamp":"2026-06-25T22:41:32.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58083 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"3716ecc8c91d4b49","trace":"a8483c83c1cb5599f9f1b57eda9d0f68"}
{"@timestamp":"2026-06-25T22:41:34.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58086 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"a7c20dc726086d0b","trace":"236ed4de019e99c632d119b576cbe9a8"}
{"@timestamp":"2026-06-25T22:41:36.426+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2075.7ms)","duration":"2075.7ms","level":"slow","span":"cdaacc2d06ee5680","trace":"2e82addbc72db3fa26497de3199924b0"}
{"@timestamp":"2026-06-25T22:41:36.426+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2075.7ms","level":"info","span":"cdaacc2d06ee5680","trace":"2e82addbc72db3fa26497de3199924b0"}
{"@timestamp":"2026-06-25T22:41:36.920+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58092 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"11f397c28317d90c","trace":"484c541839cbc55b547cac8afaaba70e"}
{"@timestamp":"2026-06-25T22:41:38.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58098 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"f80be55ea6800e5a","trace":"f91f739e0ee4b38e17e3bfcf7ae4ddc5"}
{"@timestamp":"2026-06-25T22:41:41.155+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58100 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"de95665fa3eccc3d","trace":"02fea6be580f65c91d3848e4409ed6c3"}
{"@timestamp":"2026-06-25T22:41:41.439+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2009.1ms)","duration":"2009.1ms","level":"slow","span":"7794915aad5384e8","trace":"bdc20b41a921ac95ce4c5be9dbbb2991"}
{"@timestamp":"2026-06-25T22:41:41.439+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2009.1ms","level":"info","span":"7794915aad5384e8","trace":"bdc20b41a921ac95ce4c5be9dbbb2991"}
{"@timestamp":"2026-06-25T22:41:43.155+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58104 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"68962bc6f77fd408","trace":"38efdc50a001a720db28831002e13da4"}
{"@timestamp":"2026-06-25T22:41:45.154+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58110 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"fb37c97346504c8f","trace":"c799182b2e6c933fcc14029c05c697a0"}
{"@timestamp":"2026-06-25T22:41:46.480+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2038.7ms)","duration":"2038.7ms","level":"slow","span":"dec716bed2223031","trace":"8af921cabe277457ab5c6cac894581c9"}
{"@timestamp":"2026-06-25T22:41:46.480+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2038.7ms","level":"info","span":"dec716bed2223031","trace":"8af921cabe277457ab5c6cac894581c9"}
{"@timestamp":"2026-06-25T22:41:47.157+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58115 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"0349002f9c82b3ba","trace":"1943d40daf0d38c5f968da2c6b429825"}
{"@timestamp":"2026-06-25T22:41:49.151+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58119 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"b14f98b91609d59b","trace":"176846a64b8c6fad27d67a760804ac45"}
{"@timestamp":"2026-06-25T22:41:50.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58126 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"47ffd15c937b26c1","trace":"51d047d05415e7f56f6dfcd90610603f"}
{"@timestamp":"2026-06-25T22:41:51.503+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2020.9ms)","duration":"2020.9ms","level":"slow","span":"f63d6cf956661f35","trace":"0db0e1b81600cba664eccef6e0fe8677"}
{"@timestamp":"2026-06-25T22:41:51.503+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2020.9ms","level":"info","span":"f63d6cf956661f35","trace":"0db0e1b81600cba664eccef6e0fe8677"}
{"@timestamp":"2026-06-25T22:41:52.655+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/personas - 127.0.0.1:58128 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"19.3ms","level":"info","span":"6a65f3fbf5ec6ccb","trace":"eb6e34a332e38ecdaa5fd3283106f778"}
{"@timestamp":"2026-06-25T22:41:52.658+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58130 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.8ms","level":"info","span":"5398facd237500e7","trace":"0ea7e7cbeae4d048e4476ca0a87771f3"}
{"@timestamp":"2026-06-25T22:41:52.661+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58132 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"0a1581bf5f9443c9","trace":"51181c0903b1a5320abeb2639c7f878a"}
{"@timestamp":"2026-06-25T22:41:52.690+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58141 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"31c1a1e0f91381ad","trace":"fca26691954c2633d24a43f2090305c2"}
{"@timestamp":"2026-06-25T22:41:52.690+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58139 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"73fda4ae340cdebf","trace":"78d64fa2cb1bbe7d81317f062ae87ac5"}
{"@timestamp":"2026-06-25T22:41:52.690+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58140 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"fcd9cc33586f144a","trace":"e3d07ee446cd0b76f439efadf066c92a"}
{"@timestamp":"2026-06-25T22:41:52.693+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58142 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"957a815578652e2e","trace":"79c0f7706f5fc4b154dfab13bd9b0a47"}
{"@timestamp":"2026-06-25T22:41:52.694+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58143 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"a5a087c64e81a018","trace":"ae8a0b27992a7a97f734686e96569e24"}
{"@timestamp":"2026-06-25T22:41:52.695+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58144 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"7af6b3a490c21d88","trace":"15d090221427498f890001d4b6c7d639"}
{"@timestamp":"2026-06-25T22:41:52.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58146 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"7860de856667852d","trace":"dc1229bfc3fc22444d6962357961da90"}
{"@timestamp":"2026-06-25T22:41:54.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58149 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"8d349297a452d0f9","trace":"5b8e02a2395cedc7f2710ef6dc3d67ce"}
{"@timestamp":"2026-06-25T22:41:56.539+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2032.6ms)","duration":"2032.6ms","level":"slow","span":"2f310391ddc316a9","trace":"a9848b94a5e1088865a68cba5e461be5"}
{"@timestamp":"2026-06-25T22:41:56.539+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2032.6ms","level":"info","span":"2f310391ddc316a9","trace":"a9848b94a5e1088865a68cba5e461be5"}
{"@timestamp":"2026-06-25T22:41:56.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58155 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.5ms","level":"info","span":"1205c0c8a8bafa7a","trace":"4f09b4c4f975ab8f5932b09cbab04b49"}
{"@timestamp":"2026-06-25T22:41:57.807+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.1Mi, TotalAlloc=13.4Mi, Sys=22.8Mi, NumGC=8","level":"stat"}
{"@timestamp":"2026-06-25T22:41:58.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58159 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"0f15b94319f070c3","trace":"6349c304e9dad0fbf2b5758679fea8c1"}
{"@timestamp":"2026-06-25T22:41:59.875+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 108, pass: 108, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:42:00.644+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 1.8/s, drops: 0, avg time: 227.6ms, med: 2.2ms, 90th: 2009.0ms, 99th: 2075.7ms, 99.9th: 2075.7ms","level":"stat"}
{"@timestamp":"2026-06-25T22:42:00.907+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58165 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"e051e8a09054f0d3","trace":"b1504cacd27278405ca72cbf4d839cb3"}
{"@timestamp":"2026-06-25T22:42:01.568+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.8ms)","duration":"2024.8ms","level":"slow","span":"03d28eb9116d875f","trace":"634389a893bee10325aafdcb366da16f"}
{"@timestamp":"2026-06-25T22:42:01.568+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.8ms","level":"info","span":"03d28eb9116d875f","trace":"634389a893bee10325aafdcb366da16f"}
{"@timestamp":"2026-06-25T22:42:02.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58171 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"ea59f10966933ada","trace":"04a85d13ca0932bdf8f951567ef4f635"}
{"@timestamp":"2026-06-25T22:42:04.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58173 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.7ms","level":"info","span":"0acd9d9dcad99ae3","trace":"bb78e88f0c116aa338048499d3048c7c"}
{"@timestamp":"2026-06-25T22:42:06.592+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2021.4ms)","duration":"2021.4ms","level":"slow","span":"e2527711e153fd51","trace":"133cd614c2d07a0d5867af95e62e90cd"}
{"@timestamp":"2026-06-25T22:42:06.593+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2021.4ms","level":"info","span":"e2527711e153fd51","trace":"133cd614c2d07a0d5867af95e62e90cd"}
{"@timestamp":"2026-06-25T22:42:06.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58178 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.5ms","level":"info","span":"c409932a76981c62","trace":"a05c6080ba07f31ec157f9bc0893e729"}
{"@timestamp":"2026-06-25T22:42:08.912+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58183 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.8ms","level":"info","span":"a820ebfb0d39c7fc","trace":"c238758f3bf4aab51d320432507eea9a"}
{"@timestamp":"2026-06-25T22:42:10.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58187 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"1891664093ece4fb","trace":"45777d8db4e812ba4ee2f30d9efb1db8"}
{"@timestamp":"2026-06-25T22:42:11.606+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2010.6ms)","duration":"2010.6ms","level":"slow","span":"6077db5005c480b7","trace":"839912c4ea1c974d4666a7bd4975ebfe"}
{"@timestamp":"2026-06-25T22:42:11.606+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2010.6ms","level":"info","span":"6077db5005c480b7","trace":"839912c4ea1c974d4666a7bd4975ebfe"}
{"@timestamp":"2026-06-25T22:42:12.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58191 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"68d2010b5eb9d4ab","trace":"7429c90171eaa5b2c60e2e9982467c6d"}
{"@timestamp":"2026-06-25T22:42:14.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58199 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"8a7dc23a78b74694","trace":"11e649f484fd0700d2bc2a6f510cd084"}
{"@timestamp":"2026-06-25T22:42:16.623+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2015.5ms)","duration":"2015.5ms","level":"slow","span":"a06d0a553b24ff51","trace":"95f5463feef242ba557eede8ff3fc719"}
{"@timestamp":"2026-06-25T22:42:16.623+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2015.5ms","level":"info","span":"a06d0a553b24ff51","trace":"95f5463feef242ba557eede8ff3fc719"}
{"@timestamp":"2026-06-25T22:42:16.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58205 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"18c4ba0e589aca81","trace":"c968df81f9ec505a2018514acbed9012"}
{"@timestamp":"2026-06-25T22:42:18.911+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58209 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.3ms","level":"info","span":"572a72bb1e319afe","trace":"7f22fb4b2de56bd37d7e7e5085370347"}
{"@timestamp":"2026-06-25T22:42:20.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58213 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"337aeb7af56fda7c","trace":"d6b61774d6f8fabe83936508bd141cab"}
{"@timestamp":"2026-06-25T22:42:21.650+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2023.7ms)","duration":"2023.7ms","level":"slow","span":"91f2b6c862639f4d","trace":"27fa51b57fd5c5aee75a024f608ba76b"}
{"@timestamp":"2026-06-25T22:42:21.650+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2023.7ms","level":"info","span":"91f2b6c862639f4d","trace":"27fa51b57fd5c5aee75a024f608ba76b"}
{"@timestamp":"2026-06-25T22:42:22.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58219 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"f17f93cd64f30979","trace":"dd3f67d1f7732ae523bbd8b50d5847a9"}
{"@timestamp":"2026-06-25T22:42:24.911+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58222 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.4ms","level":"info","span":"b98c143d45584fff","trace":"23d0ed1833f40068281ea04061958554"}
{"@timestamp":"2026-06-25T22:42:26.663+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2009.3ms)","duration":"2009.3ms","level":"slow","span":"6896090b8d40ddc2","trace":"f850da86b6d23c5028d9def60176cca9"}
{"@timestamp":"2026-06-25T22:42:26.663+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2009.3ms","level":"info","span":"6896090b8d40ddc2","trace":"f850da86b6d23c5028d9def60176cca9"}
{"@timestamp":"2026-06-25T22:42:26.911+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58231 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.6ms","level":"info","span":"40923dea7fae6bcf","trace":"d049726689d8770739a46b7c2fe579c5"}
{"@timestamp":"2026-06-25T22:42:28.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58235 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"02f5c1c797111149","trace":"cc6df7f399fac65daebb48bb223be485"}
{"@timestamp":"2026-06-25T22:42:30.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58240 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"89af160d80f1282a","trace":"20ea1a05148fd069fdf3553d721b5552"}
{"@timestamp":"2026-06-25T22:42:31.712+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2044.9ms)","duration":"2044.9ms","level":"slow","span":"9ecc1ab6419da652","trace":"9ec972fd8820ad8c23137bff31c39139"}
{"@timestamp":"2026-06-25T22:42:31.712+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2044.9ms","level":"info","span":"9ecc1ab6419da652","trace":"9ec972fd8820ad8c23137bff31c39139"}
{"@timestamp":"2026-06-25T22:42:32.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58244 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"8626a42589a0c9cc","trace":"a0e977f9f410a20e21c708d6865967bc"}
{"@timestamp":"2026-06-25T22:42:34.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58250 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"2516b64e60a5a8b7","trace":"be3927181a55f6d1fde1e6b5bcb96951"}
{"@timestamp":"2026-06-25T22:42:36.724+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2007.9ms)","duration":"2007.9ms","level":"slow","span":"de1862f6b1632239","trace":"0fedcade6153c2ff64cb05739f6c737a"}
{"@timestamp":"2026-06-25T22:42:36.724+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2007.9ms","level":"info","span":"de1862f6b1632239","trace":"0fedcade6153c2ff64cb05739f6c737a"}
{"@timestamp":"2026-06-25T22:42:36.912+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58254 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.9ms","level":"info","span":"91da5d969bf014c5","trace":"02db52448bb685403253842c6d590866"}
{"@timestamp":"2026-06-25T22:42:38.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58263 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"d5c6d312276d172d","trace":"b0655029352441383a224166191a7bee"}
{"@timestamp":"2026-06-25T22:42:40.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58265 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.0ms","level":"info","span":"f790bcc04ae554da","trace":"00df1b47b68dd04cced8911cf405d537"}
{"@timestamp":"2026-06-25T22:42:41.773+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2045.0ms)","duration":"2045.0ms","level":"slow","span":"d5aafa4113295193","trace":"08d46586712825b3a1d13e26de0b65f9"}
{"@timestamp":"2026-06-25T22:42:41.773+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2045.0ms","level":"info","span":"d5aafa4113295193","trace":"08d46586712825b3a1d13e26de0b65f9"}
{"@timestamp":"2026-06-25T22:42:42.905+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58268 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"6413edfd3836dae2","trace":"98779a1100f9b83be0655189710aa929"}
{"@timestamp":"2026-06-25T22:42:44.908+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58274 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"854635e4c7cba678","trace":"d94c840166e60ca9434b11c5d5f97851"}
{"@timestamp":"2026-06-25T22:42:45.363+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58282 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"4afb97486a8d5fd1","trace":"b4b1948b28f8c799115a1dca2af06dd0"}
{"@timestamp":"2026-06-25T22:42:45.384+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58283 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"37b3b5fac4482648","trace":"a172183d4d401ba7467041e84ae6ffd7"}
{"@timestamp":"2026-06-25T22:42:45.387+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58285 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"102a3a3d989af774","trace":"8b9a89017bca46cc86d04ae19c64665b"}
{"@timestamp":"2026-06-25T22:42:45.388+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58287 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.2ms","level":"info","span":"b57a23778f31fa31","trace":"48ee4ba27e63a02d8a81884fa45c4860"}
{"@timestamp":"2026-06-25T22:42:45.389+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58292 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"1eb83388d3a2744c","trace":"b29dda3c759a94df8a1e0a62a47a7371"}
{"@timestamp":"2026-06-25T22:42:45.389+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58291 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"7b40625586dc2ac9","trace":"287983635f20ec86caff602e5bf92bf1"}
{"@timestamp":"2026-06-25T22:42:45.390+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58288 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"a9f9779e22ed43bf","trace":"d41f907aa30f3a8791832a265a510d1e"}
{"@timestamp":"2026-06-25T22:42:45.390+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58286 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.2ms","level":"info","span":"7d1a2bda13652e4b","trace":"f4621eb75f0cbb1bcdc8c43fac51ad45"}
{"@timestamp":"2026-06-25T22:42:45.390+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58294 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"fd77a752cc2fc1c4","trace":"33213d39854d70b099ccb41eb13a93b4"}
{"@timestamp":"2026-06-25T22:42:45.391+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58296 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"2e367acfd7540b36","trace":"7f2b5b28e211d7161cdcb818fb1ebdde"}
{"@timestamp":"2026-06-25T22:42:45.391+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58298 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.7ms","level":"info","span":"f85469dd422dca70","trace":"f00d5027ffcaaf0adbef281372f32b02"}
{"@timestamp":"2026-06-25T22:42:45.392+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58300 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"1558bf5d5613957d","trace":"9904769e1315ec4cd3df8d1ca9fca34b"}
{"@timestamp":"2026-06-25T22:42:45.393+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58302 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.6ms","level":"info","span":"27c9784036ab36af","trace":"eac5a64012059e7e7a82f07644476a8d"}
{"@timestamp":"2026-06-25T22:42:45.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58305 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.8ms","level":"info","span":"030999555f180888","trace":"149d5ca08d5a3f15d3c867da497b3e3e"}
{"@timestamp":"2026-06-25T22:42:45.395+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58306 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"cd189245c64bbb97","trace":"97194254a7029bb7c1e9d4934dea80d5"}
{"@timestamp":"2026-06-25T22:42:45.398+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58310 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"aa2c84397b374026","trace":"51b9b83af4056e656af5b845c5b65f5a"}
{"@timestamp":"2026-06-25T22:42:45.398+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58309 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"eb6367ccc535d7dc","trace":"854cea268438049fa6fece9cef03a8cf"}
{"@timestamp":"2026-06-25T22:42:45.400+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58312 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"751b8fca5a4953a4","trace":"14af623607b511cac30d6e718691f7ea"}
{"@timestamp":"2026-06-25T22:42:45.402+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58314 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"520ab551f9c8a869","trace":"6e47c42d87e77b829db39d9673bbbd28"}
{"@timestamp":"2026-06-25T22:42:45.403+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58316 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"572a76dc934e4cfa","trace":"49db977d1e93fc4282a64985e02854ce"}
{"@timestamp":"2026-06-25T22:42:46.803+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.1ms)","duration":"2026.1ms","level":"slow","span":"e0fe3de958f31dfd","trace":"e337d40b72f4f96219629a83e7421ce2"}
{"@timestamp":"2026-06-25T22:42:46.804+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.1ms","level":"info","span":"e0fe3de958f31dfd","trace":"e337d40b72f4f96219629a83e7421ce2"}
{"@timestamp":"2026-06-25T22:42:47.390+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58328 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.3ms","level":"info","span":"33454daf37997dea","trace":"c443613c4a5d8b19f98a0cfb13453d37"}
{"@timestamp":"2026-06-25T22:42:49.390+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58329 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"99596bd4c461cc6b","trace":"f5fa827ad847f0c9f164a6fa9037437e"}
{"@timestamp":"2026-06-25T22:42:50.921+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58333 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"e7ee8f473b7c9b79","trace":"4e0ad5223a0a77904a24dad7d1258406"}
{"@timestamp":"2026-06-25T22:42:50.924+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58334 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"58988e3c5b232e09","trace":"ea3565a10cccc71d819940d89e57bc56"}
{"@timestamp":"2026-06-25T22:42:50.926+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58335 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.8ms","level":"info","span":"869575386c485a07","trace":"c20126e3a251a31594fbf0bf8c90c19d"}
{"@timestamp":"2026-06-25T22:42:50.931+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58343 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"11cb9a8016509eee","trace":"92c9f2839558c4c19427bd96e9ce050f"}
{"@timestamp":"2026-06-25T22:42:50.932+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f/copy-missions - 127.0.0.1:58341 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"647d2d00c863a981","trace":"f562800062829d3ecaf418091af7bda8"}
{"@timestamp":"2026-06-25T22:42:50.933+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58342 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"f272673ef1f525be","trace":"71f10d588519c5f70a41151b76f30448"}
{"@timestamp":"2026-06-25T22:42:51.393+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58345 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"15e0371afa3de44d","trace":"988459ce5b8f5a1bd7cd6a27a7ecae69"}
{"@timestamp":"2026-06-25T22:42:51.834+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2027.5ms)","duration":"2027.5ms","level":"slow","span":"ac12ee19fc74efbf","trace":"9fef45f190c508ccf12972f7e63e280b"}
{"@timestamp":"2026-06-25T22:42:51.834+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2027.5ms","level":"info","span":"ac12ee19fc74efbf","trace":"9fef45f190c508ccf12972f7e63e280b"}
{"@timestamp":"2026-06-25T22:42:53.389+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58346 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"fc82c69dc47d5642","trace":"5ef18cecb5f022a2856fd256fd613960"}
{"@timestamp":"2026-06-25T22:42:54.061+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58354 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"0baae6459641edfa","trace":"d63f10d71f7eaff6b3b79729bc20d1e7"}
{"@timestamp":"2026-06-25T22:42:54.061+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58352 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"a2fa49974531f519","trace":"bc3f2d0c598910478480a3c1f266dfae"}
{"@timestamp":"2026-06-25T22:42:54.061+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58353 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"712b859896ad6744","trace":"561b14314543d3d373ea5a27c3ddd3d2"}
{"@timestamp":"2026-06-25T22:42:54.063+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58355 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"c277d742c439ff5a","trace":"438803c6e882488a7d1839f7cef82666"}
{"@timestamp":"2026-06-25T22:42:54.064+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58356 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.1ms","level":"info","span":"51fab99015384ff0","trace":"627635232ce64af03ab5b8d94995d7c1"}
{"@timestamp":"2026-06-25T22:42:54.065+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58357 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.2ms","level":"info","span":"4717370b288fc30e","trace":"46219a31dfd8e7446bee8875b9059dda"}
{"@timestamp":"2026-06-25T22:42:55.391+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58365 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"6de9aafeb1a1ccde","trace":"3dd3d9982f9805696557682e32c8a7af"}
{"@timestamp":"2026-06-25T22:42:56.858+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2022.1ms)","duration":"2022.1ms","level":"slow","span":"1d4dfdeefa53aecf","trace":"1d2d229dd28204d5e5f40890dac3bed2"}
{"@timestamp":"2026-06-25T22:42:56.858+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2022.1ms","level":"info","span":"1d4dfdeefa53aecf","trace":"1d2d229dd28204d5e5f40890dac3bed2"}
{"@timestamp":"2026-06-25T22:42:57.391+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58368 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"9eda6ce2b5aa6554","trace":"ec0db61445caa0c629cb2d897d9fc7c3"}
{"@timestamp":"2026-06-25T22:42:57.807+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.9Mi, TotalAlloc=16.1Mi, Sys=22.8Mi, NumGC=9","level":"stat"}
{"@timestamp":"2026-06-25T22:42:59.875+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 73, pass: 73, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:43:00.153+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58376 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"6f8b691754df64dc","trace":"54f01e5daace729b75f933c6f7015365"}
{"@timestamp":"2026-06-25T22:43:00.644+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 1.2/s, drops: 0, avg time: 330.2ms, med: 2.7ms, 90th: 2022.0ms, 99th: 2044.9ms, 99.9th: 2044.9ms","level":"stat"}
{"@timestamp":"2026-06-25T22:43:01.885+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.3ms)","duration":"2025.3ms","level":"slow","span":"b32c86c67c401100","trace":"9c02ebdcb10c8343d2acae4532bfd354"}
{"@timestamp":"2026-06-25T22:43:01.885+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.3ms","level":"info","span":"b32c86c67c401100","trace":"9c02ebdcb10c8343d2acae4532bfd354"}
{"@timestamp":"2026-06-25T22:43:02.156+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58380 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"e06d597fcb552d23","trace":"318a6617093d097698b069a4346c3558"}
{"@timestamp":"2026-06-25T22:43:03.389+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58386 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"eaa1e6ff82c43975","trace":"6f1cac436409434537a6c3819e5c6788"}
{"@timestamp":"2026-06-25T22:43:04.632+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 404 - POST /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f/style-analysis - 127.0.0.1:58388 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"e2a3ff0e87354db0","trace":"bf7be31e3745231b44c4c7b7170908c6"}
{"@timestamp":"2026-06-25T22:43:05.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58390 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.4ms","level":"info","span":"cf2a7327ae230acb","trace":"14d235f35b86387c97204b1a90af4af3"}
{"@timestamp":"2026-06-25T22:43:06.914+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.3ms)","duration":"2025.3ms","level":"slow","span":"51c424d9acd59873","trace":"0927055fe60ba8da17d3eb71b2799fed"}
{"@timestamp":"2026-06-25T22:43:06.914+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.3ms","level":"info","span":"51c424d9acd59873","trace":"0927055fe60ba8da17d3eb71b2799fed"}
{"@timestamp":"2026-06-25T22:43:07.396+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58395 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"c8d88012bac2f2e5","trace":"9172cd5e01950e99f0e561cfd3eade09"}
{"@timestamp":"2026-06-25T22:43:09.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58399 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"81719f39a0bd9d82","trace":"7e4ea462e686ef702faa3d28d15e8a24"}
{"@timestamp":"2026-06-25T22:43:11.392+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58404 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"4cecdbdc6895ed3e","trace":"7fb19817ca1a3b9cfaf9276df3f75095"}
{"@timestamp":"2026-06-25T22:43:11.936+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2018.8ms)","duration":"2018.8ms","level":"slow","span":"80f53251796f6306","trace":"b5809948cd45a9fc7ff514ee0162aad1"}
{"@timestamp":"2026-06-25T22:43:11.936+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2018.8ms","level":"info","span":"80f53251796f6306","trace":"b5809948cd45a9fc7ff514ee0162aad1"}
{"@timestamp":"2026-06-25T22:43:13.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58406 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.8ms","level":"info","span":"0f72d11ae1129a22","trace":"fac5891c5389e59659ab65e1dc2845ff"}
{"@timestamp":"2026-06-25T22:43:13.473+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 404 - POST /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f/style-analysis - 127.0.0.1:58408 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"8.1ms","level":"info","span":"048900f0ddb888de","trace":"96946f5cd6003f68c06be62de7535082"}
{"@timestamp":"2026-06-25T22:43:15.393+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58416 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"d0b9a7cf9341b59c","trace":"75faf80af0b958e40baa9fd60215327c"}
{"@timestamp":"2026-06-25T22:43:17.007+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2067.2ms)","duration":"2067.2ms","level":"slow","span":"d90685e83b9789ef","trace":"1a6f99962db93e3e282e4a99fd693205"}
{"@timestamp":"2026-06-25T22:43:17.007+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2067.2ms","level":"info","span":"d90685e83b9789ef","trace":"1a6f99962db93e3e282e4a99fd693205"}
{"@timestamp":"2026-06-25T22:43:17.395+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58418 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.4ms","level":"info","span":"fa77f0ed15de9d89","trace":"13de28186c250ac9709cc3fa610cc935"}
{"@timestamp":"2026-06-25T22:43:19.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58423 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"c5c5eeaa976231e8","trace":"a29b6bd74c5f3f862470bcb1ffc999c7"}
{"@timestamp":"2026-06-25T22:43:21.391+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58427 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"1b7600df77d0bd55","trace":"32d5b41927119249d98e3c4576316b6e"}
{"@timestamp":"2026-06-25T22:43:22.038+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.5ms)","duration":"2025.5ms","level":"slow","span":"1d02968fe8a6bd3e","trace":"ca7ced55af5645cdb43031113d90d0f7"}
{"@timestamp":"2026-06-25T22:43:22.038+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.5ms","level":"info","span":"1d02968fe8a6bd3e","trace":"ca7ced55af5645cdb43031113d90d0f7"}
{"@timestamp":"2026-06-25T22:43:23.394+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58432 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.0ms","level":"info","span":"5eeca9fa6d2ada3f","trace":"2c9e86ca7b9b536f56835d05251e8d9f"}
{"@timestamp":"2026-06-25T22:43:25.392+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58434 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"7609b1d558ed93cd","trace":"6dac3cc2968a2976713a9dfdc6f7aa17"}
{"@timestamp":"2026-06-25T22:43:26.877+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58449 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"f42daa73572c8ea4","trace":"f55f80b2b1c5c7b0a721bbf9442f40d0"}
{"@timestamp":"2026-06-25T22:43:26.877+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58448 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"3623f6dfe184865b","trace":"1ab7d1a08092c2c12c0eaae0c051ed18"}
{"@timestamp":"2026-06-25T22:43:26.889+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58455 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"9fccc730c0db7e88","trace":"72fe7a2f7644cd967b21df0cb390430a"}
{"@timestamp":"2026-06-25T22:43:26.889+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58452 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"0fc2a94060d8e49e","trace":"2502c54a83ca09e157d5f39401d236f6"}
{"@timestamp":"2026-06-25T22:43:26.889+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58454 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"35c110657234bf41","trace":"30fe87aa559563d79bc70ad0b561d562"}
{"@timestamp":"2026-06-25T22:43:26.890+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58453 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"49d6c710d033b750","trace":"182bf7f670ee19794de2ecdf93017850"}
{"@timestamp":"2026-06-25T22:43:26.891+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58461 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"06895711d53836b3","trace":"79e275b0b0c5ed5cd3ae98e1826ec07c"}
{"@timestamp":"2026-06-25T22:43:26.892+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58464 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"1242f301f93e2a75","trace":"aa85c6bc5d1b00f058a321ff6d72b661"}
{"@timestamp":"2026-06-25T22:43:26.892+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58458 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.9ms","level":"info","span":"ca2911123a5b83f3","trace":"7b52c85b1e28c67a472988079ea6a0e5"}
{"@timestamp":"2026-06-25T22:43:26.892+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58456 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.4ms","level":"info","span":"14694e70c95bd00b","trace":"629215e3d7f220cf16f2fa1e84ac0753"}
{"@timestamp":"2026-06-25T22:43:26.892+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58463 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"52f7aa585c703b31","trace":"a711e197c2da5a02c46d4ddc2f76bcbf"}
{"@timestamp":"2026-06-25T22:43:26.894+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58467 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"127466f925568541","trace":"60c8ca6fe41e9befc5e4f450d5bdb94a"}
{"@timestamp":"2026-06-25T22:43:26.894+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58468 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"079f27a6b54a8328","trace":"341a86e30860308c228dd814b5cb00f0"}
{"@timestamp":"2026-06-25T22:43:26.900+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58470 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"feeb50abae9e7ab7","trace":"43fa877d849e5e967b9003253b43f24b"}
{"@timestamp":"2026-06-25T22:43:26.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58473 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.7ms","level":"info","span":"e3cb10c0f7c32836","trace":"aeda057b659de8261200ce59f9528a4b"}
{"@timestamp":"2026-06-25T22:43:26.902+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58474 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.1ms","level":"info","span":"5694d5161094593a","trace":"a8577ce64591c8651bc5bb61bee3b28b"}
{"@timestamp":"2026-06-25T22:43:26.904+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58476 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.0ms","level":"info","span":"dd0b6253bb3f6aa2","trace":"930af395d83dae083028092b6f83a7a4"}
{"@timestamp":"2026-06-25T22:43:26.906+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58478 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"bbb9b7e615aa61cd","trace":"2bf085b14e8c1ecaf79a2a68a663654f"}
{"@timestamp":"2026-06-25T22:43:27.060+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2018.5ms)","duration":"2018.5ms","level":"slow","span":"1918ad738a90eaec","trace":"1605060970620da56ec48df6dd7e67bc"}
{"@timestamp":"2026-06-25T22:43:27.061+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2018.5ms","level":"info","span":"1918ad738a90eaec","trace":"1605060970620da56ec48df6dd7e67bc"}
{"@timestamp":"2026-06-25T22:43:27.798+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58487 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"f69e28e16aa25aee","trace":"cd64e55f8e0f4efb4eb5ba999c4c2455"}
{"@timestamp":"2026-06-25T22:43:27.798+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/members/me - 127.0.0.1:58486 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"404db16dca5e0cb0","trace":"fef3425a848f45ee10c0d3ede91ede88"}
{"@timestamp":"2026-06-25T22:43:27.814+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58490 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"91e2d068b46cbc1a","trace":"72ff471e29110a44dc25fd68bdb9917d"}
{"@timestamp":"2026-06-25T22:43:27.814+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58493 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"60e74227b4f813de","trace":"3cf8572cdfbe76f8db52a8d12ff36e5d"}
{"@timestamp":"2026-06-25T22:43:27.815+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58491 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"b19a7eaf6eb4c6d3","trace":"1a2feaedfbc81a96db1bc11c25534eb4"}
{"@timestamp":"2026-06-25T22:43:27.815+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58492 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.1ms","level":"info","span":"416f6d84bdefa59f","trace":"0d88d65ddd17c280ec82769b677dbc33"}
{"@timestamp":"2026-06-25T22:43:27.815+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58494 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.5ms","level":"info","span":"ee9ec0fd6c48f5b1","trace":"5319f631931fc6ec8055f88eefc02672"}
{"@timestamp":"2026-06-25T22:43:27.816+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas/d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58497 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"0.9ms","level":"info","span":"2f4a208966fb3139","trace":"9cc85372d68e7e19a2035193123ba76c"}
{"@timestamp":"2026-06-25T22:43:27.817+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58499 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.2ms","level":"info","span":"aeb42d8b4cdc7085","trace":"854092f1010e48af110c520c51ac35a8"}
{"@timestamp":"2026-06-25T22:43:27.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58503 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.6ms","level":"info","span":"6fdf79e6c38ea719","trace":"6a869c0c3009f7a1feff7c1b1e597051"}
{"@timestamp":"2026-06-25T22:43:27.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=20&scope=persona&scope_id=d5e71242-8a86-4508-90fb-7a74edf2cf6f - 127.0.0.1:58500 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.8ms","level":"info","span":"6aa6d5dde23a0a1e","trace":"9888f540c10bf859d9471367c3c635ff"}
{"@timestamp":"2026-06-25T22:43:27.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58509 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"3b5cc99050fcb022","trace":"4f50b6775d837d39426420a68b90fc70"}
{"@timestamp":"2026-06-25T22:43:27.823+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts - 127.0.0.1:58508 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"ef6fb52e79b81425","trace":"25eb921363534f35f80c4b75337a0ea5"}
{"@timestamp":"2026-06-25T22:43:27.956+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58512 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"e9fab1d13cf6deb2","trace":"512f592fb65330ebe2207044f4282e17"}
{"@timestamp":"2026-06-25T22:43:27.959+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/personas - 127.0.0.1:58513 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"e92093463b9f8dba","trace":"42d95e05b7cb66e321fecb8a17cb54f2"}
{"@timestamp":"2026-06-25T22:43:27.961+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58514 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"a102ccc1277a79c4","trace":"959391b26c2074b0fa3fa01785fa5892"}
{"@timestamp":"2026-06-25T22:43:27.964+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58515 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.0ms","level":"info","span":"0e9fd6aa525a2f2f","trace":"a2b687bb3083f71f7f7d21086dae66fd"}
{"@timestamp":"2026-06-25T22:43:27.967+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/threads-accounts/cdfe3861-7238-4874-9744-cd913b4245ee/connection - 127.0.0.1:58516 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.0ms","level":"info","span":"ffc3c30b8a983b24","trace":"df4ba8a29ae03679b33aa89821cfd887"}
{"@timestamp":"2026-06-25T22:43:29.819+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58517 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"fe3f3661bf2b481e","trace":"1c2348fa2c29ec0d56821e1a890ac179"}
{"@timestamp":"2026-06-25T22:43:31.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58522 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.3ms","level":"info","span":"87d7b95268e23a57","trace":"727992c6d56f05c9fee0dca361d42191"}
{"@timestamp":"2026-06-25T22:43:32.093+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2028.7ms)","duration":"2028.7ms","level":"slow","span":"217863c01fec72a4","trace":"0167ef11c57a3364200626415dd0b7f6"}
{"@timestamp":"2026-06-25T22:43:32.093+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2028.7ms","level":"info","span":"217863c01fec72a4","trace":"0167ef11c57a3364200626415dd0b7f6"}
{"@timestamp":"2026-06-25T22:43:33.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58528 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"610ff6858761307f","trace":"d7dd3db5b7314e542fe64cfd7c493895"}
{"@timestamp":"2026-06-25T22:43:35.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58533 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"b97a4f82eb87761d","trace":"1b7cd30d729bc09a5da74f94ccd6c910"}
{"@timestamp":"2026-06-25T22:43:37.121+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.6ms)","duration":"2024.6ms","level":"slow","span":"81457ca8a614fcee","trace":"49bcea174cb0ba82ecb25283ec8c700c"}
{"@timestamp":"2026-06-25T22:43:37.122+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.6ms","level":"info","span":"81457ca8a614fcee","trace":"49bcea174cb0ba82ecb25283ec8c700c"}
{"@timestamp":"2026-06-25T22:43:37.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58535 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.2ms","level":"info","span":"e0fcba2fab433ced","trace":"11d4c4d4b412494abb923697998ab325"}
{"@timestamp":"2026-06-25T22:43:39.817+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58543 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"ee32d46931e3b055","trace":"f901efb590a24a040d78841e3e458551"}
{"@timestamp":"2026-06-25T22:43:41.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58545 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"b8b4cdff153e3af0","trace":"14806762047dd0bfba4c1af66b41c47f"}
{"@timestamp":"2026-06-25T22:43:42.156+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2030.8ms)","duration":"2030.8ms","level":"slow","span":"08fbc2a6bb6e25e4","trace":"141b8dc5edc7e3d0d44290a7c1d0d65f"}
{"@timestamp":"2026-06-25T22:43:42.156+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2030.8ms","level":"info","span":"08fbc2a6bb6e25e4","trace":"141b8dc5edc7e3d0d44290a7c1d0d65f"}
{"@timestamp":"2026-06-25T22:43:43.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58550 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"9bbf1bdaf0094485","trace":"3cbdd6e95e2cd3a2fd0ef47c608eb74f"}
{"@timestamp":"2026-06-25T22:43:45.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58554 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"e804112377383750","trace":"dccbf2e9f8af7c33eb620c76b87eb9ed"}
{"@timestamp":"2026-06-25T22:43:47.180+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2021.2ms)","duration":"2021.2ms","level":"slow","span":"9ad2e19b70e4e7df","trace":"c08682584e69e70d6a1b18d3027481c3"}
{"@timestamp":"2026-06-25T22:43:47.180+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2021.2ms","level":"info","span":"9ad2e19b70e4e7df","trace":"c08682584e69e70d6a1b18d3027481c3"}
{"@timestamp":"2026-06-25T22:43:47.823+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58561 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.0ms","level":"info","span":"8c46f22e80e26a21","trace":"1b9aa69ea122f5ac9102ab4b256fe9ef"}
{"@timestamp":"2026-06-25T22:43:49.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58563 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"ba611fc888f7e1ac","trace":"d3b8069c07462afddd8255365ebf119f"}
{"@timestamp":"2026-06-25T22:43:51.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58569 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"c4a6867fa131bd1d","trace":"ee8a8964d0fed58fc344c6ae255dea86"}
{"@timestamp":"2026-06-25T22:43:52.207+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2023.1ms)","duration":"2023.1ms","level":"slow","span":"05f93d6ea91ebc0e","trace":"5a4e52ea45d0a7df75f920d4532e6ee6"}
{"@timestamp":"2026-06-25T22:43:52.207+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2023.1ms","level":"info","span":"05f93d6ea91ebc0e","trace":"5a4e52ea45d0a7df75f920d4532e6ee6"}
{"@timestamp":"2026-06-25T22:43:53.817+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58571 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"ba4e70c0aa3d60a9","trace":"57dd95a8d7110708ad5d72336cded413"}
{"@timestamp":"2026-06-25T22:43:55.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58578 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"83a743fbc50b57bc","trace":"3e3712889f8500d14724387836c86862"}
{"@timestamp":"2026-06-25T22:43:57.222+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2011.0ms)","duration":"2011.0ms","level":"slow","span":"9559b3be4bcef230","trace":"5030bef5885ea69db030fddd0d9eac2c"}
{"@timestamp":"2026-06-25T22:43:57.222+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2011.0ms","level":"info","span":"9559b3be4bcef230","trace":"5030bef5885ea69db030fddd0d9eac2c"}
{"@timestamp":"2026-06-25T22:43:57.808+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=4.1Mi, TotalAlloc=19.8Mi, Sys=23.6Mi, NumGC=11","level":"stat"}
{"@timestamp":"2026-06-25T22:43:57.827+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58582 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"9.4ms","level":"info","span":"b55dd75291c7ce96","trace":"d78b9c06a7a94d07b0dccfcb5643aad6"}
{"@timestamp":"2026-06-25T22:43:59.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58587 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.3ms","level":"info","span":"11fccf49ce065bab","trace":"d675d5d1b7008e6436c15e62355f0044"}
{"@timestamp":"2026-06-25T22:43:59.876+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 79, pass: 80, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:44:00.645+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 1.3/s, drops: 0, avg time: 310.1ms, med: 2.7ms, 90th: 2024.5ms, 99th: 2067.0ms, 99.9th: 2067.0ms","level":"stat"}
{"@timestamp":"2026-06-25T22:44:01.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58589 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"1bdeab9a97b6c91c","trace":"53f1880343fc861b213864afc840975b"}
{"@timestamp":"2026-06-25T22:44:02.286+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2060.0ms)","duration":"2060.0ms","level":"slow","span":"b616698f2d191c84","trace":"b9a765a55de23867bb9da93c108ce431"}
{"@timestamp":"2026-06-25T22:44:02.286+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2060.0ms","level":"info","span":"b616698f2d191c84","trace":"b9a765a55de23867bb9da93c108ce431"}
{"@timestamp":"2026-06-25T22:44:03.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58597 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"e08ad8ca68e63104","trace":"996ca2db2a8e67453777b7133a6ec145"}
{"@timestamp":"2026-06-25T22:44:05.817+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58599 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"f0e326fcc97afd47","trace":"cd51b139d3c6ed56fbaa6bf3e1d6f58a"}
{"@timestamp":"2026-06-25T22:44:07.315+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.2ms)","duration":"2026.2ms","level":"slow","span":"1f4cb5620b1b9a18","trace":"e4fb707bad078a91c1c12806d53b56fc"}
{"@timestamp":"2026-06-25T22:44:07.315+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.2ms","level":"info","span":"1f4cb5620b1b9a18","trace":"e4fb707bad078a91c1c12806d53b56fc"}
{"@timestamp":"2026-06-25T22:44:07.823+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58604 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.3ms","level":"info","span":"72312b25fa7ecce3","trace":"04b30acba4cd59e503f0edae719e39ac"}
{"@timestamp":"2026-06-25T22:44:09.819+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58607 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"98d0a701c4aa1ba4","trace":"af645d64bdf90008f06c908850750d2a"}
{"@timestamp":"2026-06-25T22:44:11.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58614 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"f88f86393fff629f","trace":"454bd8b217a1244c1804266fb47e803c"}
{"@timestamp":"2026-06-25T22:44:12.346+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2028.9ms)","duration":"2028.9ms","level":"slow","span":"9455b27e40067a04","trace":"a0793719912a963786a8e83439fbe92a"}
{"@timestamp":"2026-06-25T22:44:12.346+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2028.9ms","level":"info","span":"9455b27e40067a04","trace":"a0793719912a963786a8e83439fbe92a"}
{"@timestamp":"2026-06-25T22:44:13.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58616 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"f0df169c77ce1721","trace":"2a66be4932ffe667eba2be75496a16df"}
{"@timestamp":"2026-06-25T22:44:15.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58624 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.8ms","level":"info","span":"88c3f4d0ce62ddc0","trace":"767488c4f42fe4e0bab5624db2429a07"}
{"@timestamp":"2026-06-25T22:44:17.374+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.4ms)","duration":"2025.4ms","level":"slow","span":"3ef4814685db82c6","trace":"bb12dc279b8378dbb9863c3cbe1e45f1"}
{"@timestamp":"2026-06-25T22:44:17.374+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.4ms","level":"info","span":"3ef4814685db82c6","trace":"bb12dc279b8378dbb9863c3cbe1e45f1"}
{"@timestamp":"2026-06-25T22:44:17.818+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58627 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"f9a3abae789ed26b","trace":"523845035d5333aabb0aae901c255ea2"}
{"@timestamp":"2026-06-25T22:44:19.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58632 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"bf6c0895678df0e9","trace":"f7c26f4d0cfe209784061abb9db3d7f0"}
{"@timestamp":"2026-06-25T22:44:21.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58636 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"c667b1199397f538","trace":"49a7cbe0f00f0305eee427058c8eef6b"}
{"@timestamp":"2026-06-25T22:44:22.405+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.5ms)","duration":"2025.5ms","level":"slow","span":"a059d90d0604ddf1","trace":"699c7968a6c7c8f6ec7919d733bf9f18"}
{"@timestamp":"2026-06-25T22:44:22.405+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.5ms","level":"info","span":"a059d90d0604ddf1","trace":"699c7968a6c7c8f6ec7919d733bf9f18"}
{"@timestamp":"2026-06-25T22:44:23.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58641 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.2ms","level":"info","span":"c382cf30acbd32e3","trace":"ba3572f73909278a77438a4276ee83d5"}
{"@timestamp":"2026-06-25T22:44:25.819+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58643 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.5ms","level":"info","span":"d800c12e40e43174","trace":"a7511507547f9564058adf78a4741d03"}
{"@timestamp":"2026-06-25T22:44:27.438+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2027.8ms)","duration":"2027.8ms","level":"slow","span":"f959baaed7e0ca79","trace":"5edf9b48b7692298998dc450155495d0"}
{"@timestamp":"2026-06-25T22:44:27.438+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2027.8ms","level":"info","span":"f959baaed7e0ca79","trace":"5edf9b48b7692298998dc450155495d0"}
{"@timestamp":"2026-06-25T22:44:27.822+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58651 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"957e694ee8730cf3","trace":"0b3a174edd509c8130f663d05f53a1f0"}
{"@timestamp":"2026-06-25T22:44:29.821+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58653 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"916cf3991b073a88","trace":"f1367b69130d9d22a0121a174b483389"}
{"@timestamp":"2026-06-25T22:44:31.820+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58658 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.3ms","level":"info","span":"a992b7718e6f6ba2","trace":"07fae97416e78ee1b24f129221410b41"}
{"@timestamp":"2026-06-25T22:44:32.463+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2022.4ms)","duration":"2022.4ms","level":"slow","span":"855ed5122a6714fd","trace":"e3f88bc899e47363d1a34667e3700b82"}
{"@timestamp":"2026-06-25T22:44:32.463+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2022.4ms","level":"info","span":"855ed5122a6714fd","trace":"e3f88bc899e47363d1a34667e3700b82"}
{"@timestamp":"2026-06-25T22:44:33.819+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58662 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.4ms","level":"info","span":"ec060b2a323369be","trace":"9c1e43083329abc5299d29ccb207a3c1"}
{"@timestamp":"2026-06-25T22:44:36.156+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58667 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.0ms","level":"info","span":"b168fc3d36f42b8c","trace":"34a5e54d7e0869880206e88c973222cd"}
{"@timestamp":"2026-06-25T22:44:37.496+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2028.9ms)","duration":"2028.9ms","level":"slow","span":"71c81c12841b4edf","trace":"a54f231daa0b0f2bd9ecbacdffedbdc6"}
{"@timestamp":"2026-06-25T22:44:37.496+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2028.9ms","level":"info","span":"71c81c12841b4edf","trace":"a54f231daa0b0f2bd9ecbacdffedbdc6"}
{"@timestamp":"2026-06-25T22:44:38.161+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58673 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"336589674e332ab5","trace":"9b0f7813759f6c677e085fc48a3d3669"}
{"@timestamp":"2026-06-25T22:44:40.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58678 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"5ff8354f03022314","trace":"fbf24889a505da19da1f986f7164b7f2"}
{"@timestamp":"2026-06-25T22:44:42.157+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58681 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"0c3aba43f063f5ef","trace":"0c2964fe265bedbd48d26933188c8661"}
{"@timestamp":"2026-06-25T22:44:42.502+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2002.6ms)","duration":"2002.6ms","level":"slow","span":"cfd228bcc59054e2","trace":"44b6976c3c14647e0c87744df5a31007"}
{"@timestamp":"2026-06-25T22:44:42.502+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2002.6ms","level":"info","span":"cfd228bcc59054e2","trace":"44b6976c3c14647e0c87744df5a31007"}
{"@timestamp":"2026-06-25T22:44:44.162+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58685 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.2ms","level":"info","span":"ac89b7add6683b8b","trace":"105b1af8fdfb70b13e7543d96a863192"}
{"@timestamp":"2026-06-25T22:44:46.159+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58690 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"80e3004a5f2edcaa","trace":"5a6776ebff6a4a25fc41e4ec4bc8707e"}
{"@timestamp":"2026-06-25T22:44:47.541+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2035.8ms)","duration":"2035.8ms","level":"slow","span":"842734396c05b4d9","trace":"6ad075423aa7dce944f1be032e85a695"}
{"@timestamp":"2026-06-25T22:44:47.541+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2035.8ms","level":"info","span":"842734396c05b4d9","trace":"6ad075423aa7dce944f1be032e85a695"}
{"@timestamp":"2026-06-25T22:44:48.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58694 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"87eeb50a6b8e45f6","trace":"e069799ebf2a35b8262c0854ef0788b0"}
{"@timestamp":"2026-06-25T22:44:50.157+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58696 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"1428eb8ef399ba7a","trace":"386e0afc0aefe24a597d24a33382680a"}
{"@timestamp":"2026-06-25T22:44:52.159+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58704 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"6a94aa00a02000e6","trace":"9497c2d8d48338332631d8480ef42339"}
{"@timestamp":"2026-06-25T22:44:52.559+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2015.5ms)","duration":"2015.5ms","level":"slow","span":"5f8ff827d860de58","trace":"e2140b8437774906ec2267d90dccc37e"}
{"@timestamp":"2026-06-25T22:44:52.559+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2015.5ms","level":"info","span":"5f8ff827d860de58","trace":"e2140b8437774906ec2267d90dccc37e"}
{"@timestamp":"2026-06-25T22:44:54.155+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58710 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.6ms","level":"info","span":"514abf4af9cc39ca","trace":"463b84b92ee070c26753958ee4de7196"}
{"@timestamp":"2026-06-25T22:44:56.155+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58717 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.0ms","level":"info","span":"9f76d46f0a3b5eeb","trace":"e8001fbed73b48b6783b7a7bb21ee21a"}
{"@timestamp":"2026-06-25T22:44:57.587+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2024.5ms)","duration":"2024.5ms","level":"slow","span":"5b17a40555812d97","trace":"263025c138277aa3b949aebd8eca2dd2"}
{"@timestamp":"2026-06-25T22:44:57.587+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2024.5ms","level":"info","span":"5b17a40555812d97","trace":"263025c138277aa3b949aebd8eca2dd2"}
{"@timestamp":"2026-06-25T22:44:57.808+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.8Mi, TotalAlloc=21.8Mi, Sys=23.6Mi, NumGC=12","level":"stat"}
{"@timestamp":"2026-06-25T22:44:58.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58722 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.5ms","level":"info","span":"0dd7df21ddb2ee57","trace":"3cb12c51f0d5fc6a32d428a3141a0815"}
{"@timestamp":"2026-06-25T22:44:59.877+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 41, pass: 41, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:45:00.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58726 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.3ms","level":"info","span":"a9b0b4321c9784b9","trace":"2eb3ea76d3da2733ef50ba49995b643f"}
{"@timestamp":"2026-06-25T22:45:00.646+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.7/s, drops: 0, avg time: 581.4ms, med: 3.7ms, 90th: 2028.8ms, 99th: 2059.9ms, 99.9th: 2059.9ms","level":"stat"}
{"@timestamp":"2026-06-25T22:45:02.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58728 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.7ms","level":"info","span":"4d17f01a02412f58","trace":"1a926697b9d78c28a2b753da30f6b887"}
{"@timestamp":"2026-06-25T22:45:02.617+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2028.1ms)","duration":"2028.1ms","level":"slow","span":"83a37a100660ee2a","trace":"e82039aad2c471924af98654ad1db09f"}
{"@timestamp":"2026-06-25T22:45:02.617+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2028.1ms","level":"info","span":"83a37a100660ee2a","trace":"e82039aad2c471924af98654ad1db09f"}
{"@timestamp":"2026-06-25T22:45:04.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58736 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"7e5a21a1860e0aed","trace":"5243095f70562e08cd1b4005abc8da20"}
{"@timestamp":"2026-06-25T22:45:06.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58739 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.0ms","level":"info","span":"1add21ec9bfe0acb","trace":"a818790ac63a01bf08bef1c28567cdf1"}
{"@timestamp":"2026-06-25T22:45:07.648+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2029.4ms)","duration":"2029.4ms","level":"slow","span":"5294feadd5078927","trace":"9b469ba2f0f77d0d73663a2ff845a1e4"}
{"@timestamp":"2026-06-25T22:45:07.648+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2029.4ms","level":"info","span":"5294feadd5078927","trace":"9b469ba2f0f77d0d73663a2ff845a1e4"}
{"@timestamp":"2026-06-25T22:45:08.157+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58745 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.5ms","level":"info","span":"fc1818a633d14fda","trace":"2df17c8a349c7dd15841506fd1b892ec"}
{"@timestamp":"2026-06-25T22:45:10.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58748 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"007b2592ffa80521","trace":"d861f91b8b97fb70b49256524f0ff2dd"}
{"@timestamp":"2026-06-25T22:45:12.156+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58754 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.9ms","level":"info","span":"5124eec0e9ea250d","trace":"fe1542160bc1b5efe60731676777772d"}
{"@timestamp":"2026-06-25T22:45:12.677+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.0ms)","duration":"2025.0ms","level":"slow","span":"da1a57775a58ff81","trace":"393b5545718aa665b177c7aa4464d301"}
{"@timestamp":"2026-06-25T22:45:12.677+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.0ms","level":"info","span":"da1a57775a58ff81","trace":"393b5545718aa665b177c7aa4464d301"}
{"@timestamp":"2026-06-25T22:45:14.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58759 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"3e8c3d3196a4a659","trace":"d2fbda83a343c930a8ef50ba1e2e3fca"}
{"@timestamp":"2026-06-25T22:45:16.159+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58764 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.8ms","level":"info","span":"115696738cb1c876","trace":"b470231070e4a4b94780aace12d3b817"}
{"@timestamp":"2026-06-25T22:45:17.705+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2023.0ms)","duration":"2023.0ms","level":"slow","span":"282fdab095431434","trace":"91287af1166e99600f6b628f228f2eb6"}
{"@timestamp":"2026-06-25T22:45:17.705+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2023.0ms","level":"info","span":"282fdab095431434","trace":"91287af1166e99600f6b628f228f2eb6"}
{"@timestamp":"2026-06-25T22:45:18.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58766 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"2.4ms","level":"info","span":"bcf5c5591eade6d4","trace":"d86401b2dc54eba25c73ac8c28ffc43e"}
{"@timestamp":"2026-06-25T22:45:20.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58772 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.6ms","level":"info","span":"9868ba6e6c50139d","trace":"22844f5906cb869b65e523c2fd6007bf"}
{"@timestamp":"2026-06-25T22:45:22.156+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58775 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.1ms","level":"info","span":"f392f5c471569bb5","trace":"b25c4ab65a0180c65dd565a76266148a"}
{"@timestamp":"2026-06-25T22:45:22.719+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2009.3ms)","duration":"2009.3ms","level":"slow","span":"9d2de9cfc4fe445a","trace":"7afb54f1afcce31b481af5631f052958"}
{"@timestamp":"2026-06-25T22:45:22.719+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2009.3ms","level":"info","span":"9d2de9cfc4fe445a","trace":"7afb54f1afcce31b481af5631f052958"}
{"@timestamp":"2026-06-25T22:45:24.158+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58780 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"3.9ms","level":"info","span":"3f27c47a75f8ac46","trace":"53c08ca1470eba19e77cd10014930401"}
{"@timestamp":"2026-06-25T22:45:26.162+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58782 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"6.2ms","level":"info","span":"3ef32fdaa8dd8529","trace":"552592ad769ff4270544d4f031dfb31f"}
{"@timestamp":"2026-06-25T22:45:27.778+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2053.8ms)","duration":"2053.8ms","level":"slow","span":"d4a2f00fad9d63f6","trace":"4eb1dbe6d132c16844ea906acd0f09d8"}
{"@timestamp":"2026-06-25T22:45:27.778+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2053.8ms","level":"info","span":"d4a2f00fad9d63f6","trace":"4eb1dbe6d132c16844ea906acd0f09d8"}
{"@timestamp":"2026-06-25T22:45:28.163+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58790 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"6.9ms","level":"info","span":"016832778acee71a","trace":"18415f9b0098a11c2bc34e4ca1dcd0f6"}
{"@timestamp":"2026-06-25T22:45:30.161+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58793 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.1ms","level":"info","span":"9a7c07ea39e387a1","trace":"fe26a37c07141bbbcc0d2cd13a564029"}
{"@timestamp":"2026-06-25T22:45:32.160+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58798 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"4.0ms","level":"info","span":"730d6ec42a1da781","trace":"5be93474c728c074dc454eb5a5e0676d"}
{"@timestamp":"2026-06-25T22:45:32.808+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2026.5ms)","duration":"2026.5ms","level":"slow","span":"c53f151768cf26e6","trace":"66780d4f3a2931a5011a814cb6d1163d"}
{"@timestamp":"2026-06-25T22:45:32.808+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2026.5ms","level":"info","span":"c53f151768cf26e6","trace":"66780d4f3a2931a5011a814cb6d1163d"}
{"@timestamp":"2026-06-25T22:45:34.154+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58801 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"1.4ms","level":"info","span":"cf8a3c951ab00096","trace":"c4b73f7b7e0a9f5ee36d36e5016290a7"}
{"@timestamp":"2026-06-25T22:45:37.836+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.6ms)","duration":"2025.6ms","level":"slow","span":"5e96987e4ca813c6","trace":"070fd31cf9b871df11df8274872003d1"}
{"@timestamp":"2026-06-25T22:45:37.837+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.6ms","level":"info","span":"5e96987e4ca813c6","trace":"070fd31cf9b871df11df8274872003d1"}
{"@timestamp":"2026-06-25T22:45:42.867+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2025.8ms)","duration":"2025.8ms","level":"slow","span":"bfce3ec70916d0c7","trace":"820a04bc68c34a68bfa8ef3e51ace368"}
{"@timestamp":"2026-06-25T22:45:42.867+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2025.8ms","level":"info","span":"bfce3ec70916d0c7","trace":"820a04bc68c34a68bfa8ef3e51ace368"}
{"@timestamp":"2026-06-25T22:45:44.166+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - GET /api/v1/jobs?page=1&pageSize=12 - 127.0.0.1:58816 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36","duration":"5.3ms","level":"info","span":"d197b00dc73d21ff","trace":"7f8d80d62ccb64870ceeb97e26ed8164"}
{"@timestamp":"2026-06-25T22:45:47.887+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2015.7ms)","duration":"2015.7ms","level":"slow","span":"591ab371efb2de21","trace":"980299a9b493c838515c1e615b2b2747"}
{"@timestamp":"2026-06-25T22:45:47.887+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2015.7ms","level":"info","span":"591ab371efb2de21","trace":"980299a9b493c838515c1e615b2b2747"}
{"@timestamp":"2026-06-25T22:45:52.928+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2036.5ms)","duration":"2036.5ms","level":"slow","span":"d8a9a73a7854c599","trace":"122113141f4748281ae4ebde1281cb59"}
{"@timestamp":"2026-06-25T22:45:52.928+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2036.5ms","level":"info","span":"d8a9a73a7854c599","trace":"122113141f4748281ae4ebde1281cb59"}
{"@timestamp":"2026-06-25T22:45:57.809+08:00","caller":"stat/usage.go:82","content":"CPU: 0m, MEMORY: Alloc=3.3Mi, TotalAlloc=23.6Mi, Sys=23.6Mi, NumGC=13","level":"stat"}
{"@timestamp":"2026-06-25T22:45:57.949+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2018.7ms)","duration":"2018.7ms","level":"slow","span":"8ed4b4998bf04899","trace":"8a426567c390f227feb3185576b80b54"}
{"@timestamp":"2026-06-25T22:45:57.949+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2018.7ms","level":"info","span":"8ed4b4998bf04899","trace":"8a426567c390f227feb3185576b80b54"}
{"@timestamp":"2026-06-25T22:45:59.877+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 31, pass: 31, drop: 0","level":"stat"}
{"@timestamp":"2026-06-25T22:46:00.646+08:00","caller":"stat/metrics.go:210","content":"(haixun-backend) - qps: 0.5/s, drops: 0, avg time: 812.8ms, med: 5.3ms, 90th: 2029.3ms, 99th: 2053.7ms, 99.9th: 2053.7ms","level":"stat"}
{"@timestamp":"2026-06-25T22:46:02.958+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2005.3ms)","duration":"2005.3ms","level":"slow","span":"1e3e4dad9c6d154d","trace":"1adb8248e595d7be00c6c65694d1b002"}
{"@timestamp":"2026-06-25T22:46:02.959+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2005.3ms","level":"info","span":"1e3e4dad9c6d154d","trace":"1adb8248e595d7be00c6c65694d1b002"}
{"@timestamp":"2026-06-25T22:46:08.015+08:00","caller":"handler/loghandler.go:151","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node - slowcall(2052.0ms)","duration":"2052.0ms","level":"slow","span":"e53391543851c603","trace":"d6718d0c6962ec735ea863626481c54d"}
{"@timestamp":"2026-06-25T22:46:08.016+08:00","caller":"handler/loghandler.go:167","content":"[HTTP] 200 - POST /api/v1/internal/workers/jobs/claim - 127.0.0.1:57392 - node","duration":"2052.0ms","level":"info","span":"e53391543851c603","trace":"d6718d0c6962ec735ea863626481c54d"}

View File

@ -0,0 +1,15 @@
> haixun-web@0.1.0 dev
> vite
VITE v6.4.3 ready in 192 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
10:42:45 PM [vite] (client) hmr update /src/components/AppSidebar.tsx, /src/index.css, /src/components/MobileBottomNav.tsx, /src/components/OnboardingRouteGuard.tsx, /src/onboarding/OnboardingContext.tsx, /src/components/OnboardingGuide.tsx
10:42:45 PM [vite] (client) hmr update /src/pages/SettingsPage.tsx, /src/index.css
10:42:45 PM [vite] (client) hmr invalidate /src/onboarding/OnboardingContext.tsx Could not Fast Refresh ("useOnboarding" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports
10:42:45 PM [vite] (client) hmr update /src/components/Layout.tsx, /src/index.css, /src/pages/PersonasPage.tsx, /src/components/OnboardingBanner.tsx, /src/components/AppSidebar.tsx, /src/components/MobileBottomNav.tsx, /src/components/AccountSwitcher.tsx, /src/components/OnboardingRouteGuard.tsx, /src/components/islander/IslanderCompanion.tsx, /src/components/AccountConnectionMode.tsx, /src/components/OnboardingGuide.tsx, /src/components/DevToolsPanel.tsx
10:42:45 PM [vite] (client) hmr invalidate /src/components/OnboardingGuide.tsx Could not Fast Refresh ("useOnboardingGuide" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports
10:42:45 PM [vite] (client) hmr update /src/pages/SettingsPage.tsx, /src/index.css, /src/pages/ThreadsAccountConnectionsPage.tsx, /src/pages/PersonasPage.tsx

View File

@ -0,0 +1,9 @@
> haixun-master@0.1.0 worker:style-8d
> . scripts/playwright-env.sh && npx playwright install chromium && tsx haixun-backend/worker/style-8d-worker.ts
[8d-worker] started id=local-style-8d-node-51888 api=http://127.0.0.1:8890
[8d-worker] loop error Error: internal server error
at api (/Users/daniel/Desktop/haixunMaster/haixun-backend/worker/style-8d-worker.ts:89:11)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async main (/Users/daniel/Desktop/haixunMaster/haixun-backend/worker/style-8d-worker.ts:346:19)

View File

@ -0,0 +1 @@
51805

View File

@ -0,0 +1 @@
51806

412
haixun-backend/AGENTS.md Normal file
View File

@ -0,0 +1,412 @@
# Agent Handoff Notes
這個資料夾是新的巡樓後端核心,請優先維持乾淨邊界,不要把舊 Next.js 或 `template-monorepo` 的業務包袱搬進來。
## 核心原則
- 全系統時間一律 **UTC+0**;寫入 Mongo / API 的時間欄位一律 **unix nanoseconds**`int64`)。排程的 `timezone` 只用於 cron 解讀與下發 payload不作為儲存時區。
- 複製模式,不複製舊業務。
- `logic` 做 API 編排,`model/usecase` 做可重複使用能力。
- provider adapter 不讀 setting、不碰 Mongo、不知道 HTTP。
- setting 是通用 key-value model不依賴 AI 或其他業務。
- token / API key 第一版每次 request 帶入,不寫入 config。
- SSE contract 由本服務 normalize前端不要讀 provider 原始 chunk。
- JSON API 必須使用 `code/message/data/error` envelope 與 `SSCCCDDD` 錯誤碼。
- 列表 API 必須使用 `page/pageSize` query並在 `data` 回傳 `pagination/list`
- Job 狀態轉移必須使用 guarded/conditional update不要在 API/worker 直接裸 `Update` 覆蓋 job 狀態。
- Redis job lock 的 value 是 `workerID`release / refresh 必須檢查 owner長任務必須 heartbeat。
- Auth 目前是 native email/password + JWT不包含 OAuth / OTP / MFA / Zitadel。不要為了相容 template-monorepo 把重依賴搬進來。
- AI provider token 與會員 JWT 是兩種不同 tokenAI token 每次 request header 帶入,會員 JWT 由 `/api/v1/auth/*` 簽發。
## 設計文件
- `docs/job-system-plan.md`:通用 job system 規劃,包含 template、run、schedule、Redis queue/lock、取消語意與 API 草案。
- `docs/scan-placement-plan.md`:海巡獲客(流程 B— 知識圖譜、Brave 擴展、雙軌爬取7 天重點 / 30 天補充)、產品匹配、島民交接。
## 新增 API 流程
1. 修改 `generate/api/*.api`
2. 優先使用 `make gen-api` 重新產生 handler/logic/types。
3. 若手寫 handler仍需遵守 `response.Write` 與 validator 流程。
4. SSE endpoint 不使用 `response.Write`,直接輸出 `text/event-stream`
5. 更新 `README.md` 的 API 與架構說明。
## Response / Error Code
錯誤碼格式是 `SSCCCDDD`
```text
SS = scope
CCC = category
DDD = detail
```
目前 scope
```text
10 = Facade
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
```
建立錯誤時使用:
```go
errs.For(code.AI).InputMissingRequired("缺少 AI provider token")
errs.For(code.Setting).ResNotFound("找不到設定")
errs.For(code.Job).ResInvalidState("job state changed; update rejected")
errs.For(code.Auth).AuthUnauthorized("missing bearer token")
```
不要直接手寫 `33104000` 這種數字,也不要回傳裸 `error` 給 handler 後讓使用者看到內部錯誤。
## Pagination
列表 API 使用:
```text
?page=1&pageSize=10
```
response
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
},
"list": []
}
}
```
`page/pageSize` 必須是 server 正規化後的值。不要使用 `offset/limit/items`
## 新增 Model 流程
模組放在:
```text
internal/model/<module>/
domain/entity
domain/repository
domain/usecase
repository
usecase
```
依賴方向:
```text
handler -> logic -> model/domain/usecase
model/usecase -> model/domain/repository
model/repository -> Mongo / Redis
```
不要讓 `logic` import `model/<module>/repository`
## Auth / Permission 擴充
目前已接:
```text
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout # requires member JWT
GET /api/v1/members/me
PATCH /api/v1/members/me
GET /api/v1/permissions/catalog
GET /api/v1/permissions/me
```
Auth matrix`internal/handler/routes.go`
| 路由 | 需要會員 JWT |
|------|----------------|
| `GET /api/v1/health` | 否 |
| `POST /api/v1/auth/register/login/refresh` | 否 |
| `POST /api/v1/auth/logout` | 是(`Authorization` |
| `GET /api/v1/ai/providers` | 否 |
| `POST /api/v1/ai/chat/stream/models` | 是(`X-Member-Authorization`+ provider token`Authorization` |
| `/api/v1/members/*`、`/api/v1/permissions/me` | 是(`Authorization` |
| `/api/v1/permissions/catalog`、`/api/v1/settings/*`、`/api/v1/jobs*`、`/api/v1/job/*` | 是(`Authorization` |
規則:
- 保護路由用 `internal/middleware.Auth``Authorization: Bearer <access_token>`AI 變更路由用 `middleware.MemberAuth``X-Member-Authorization`),因 `Authorization` 保留給 provider API key。
- logic 從 `authctx.ActorFromContext``tenant_id` / `uid`
- 不要在 handler 直接 parse JWTtoken 驗證集中在 `model/auth/usecase`
- 密碼只存 bcrypt hash不回傳、不寫 log。
- `members.roles` 第一版是簡化 role key。正式 RBAC 可逐步補 roles collection但不要破壞 `role_permissions` 的 tenant + role_key contract。
- `Auth.DevHeaderFallback` 只給本機開發,正式環境應關閉。
## AI Provider 擴充
新增 provider 時:
1. 在 `internal/model/ai/domain/enum` 新增 provider id。
2. 在 `internal/model/ai/provider` 新增 adapter。
3. 在 `internal/model/ai/usecase` registry 註冊 provider 與 models。
4. 確保 adapter 回傳統一 `StreamEvent`
5. 不要改 `logic/ai` 的 SSE 格式。
## Job Worker 擴充
新增 job step 時優先註冊 runner handler
```go
runner.RegisterStepHandler("analyze_8d", func(ctx context.Context, step job.StepContext) error {
if err := step.Heartbeat(ctx); err != nil {
return err
}
// do work, check cancel via job usecase if needed
return nil
})
```
規則:
- Handler 不要直接操作 Mongo / Redis透過 job usecase 更新進度、完成、失敗或取消。
- 長任務每個 checkpoint 呼叫 `StepContext.Heartbeat``RefreshRunLock`
- 收到 cancel signal 後呼叫 `AcknowledgeCancel(jobId, workerID)`,不要自行把狀態改成 `cancelled`
- release lock 時必須帶 `workerID`;不要新增無 owner 的 release helper。
## 前端設計規則(`web/`
巡樓 Console 前端在 `haixun-backend/web/`,視覺為**沉穩田園巡檢台**(動森感:天空、雲朵、奶油卡片、青綠 brand**不是**任天堂 UI 複製)。
**字體固定** Inter + Taipei Sans TC圖示僅 `AcIcon` / `AuthDecor` 內原創 SVG 線條圖。**禁止** emoji、貼圖 JPG、咖啡色木質頂欄、Nook / 任天堂命名。樣式集中在 `index.css``--hx-*` token + `hx-*` / `ac-*` / `auth-*` class
不要把舊 Next.js / `template-monorepo` UI 搬進來,也不要引入重型 UI 框架。
### 視覺架構(登入前後共用)
```text
全頁背景 .hx-scene 灰藍天空 → 淡草地單一漸層(淺/深各一套,見 index.css
裝飾層 SceneDecor 雲朵(多朵緩動)+ 淡光暈 + 小葉子;登入與 Layout 共用
奶油卡片 .auth-ticket 2px line 邊框、圓角 2rem、surface 底、可選 .ac-dialog-texture 點陣
頂部品牌列 圖示 .auth-ticket-iconbrand-soft 底)+ ink 標題;不用獨立色塊 ribbon
主內容 表單或 Outlet內文頁用 PageTitle / Card綠色 .ac-title-bar 僅內容區小標)
桌面側欄 .ac-pocket-device 掌上終端外框PATROL PAD 狀態列;固定尺寸 + .ac-pocket-scroll 內捲
手機 .ac-dock 底部最多 4 格 +「更多」sheet
```
| 區域 | 元件 | 關鍵 class |
|------|------|------------|
| 未登入 | `AuthShell` + `LoginPage` / `RegisterPage` | `hx-scene` `auth-scene` `auth-ticket` `auth-welcome` `auth-shell-form` |
| 已登入外殼 | `Layout` | `hx-scene` `ac-app-shell` `ac-app-header` `auth-ticket` `ac-app-main-inner` |
| 背景裝飾 | `AuthDecor.tsx``SceneDecor` | `hx-scene-deco` `auth-cloud--*` |
| 品牌小圖 | `AuthTicketIcon` | `auth-ticket-icon`(小屋+樹,原創 SVG |
| 側欄導覽 | `Layout` + `navApps` | `ac-pocket-device` `ac-app-tile` |
| 手機導覽 | `MobileBottomNav` | `ac-dock` |
**登入頁刻意不做的事**:上方不要獨立大色塊 header表單上方**不要**再放 `ac-title-bar`「登入」大牌(品牌已在 `auth-welcome`)。註冊頁同理可省略重複大標。
**已淘汰、勿加回**`ac-island`(改用 `hx-scene`)、`ac-wood-bar` / 咖啡色木質頂欄、`public/ac/` 貼圖、Nook Phone 文案。
### 技術棧與指令
```text
web/
src/
api/ # API clientenvelope、JWT refresh
auth/ # AuthContext
components/ # Layout、AuthShell、AuthDecor、ui、ThemeToggle、MobileBottomNav、AcIcon
theme/ # ThemeContext淺色 / 深色)
pages/ # 路由頁面
lib/ # acAssets導覽 icon key、jobStatus 等
index.css # 設計 token 與場景樣式唯一來源
```
```bash
make web-dev # dev server :5173proxy 到 :8890
make web-build # tsc + vite build
```
### 字型
| 語言 | 字型 | 載入方式 |
|------|------|----------|
| 繁體中文 | **台北黑體 Taipei Sans TC** | npm `taipei-sans-tc`,在 `index.css` `@import` Regular + Bold |
| 英文 | **Inter**(與 simular.co 相同Google Fonts 免費) | `web/index.html` link |
規則:
- `body` / 中文標題:`Inter` + `Taipei Sans TC` 混排(`--font-sans`)。
- 純英文裝飾字導覽副標、Hero 小字):加 class `display-en`,使用 `--font-en`
- 中文 `line-height` 維持 **1.7+**;不要用過細字重當標題(標題用 `font-bold` / `font-black`)。
- 只載入 Taipei Sans TC **Regular + Bold**,不要載入 Light避免小字過細。
- 不要改回 Noto Sans TC也不要手寫 `#333` 這類裸色碼當主色。
### 對比度與字級
- 內文、表頭、表單 label、卡片說明優先 `text-ink` / `text-ink-secondary`**不要**拿 `text-muted` 當主要閱讀文字。
- `text-muted` 只給次要提示筆數、hint、placeholder 用 `text-subtle`)。
- 表單輸入字級 **15px**`text-[15px]`),輸入框底用 `bg-surface` 白底,確保與背景拉開。
- 淺色 `muted``#5a6578`、深色約 `#b8c4d6`;改色時以「小字仍可舒適閱讀」為準,不要回到 `#94a3b8` 那種淡灰。
### 主題(淺色 / 深色)
- `ThemeProvider``src/theme/ThemeContext.tsx`)包住 App偏好存 `localStorage` key`haixun.theme``light` | `dark`)。
- `index.html` 內嵌 script 在 React 載入前設定 `data-theme`,避免閃爍。
- 所有顏色必須走 CSS 變數 `--hx-*`,再映射到 Tailwind `@theme``bg-canvas`、`text-brand` 等)。
- 切換按鈕用 `ThemeToggle``ac-btn-secondary` 樣式);`Layout` 頂欄與 `AuthShell` 右上角都要有。
- **禁止**在元件裡寫死 `bg-slate-*`、`text-emerald-*`、`bg-amber-*` 等 Tailwind 預設色;語意狀態用 `text-success` / `text-warning` / `text-danger``jobStatus.ts` 的 badge class。
淺色:低飽和灰藍天空 + 灰綠草地 + 奶油 `surface` + **brand 青綠**;深色:黃昏低對比、同一套 token 自動切換。頂欄與卡片內品牌區都用 **surface / ink / brand**,不要再用木色 `#c4a882` 當 header 底。
### 場景與卡片 class維護時對照
| Class | 用途 |
|-------|------|
| `.hx-scene` | 全頁天空→草地漸層(登入 + 已登入根節點) |
| `.hx-scene-deco` / `SceneDecor` | 背景雲、光暈、葉子(`pointer-events: none` |
| `.auth-ticket` | 奶油主卡片外框(登入卡、已登入主內容區) |
| `.auth-welcome` | 卡片內品牌列:圖示 + 標題 + 一句 tagline底部分隔線 |
| `.ac-app-header` | 已登入 sticky 頂欄:半透明 surface + blur**非**木色 |
| `.ac-title-bar` | 內容區綠色小標題(裝置色漸層);用於 `PageTitle` 等,**不**用於登入頁表單上方大牌 |
| `.ac-pocket-device` | 側欄掌上終端;`--pocket-width`28rem、`--pocket-screen-height` 固定,內容在 `.ac-pocket-scroll` 捲動 |
| `.ac-app-tile` / `.ac-dock` | App 格導覽、手機底欄 |
| `.auth-shell-form` | 登入/註冊表單放大字級(僅 auth 頁) |
側欄標示用 **PATROL PAD** 等中性英文裝飾字(`display-en`);圖示僅 `AcIcon` SVG。
### 色彩 token語意命名
開發時只用這些 Tailwind class值定義在 `web/src/index.css`
| Token | 用途 |
|-------|------|
| `canvas` | 全頁背景 |
| `surface` / `surface-muted` | 卡片、輸入框底 |
| `ink` / `ink-secondary` / `muted` | 主文 / 次文 / 輔助 |
| `line` | 邊框 |
| `brand` / `brand-hover` / `brand-soft` | 主 CTA、active 導覽、連結 hover |
| `glow` | 裝飾色塊(`.glow-blob-alt` |
| `success` / `warning` / `danger`(含 `*-soft` | 狀態、錯誤、Job badge |
主按鈕一律 `Button variant="primary"``bg-brand`,不要用全黑按鈕。
### 圓角與陰影
```text
--radius-sm 0.75rem 小元素、code
--radius-md 1.25rem Input / Textarea
--radius-lg 1.75rem Card
--radius-xl 2.25rem Hero、QuickLink、StatCard
--radius-pill 9999px Button、Badge、導覽 pill
```
陰影用 utility`shadow-card`(一般卡片)、`shadow-soft`主按鈕、Hero、`.auth-ticket`)。內容 Hero 可用 `ac-bulletin` + `ac-hero-gradient` token全頁裝飾雲朵走 `SceneDecor`,不要另加會打架的強色 blob。
### 共用元件(優先復用)
新頁面必須從 `src/components/ui.tsx` 組裝,不要另寫一套按鈕樣式:
| 元件 | 用途 |
|------|------|
| `PageTitle` | 頁面標題 + 副標 |
| `Card` | 內容區塊 |
| `Field` + `Input` / `Textarea` | 表單 |
| `Button` | `primary` / `ghost` / `danger` / `soft` |
| `Badge` | 標籤 pill`brand` / `sky` / `success` / `warning` / `danger` / `neutral` |
| `StatCard` / `QuickLinkCard` | 總覽統計與快捷入口 |
| `ErrorText` / `CopyableId` | 錯誤與可複製 ID |
`Button` 必須渲染 `{children}`;文案用**中文動詞**(例:「建立背景任務」「重新載入任務列表」),不要留空白小框。
### RWD手機
- `< lg`:隱藏左側欄;**底部固定導覽**最多 **4 格**(總覽 / 任務 / 排程 / **更多**),不要把漢堡或 ⋯ 選單放在左上角。
- 「更多」以底部 sheet 展開AI、模板、設定、會員、權限、主題切換、登出。
- 主內容加 `layout-main` 底部 padding避開 tab bar + `safe-area-inset-bottom`
- 寬表格包 `overflow-x-auto` + `min-w-*`,避免小螢幕擠爆版面。
### 版面與導覽
- 已登入(桌面):`Layout` = `hx-scene` 背景 + `SceneDecor` + `ac-app-header`(品牌 + 角色 chip + `ThemeToggle`+ 左 `ac-pocket-device` + 右 `auth-ticket` 主內容 `Outlet`
- 已登入(手機):同上頂欄;導覽走 `MobileBottomNav`(總覽/任務/排程/更多)。
- 側欄 App 來源:`src/lib/acAssets.ts` 的 `navApps`;圖示 key 對應 `AcIcon`
- Active 導覽:`ac-app-tile--active`brand-soft 底 + brand 字色hover`bg-brand-soft text-brand`。
- 未登入:`AuthShell` 置中 `auth-ticket` + 右上 `ThemeToggle``auth-welcome` 內品牌,表單緊接說明文字。
- 語氣:年輕、直接、短句;可帶「島民」「巡樓」等原創文案,避免企業八股與任天堂用語。
### API 與狀態
- JSON 一律走 `api/client.ts``code/message/data` envelope需登入加 `{ auth: true }`
- AI 路由用 `X-Member-Authorization`provider token 用 `Authorization`(見後端 Auth matrix
- Job 狀態中文與 badge 色:`src/lib/jobStatus.ts``jobStatusLabel` / `jobStatusBadgeClass`),列表有進行中任務時可每 3 秒 refresh。
- 不要在前端 parse JWT`uid` / `tenant_id``AuthContext` 讀。
### 新增頁面流程
1. 在 `App.tsx` 掛路由(需登入的放在 `Layout` 底下,自動享有 `hx-scene` + 頂欄 + 主內容 `auth-ticket`)。
2. 頁面內用 `PageTitle`(含 `.ac-title-bar` 小標)+ `Card` / `ac-bulletin` + `ui.tsx` 元件;色票只引用 semantic token。
3. 若需新語意色,**先**改 `index.css``--hx-*``@theme`,再改元件;不要頁面內硬編色碼。
4. 新導覽項:改 `acAssets.ts``navApps`,並在 `AcIcon` 補 SVG path。
5. 完成後執行 `make web-build`
### 島民頁面互動(可推廣 runtime
掛在 `Layout` 底下的新頁面**自動**支援島民操作,不需每頁手寫 executor。
模組入口:`web/src/lib/islander/index.ts`
| 層 | 職責 |
|----|------|
| `pageSnapshot` | 掃描 `.ac-app-shell` 內可互動元素,產生 `hx-*` ref |
| `islanderActions` | 解析/剝除 `islander-actions` JSON 區塊 |
| `actionExecutor` | 執行 navigate/click/fill/select/scroll 等;可 `registerIslanderActionHandler` 擴充 |
| `islanderAgent` | 串流回覆 → 執行 action → 回傳結果 → 自動 follow-up |
| `buildIslanderContext` | 組裝送給後端的頁面快照 |
**零設定(預設)**:路由掛在 `Layout` 即可;島民讀 DOM + `PageTitle` / `h1` 辨識頁面。預設**不**主動介紹這一頁;僅在使用者明確問頁面/操作時才附【可互動元素】(`userWantsPageContext`)。
**可選增強**(擇一):
1. `useIslanderPage({ title, purpose, hints, suggestions })` — 頁面內動態註冊說明
2. `registerIslanderPage(/^\/foo/, { title, ... })` — 在 `siteGuide.ts` 或模組 init 靜態註冊
3. HTML 慣例:`data-islander-label`(元素名稱)、`data-islander-kind`(類型)、`data-islander-ignore`(排除)、`data-islander-page-title`(頁名)
Action 協定AI 回覆末尾):
```islander-actions
[{ "type": "navigate", "path": "/settings" }, { "type": "click", "ref": "hx-3" }]
```
### 前端禁忌
- 不要引入 MUI / Ant Design / Chakra 等大型 UI 庫。
- 不要為單頁新增第三套配色、木質頂欄、或漸層彩虹按鈕。
- 不要在登入/註冊頁加回獨立大牌 `ac-title-bar` 或咖啡色 header ribbon。
- 不要讓 SSE / AI 直接吃 provider 原始 chunk後端已 normalize
- 不要用 `offset/limit` 呼叫列表 API`page` / `pageSize`
## 驗證
完成變更後至少執行:
```bash
cd haixun-backend
go mod tidy
make fmt
go test ./...
```
有動到前端時另執行:
```bash
make web-build
```

98
haixun-backend/Makefile Normal file
View File

@ -0,0 +1,98 @@
GO ?= go
GOFMT ?= gofmt
GOCTL ?= goctl
GO_ZERO_STYLE := go_zero
API_ENTRY := ./generate/api/gateway.api
GOFILES := $(shell find . -name '*.go')
.DEFAULT_GOAL := help
help: ## 顯示可用指令
@echo "Haixun Backend"
@echo ""
@grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf " make %-12s %s\n", $$1, $$2}'
tools: ## 安裝 goctl / goimports
@command -v $(GOCTL) >/dev/null 2>&1 || (echo ">> installing goctl" && $(GO) install github.com/zeromicro/go-zero/tools/goctl@latest)
@command -v goimports >/dev/null 2>&1 || (echo ">> installing goimports" && $(GO) install golang.org/x/tools/cmd/goimports@latest)
gen-api: tools ## 由 .api 生成 handler / logic / types
$(GOCTL) api go -api $(API_ENTRY) -dir . -style $(GO_ZERO_STYLE) -home generate/goctl
fmt: ## gofmt + goimports
$(GOFMT) -s -w $(GOFILES)
@command -v goimports >/dev/null 2>&1 && goimports -w . || true
test: ## 執行測試
$(GO) test ./...
run: ## 啟動 API前景
$(GO) run ./gateway.go -f etc/gateway.yaml
dev-all: ## 一鍵啟動 Mongo/Redis + API + 前端 + 8D worker背景
bash scripts/start-all.sh
stop-all: ## 一鍵停止全部開發服務
bash scripts/stop-all.sh
restart-all: ## 一鍵重啟全部開發服務
bash scripts/restart-all.sh
status-all: ## 查看全部開發服務狀態
bash scripts/status-all.sh
stop: stop-all ## 同 stop-all
restart: restart-all ## 同 restart-all
dev-8d: ## 一鍵啟動 API + Node 8D worker前景Ctrl+C 結束)
bash scripts/dev-with-style-8d.sh
CONFIG ?= etc/gateway.yaml
INIT_TENANT ?= default
INIT_EMAIL ?= admin@30cm.net
INIT_PASSWORD ?= Fafafa54088
tool-init: ## 初始化 Mongo indexes、預設權限與 admin 帳號
$(GO) run ./cmd/tool init -f $(CONFIG) -tenant $(INIT_TENANT) -email $(INIT_EMAIL) -password '$(INIT_PASSWORD)'
tool: ## 執行 cmd/toolmake tool ARGS="init -f etc/gateway.yaml"
$(GO) run ./cmd/tool $(ARGS)
web-install: ## 安裝前端依賴
cd web && npm install
web-dev: web-install ## 啟動前端 dev serverproxy 到 :8890
cd web && npm run dev
extension-pack: ## 打包 Chrome 擴充為 web/public/downloads/*.zip
bash scripts/package-extension.sh
web-build: web-install extension-pack ## 建置前端靜態檔
cd web && npm run build
node-worker-style-8d: ## 啟動 Node 8D 爬蟲 worker
cd .. && npm run worker:style-8d
check: fmt test ## 格式化並測試
prod: ## 一鍵啟動 production DockerAPI + Web + workers分身數見 deploy/.env
bash scripts/prod-up.sh
prod-update: ## 只重建/重啟 API+Web+Workersmongo/redis 不重啟,資料留在 volume
bash scripts/prod-update.sh
prod-deps: ## 只啟動 mongo+redisnamed volume 持久化)
bash scripts/prod-deps.sh
prod-down: ## 停止 stack不刪 volumeMongo/Redis 資料保留)
bash scripts/prod-down.sh
prod-wipe-data: ## 停止並刪除 mongo/redis volume危險需輸入 yes
bash scripts/prod-wipe-data.sh
prod-logs: ## 追蹤 production logs可傳 service 名make prod-logs ARGS=api
bash scripts/prod-logs.sh $(ARGS)
prod-build: web-build ## 建置靜態前端 + production images不啟動
cd deploy && docker compose -f docker-compose.prod.yml build

396
haixun-backend/README.md Normal file
View File

@ -0,0 +1,396 @@
# Haixun Backend
新的巡樓後端核心。這個資料夾刻意不直接複製 `template-monorepo` 的產物碼只沿用它的架構模式、goctl handler template 概念與必要 runtime library讓後續可以用更乾淨的邊界重建服務。
## 目前範圍
第一版先放六個核心能力:
- `setting`:通用設定模型,支援 `scope + scope_id + key` 儲存不同類型設定。
- `ai`:可替換 AI provider interface第一版支援 OpenCode Go 與 Grok/xAI並提供 SSE 串流回應。
- `job`:通用背景任務系統,支援 template/run/schedule/event、Redis queue/lock、進度、retry 與 cooperative cancel。
- `auth`native email/password 登入、JWT access/refresh token、logout revoke。
- `member`:目前登入會員的 profile 讀寫。
- `permission`permission catalog 與目前會員權限查詢。
暫時不包含 template-monorepo 裡較重的 OAuth / OTP / MFA / Zitadel 整合,也不包含 notification、Playwright worker。這些之後要接時再按服務邊界新增。
## 快速開始
```bash
cd haixun-backend
go mod download
make run
```
預設服務:
```text
http://127.0.0.1:8890
```
健康檢查:
```bash
curl http://127.0.0.1:8890/api/v1/health
```
### 8D Node 爬蟲 worker 驗證
`style-8d` job 由 `worker_type=node` 消費。啟動 Gateway 與 Redis 後,另開一個終端:
```bash
make node-worker-style-8d
```
也可以在 repo 根目錄執行:
```bash
npm run worker:style-8d
```
常用環境變數:
```text
HAIXUN_BACKEND_URL=http://127.0.0.1:8890
HAIXUN_WORKER_SECRET=... # 若 etc/gateway.yaml 設了 InternalWorker.Secretworker 需帶同一把
HAIXUN_NODE_WORKER_ID=local-8d # 可選,方便辨識 lock holder
HAIXUN_8D_MIN_SAMPLES=1 # 驗證期預設 1要嚴格一點可調高
```
前端在人設詳情頁按「開始 8D 分析」後,任務會進入:
```text
確認連線 -> 抓取樣本 -> AI 8D -> 儲存策略
```
目前 Node worker 先用 Playwright 抓 Threads 公開頁樣本並產生可驗證的 8D 結構若公開頁無法讀到足夠樣本job 會標記為 `failed` 並顯示原因,不會停在等待狀態。
## 專案結構
```text
haixun-backend/
gateway.go # go-zero server 入口
Makefile # gen-api / fmt / test / run
etc/ # runtime config
generate/
api/ # goctl .api 定義
goctl/api/handler.tpl # 從 template-monorepo 精簡改來的 handler 模板
internal/
config/ # config struct
handler/ # HTTP handler目前手寫之後可由 goctl 生成
logic/ # API 編排層
model/
setting/ # 通用設定 model
ai/ # AI provider interface + adapter
job/ # Job template/run/schedule/event usecase + repository
auth/ # JWT token issue/refresh/logout + Redis revoke store
member/ # Native member profile + password hash
permission/ # Permission catalog + role permission mapping
worker/ # 常駐背景 worker / scheduler / reaper
library/ # 最小 runtime library
response/ # 統一 JSON response envelope
svc/ # ServiceContext 組裝依賴
types/ # API request/response types
```
## 分層規則
## Response 與錯誤碼標準
所有一般 JSON API 都必須回傳同一層 envelope
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {}
}
```
成功固定:
```text
HTTP 200
code = 102000
message = SUCCESS
```
失敗格式:
```json
{
"code": 33101000,
"message": "缺少 AI provider token",
"error": {
"biz_code": "33101000",
"scope": 33,
"category": 104,
"detail": 0
}
}
```
錯誤碼採 `SSCCCDDD`
```text
SS = scope服務或模組範圍
CCC = category錯誤分類
DDD = detail細分錯誤碼未細分時為 000
```
目前 scope
```text
10 = Facade / request parse / validation
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
```
常用 category
```text
101 = InputInvalidFormat
104 = InputMissingRequired
204 = DBUnavailable
301 = ResourceNotFound
303 = ResourceConflict
401 = AuthUnauthorized
505 = AuthForbidden
601 = SystemInternal
802 = ServiceThirdParty
```
實作規則:
- Handler 成功/失敗都用 `internal/response.Write`SSE endpoint 例外。
- Request parse / validation 錯誤用 `response.WrapRequestError`,會落在 Facade scope。
- Model/usecase 內建立錯誤時使用 `errors.For(code.<Scope>)` builder不要手刻數字。
- 不要把 provider 原始錯誤完整洩漏到前端;必要時只保留可排查的摘要。
### 分頁標準
列表型 API 的 query 使用 `page` / `pageSize`
```text
GET /api/v1/settings/user/user_123?page=1&pageSize=10
```
回應的分頁資訊放在 `data.pagination`,資料陣列放在 `data.list`
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 42,
"page": 1,
"pageSize": 10,
"totalPages": 5
},
"list": []
}
}
```
規則:
- `page` 從 1 開始。
- `pageSize <= 0` 時由 server 套用預設值。
- `pageSize` 超過 server 上限時由 server 截斷。
- `totalPages = ceil(total / pageSize)`
- response 內的 `page/pageSize` 必須回傳 server 正規化後的值。
### logic
`internal/logic/*` 只負責一次 API 請求的流程編排:
- 轉換 HTTP types 與 usecase DTO
- 呼叫一個或多個 model usecase
- 不直接操作 Mongo / Redis
- 不放 provider HTTP 細節
### model
`internal/model/*` 放可重複使用的業務能力:
- `domain/entity`:資料結構
- `domain/repository`repository interface
- `domain/usecase`usecase interface 與 DTO
- `repository`Mongo / Redis 實作
- `usecase`:業務能力實作
### provider
`internal/model/ai/provider` 只負責外部 AI API adapter
- 不讀 setting
- 不碰 HTTP handler
- 不存 token
- token 每次由 request 帶入
## Setting Model
設定使用 typed setting 形式:
```json
{
"scope": "user",
"scope_id": "user_123",
"key": "ai.default",
"value": {
"provider": "opencode-go",
"model": "deepseek-v4-pro",
"temperature": 0.7,
"max_tokens": 2000
},
"version": 1
}
```
API
```text
GET /api/v1/settings/:scope/:scope_id?page=1&pageSize=10
GET /api/v1/settings/:scope/:scope_id/:key
PUT /api/v1/settings/:scope/:scope_id/:key
DELETE /api/v1/settings/:scope/:scope_id/:key
```
`setting` model 不知道 AI、Threads、crawler 等業務含義。各業務 model 自己解讀對應 key 的 value。
## Auth / Member / Permission
這版從 `template-monorepo` 精簡搬入會員、權限與 token 的核心概念,但不搬 OAuth / OTP / MFA / Zitadel 依賴。
Auth 採 native email/password
```text
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
```
`register` / `login` 回傳:
```json
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 900,
"uid": "user_uid",
"token_type": "Bearer"
}
```
保護路由使用:
```http
Authorization: Bearer <access_token>
```
本機開發可以開啟 `Auth.DevHeaderFallback`,用 header 模擬登入:
```http
X-Tenant-ID: default
X-UID: user_uid
```
Member API
```text
GET /api/v1/members/me
PATCH /api/v1/members/me
```
Permission API
```text
GET /api/v1/permissions/catalog?tree=true
GET /api/v1/permissions/me?include_tree=true
```
資料模型:
- `members`tenant-scoped profile、email、bcrypt password hash、roles。
- `permissions`:平台 permission catalog。
- `role_permissions`tenant + role_key 對 permission catalog 的綁定。
- Redis `auth:jwt:*`access/refresh pair 與 blacklist。Redis 未配置時仍可簽發 token但 refresh/logout revoke 不會持久化。
## AI Provider
AI token 不存在 config呼叫時每次帶入且**只放 HTTP header**,不要放 JSON body避免 log / 回應洩漏):
```http
Authorization: Bearer sk-...
Content-Type: application/json
{
"provider": "opencode-go",
"model": "deepseek-v4-pro",
"messages": [
{ "role": "user", "content": "請幫我寫一段文案" }
]
}
```
API
```text
GET /api/v1/ai/providers
POST /api/v1/ai/providers/:provider/models
POST /api/v1/ai/chat
POST /api/v1/ai/chat/stream
```
- `GET /providers`:只回傳 catalogid、label、streams不含 models、不含 token。
- `POST /providers/:provider/models`:向 provider 的 `/models` 動態拉清單,需帶 `Authorization: Bearer <token>`
- 回應與錯誤訊息不會 echo tokenprovider 原始錯誤 body 也不會直接回傳給前端。
串流 endpoint 使用 SSE
```text
event: delta
data: {"type":"delta","text":"..."}
event: done
data: {"type":"done","finish_reason":"stop"}
```
## Job System
Job 系統的詳細設計在 `docs/job-system-plan.md`。目前 runtime 原則:
- MongoDB 的 `job_runs` 是狀態真相來源claim、cancel、complete、fail、retry 必須使用 conditional update避免 worker 與 API 互相覆蓋狀態。
- Redis `jobs:lock:<jobId>` 的 value 是 `workerID`release / refresh 必須檢查 owner只能由持有 lock 的 worker 操作。
- Worker 執行長任務時要定期呼叫 `RefreshRunLock(jobId, workerID, ttlSeconds)`,避免 reaper 誤判過期。
- Runner 支援 `RegisterStepHandler(stepID, handler)` 註冊自訂 step handler未註冊時會走 demo handler。自訂 handler 可用 `StepContext.Heartbeat` 續約 lock。
- 取消採 cooperative cancellationAPI 先寫 `cancel_requested` 與 Redis cancel signalworker checkpoint 讀取後呼叫 `AcknowledgeCancel(jobId, workerID)`
## OpenCode Go 注意事項
第一版 OpenCode Go 先走 OpenAI-compatible `/chat/completions`
```text
https://opencode.ai/zen/go/v1/chat/completions
```
目前已處理 Kimi 模型 `temperature = 1` 的特殊規則。部分 OpenCode Go 模型官方文件標示為 Anthropic-compatible `/messages`,後續可在 `internal/model/ai/provider` 新增 messages adapter不需要改 logic 或前端 SSE contract。
## 下一步建議
1. 用 `goctl` 重新生成 handler / logic / types確認 `.api` 與手寫版本對齊。
2. 補 `setting` repository 測試與 Mongo integration 測試。
3. 補 AI provider mock`logic/ai` 不需要真的打 provider 也能測。
4. 新增 credential service 或 Vault/KMS 整合,但不要把 token 放進 provider config。
5. 新增 worker/job model讓 Go worker 與 Node Playwright worker 共用同一套 job contract。
## 設計文件
- [Job 核心系統規劃](docs/job-system-plan.md):通用 job template、run、schedule、事件、取消語意與 worker contract。

View File

@ -0,0 +1,87 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"strings"
"time"
"haixun-backend/internal/bootstrap"
"haixun-backend/internal/config"
"github.com/zeromicro/go-zero/core/conf"
)
func main() {
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
switch os.Args[1] {
case "init":
if err := runInit(os.Args[2:]); err != nil {
fmt.Fprintf(os.Stderr, "[tool] error: %v\n", err)
os.Exit(1)
}
default:
fmt.Fprintf(os.Stderr, "[tool] unknown command: %s\n", os.Args[1])
printUsage()
os.Exit(1)
}
}
func runInit(args []string) error {
fs := flag.NewFlagSet("init", flag.ExitOnError)
configFile := fs.String("f", "etc/gateway.yaml", "config file")
tenantID := fs.String("tenant", envOr("INIT_TENANT_ID", "default"), "tenant id for admin and role permissions")
email := fs.String("email", envOr("INIT_ADMIN_EMAIL", "admin@haixun.local"), "bootstrap admin email")
password := fs.String("password", envOr("INIT_ADMIN_PASSWORD", "Admin-Pass-1!"), "bootstrap admin password")
displayName := fs.String("display-name", envOr("INIT_ADMIN_DISPLAY_NAME", "Admin"), "bootstrap admin display name")
if err := fs.Parse(args); err != nil {
return err
}
var cfg config.Config
conf.MustLoad(*configFile, &cfg)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
report, err := bootstrap.Init(ctx, cfg, bootstrap.InitOptions{
TenantID: strings.TrimSpace(*tenantID),
AdminEmail: strings.TrimSpace(*email),
AdminPass: *password,
DisplayName: strings.TrimSpace(*displayName),
})
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "[tool] indexes ensured\n")
fmt.Fprintf(os.Stderr, "[tool] permissions catalog seeded\n")
fmt.Fprintf(os.Stderr, "[tool] role_permissions seeded (admin=all, user=default)\n")
if report.AdminCreated {
fmt.Fprintf(os.Stderr, "[tool] admin created uid=%s email=%s tenant=%s\n", report.AdminUID, *email, *tenantID)
} else {
fmt.Fprintf(os.Stderr, "[tool] admin exists uid=%s email=%s tenant=%s (roles ensured admin)\n", report.AdminUID, *email, *tenantID)
}
fmt.Printf("export INIT_TENANT_ID=%s\n", *tenantID)
fmt.Printf("export INIT_ADMIN_EMAIL=%s\n", *email)
fmt.Printf("export INIT_ADMIN_PASSWORD=%s\n", *password)
fmt.Printf("export INIT_ADMIN_UID=%s\n", report.AdminUID)
return nil
}
func envOr(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return fallback
}
func printUsage() {
fmt.Fprintf(os.Stderr, "usage:\n")
fmt.Fprintf(os.Stderr, " tool init [-f etc/gateway.yaml] [-tenant default] [-email admin@haixun.local] [-password ...]\n")
}

View File

@ -0,0 +1,43 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"haixun-backend/internal/config"
"haixun-backend/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
)
var configFile = flag.String("f", "etc/gateway.worker.yaml", "config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
if !c.JobWorker.Enabled {
fmt.Fprintln(os.Stderr, "[worker] JobWorker.Enabled must be true")
os.Exit(1)
}
sc := svc.NewServiceContext(c)
defer sc.Close(context.Background())
fmt.Printf(
"[worker] started type=%s (scheduler=%v reaper=%v)\n",
c.JobWorker.WorkerType,
c.JobScheduler.Enabled,
c.JobReaper.Enabled,
)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
fmt.Println("[worker] shutting down")
}

View File

@ -0,0 +1,40 @@
# 複製為 deploy/.env 後再啟動cp deploy/.env.example deploy/.env
# ── 對外埠 ──
HAIXUN_WEB_PORT=8080
# ── 前端打包模式 ──
# static = 本機 make web-build 後 nginx 只 COPY dist預設最快
# docker = 在 Docker 內跑 npm build需改 compose 用 Dockerfile.web
# HAIXUN_WEB_BUILD_MODE=static
# ── Worker 分身數make prod 會帶入 docker compose --scale──
GO_WORKER_REPLICAS=1
NODE_STYLE8D_WORKER_REPLICAS=1
# ── Mongo / Redis容器內預設通常不用改──
# 資料存在 Docker named volumehaixun-prod_mongo_data、haixun-prod_redis_data
# prod-down 不會刪 volume重啟 container 資料仍在。
# 只改版程式make prod-update不碰 mongo/redis
HAIXUN_MONGO_URI=mongodb://mongo:27017
HAIXUN_MONGO_DATABASE=haixun
HAIXUN_REDIS_ADDR=redis:6379
# ── 安全金鑰(正式環境務必更換)──
HAIXUN_AUTH_ACCESS_SECRET=change-me-access-secret
HAIXUN_AUTH_REFRESH_SECRET=change-me-refresh-secret
HAIXUN_WORKER_SECRET=change-me-worker-secret
# ── 首次初始化管理員make prod 會自動跑 init已存在則跳過建立──
INIT_TENANT_ID=default
INIT_ADMIN_EMAIL=admin@haixun.local
INIT_ADMIN_PASSWORD=Admin-Pass-1!
# ── Node 8D worker 選項 ──
# HAIXUN_NODE_WORKER_ID=custom-node-worker-1
# HAIXUN_WORKER_POLL_MS=3000
# ── 略過自動 init ──
# 預設:若 Mongo 已有 members 會自動跳過 init。
# 強制重跑 initPROD_FORCE_INIT=1 make prod
# HAIXUN_SKIP_INIT=1

View File

@ -0,0 +1,22 @@
# syntax=docker/dockerfile:1
FROM golang:1.22-bookworm AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-api .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-worker ./cmd/worker
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-tool ./cmd/tool
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates gettext-base curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /out/haixun-api /out/haixun-worker /out/haixun-tool /app/
COPY deploy/config/gateway.runtime.yaml.tpl deploy/config/gateway.worker.runtime.yaml.tpl /app/deploy/config/
COPY deploy/docker/entrypoint-api.sh deploy/docker/entrypoint-worker.sh deploy/docker/entrypoint-init.sh /app/deploy/docker/
RUN chmod +x /app/deploy/docker/entrypoint-api.sh /app/deploy/docker/entrypoint-worker.sh /app/deploy/docker/entrypoint-init.sh
EXPOSE 8890
ENTRYPOINT ["/app/deploy/docker/entrypoint-api.sh"]

View File

@ -0,0 +1,9 @@
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/playwright:v1.49.1-noble AS base
WORKDIR /app
COPY worker/package.json ./
RUN npm install
COPY worker/ ./
ENV NODE_ENV=production
CMD ["npx", "tsx", "style-8d-worker.ts"]

View File

@ -0,0 +1,14 @@
# syntax=docker/dockerfile:1
# 備用:無本機 Node 時在 Docker 內編譯。預設請用 Dockerfile.web.static + make web-build。
FROM node:22-bookworm AS web-builder
WORKDIR /src/web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=web-builder /src/web/dist /usr/share/nginx/html
EXPOSE 80

View File

@ -0,0 +1,7 @@
# syntax=docker/dockerfile:1
# 本機先執行 make web-build再打包純靜態檔 + nginx無 Node 編譯,建置最快)
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY web/dist /usr/share/nginx/html
EXPOSE 80

View File

@ -0,0 +1,64 @@
# 本機依賴Docker Compose
Gateway 啟用 **Notification** / **Member OTP** 需要:
| 服務 | 用途 | 預設埠 |
|------|------|--------|
| **MongoDB** | `notifications`、`notification_dlq` collections | 27017 |
| **Redis** | 冪等、配額、異步重試佇列、member OTP challenge | 6379 |
| MailHog選用 | 本機 SMTP 測試 | 1025 / 8025 |
| OpenLDAP`make ldap-up` / `make k6-up` | ZITADEL LDAP IdP 本機目錄 | 389 |
| ZITADEL`make k6-up` | OIDC / Social / LDAP 登入 | 8080 |
Mongo **不需要**事先手動建 collection應用程式寫入時會自動建立。索引由 init script 或 `make mongo-index` 建立。
## 快速開始
```bash
# 1. 啟動 Mongo + Redis
make deps-up
# 2.(選用)含 MailHog
make deps-up-smtp
# 3. 確認索引(首次 docker volume 通常已由 init 建立;可再跑一次保險)
make mongo-index
# 4. 啟動 Gateway使用 etc/gateway.dev.yaml
make run-dev
```
## Mongo collections
| Collection | 模組 | 說明 |
|------------|------|------|
| `notifications` | notification | 發送紀錄、冪等 |
| `notification_dlq` | notification | 超過 MaxRetry 的死信 |
索引定義見 [`deploy/mongo/init/01-gateway-indexes.js`](mongo/init/01-gateway-indexes.js),與 Go 的 `Index20260520001UP` 一致。
## 常用指令
```bash
make deps-up # docker compose up -d mongo redis
make deps-up-smtp # 再加上 mailhogprofile smtp
make ldap-up # 只起 OpenLDAPprofile ldap
make k6-up # 全棧含 OpenLDAP + ZITADEL見 deploy/zitadel、deploy/openldap README
make ldap-test # 確認 LDAP 測試帳號 alice/bob
make deps-down # 停止並移除容器(保留 volume
make deps-down-v # 停止並刪除 volume會清掉 Mongo 資料)
make deps-logs # 查看 log
make mongo-index # 手動建立/補齊索引
```
LDAP 本機測試:[deploy/openldap/README.md](openldap/README.md)
## 連線設定
設定說明:[`etc/README.md`](../etc/README.md)
| 檔案 | 用途 |
|------|------|
| [`etc/gateway.yaml`](../etc/gateway.yaml) | 預設,無需 Docker |
| [`etc/gateway.dev.example.yaml`](../etc/gateway.dev.example.yaml) | 範例(可提交) |
| `etc/gateway.dev.yaml` | 本機專用(**勿提交**,見 `.gitignore` |

View File

@ -0,0 +1,35 @@
Name: haixun-backend
Host: 0.0.0.0
Port: 8890
Timeout: 120000
Mongo:
URI: ${HAIXUN_MONGO_URI}
Database: ${HAIXUN_MONGO_DATABASE}
TimeoutSeconds: 10
Redis:
Addr: ${HAIXUN_REDIS_ADDR}
DB: 0
Auth:
AccessSecret: ${HAIXUN_AUTH_ACCESS_SECRET}
RefreshSecret: ${HAIXUN_AUTH_REFRESH_SECRET}
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: false
InternalWorker:
Secret: ${HAIXUN_WORKER_SECRET}
JobWorker:
Enabled: false
WorkerType: go
JobScheduler:
Enabled: true
IntervalSeconds: 60
JobReaper:
Enabled: true
IntervalSeconds: 30

View File

@ -0,0 +1,35 @@
Name: haixun-worker
Host: 0.0.0.0
Port: 8891
Timeout: 120000
Mongo:
URI: ${HAIXUN_MONGO_URI}
Database: ${HAIXUN_MONGO_DATABASE}
TimeoutSeconds: 10
Redis:
Addr: ${HAIXUN_REDIS_ADDR}
DB: 0
Auth:
AccessSecret: ${HAIXUN_AUTH_ACCESS_SECRET}
RefreshSecret: ${HAIXUN_AUTH_REFRESH_SECRET}
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: false
InternalWorker:
Secret: ${HAIXUN_WORKER_SECRET}
JobWorker:
Enabled: true
WorkerType: go
JobScheduler:
Enabled: false
IntervalSeconds: 60
JobReaper:
Enabled: false
IntervalSeconds: 30

View File

@ -0,0 +1,110 @@
name: haixun-prod
services:
mongo:
image: mongo:7
restart: unless-stopped
environment:
MONGO_INITDB_DATABASE: haixun
# named volume重啟/改版不會清資料(只有 prod-wipe-data 或 docker volume rm 才會)
volumes:
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 5s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
# AOF + named volume重啟後 queue/lock 狀態可從磁碟恢復
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 12
api:
build:
context: ..
dockerfile: deploy/Dockerfile.api
restart: unless-stopped
env_file:
- .env
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8890/api/v1/health >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
go-worker:
build:
context: ..
dockerfile: deploy/Dockerfile.api
restart: unless-stopped
entrypoint: ["/app/deploy/docker/entrypoint-worker.sh"]
env_file:
- .env
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
api:
condition: service_healthy
node-worker-style-8d:
build:
context: ..
dockerfile: deploy/Dockerfile.node-worker
restart: unless-stopped
env_file:
- .env
environment:
HAIXUN_BACKEND_URL: http://api:8890
HAIXUN_WORKER_SECRET: ${HAIXUN_WORKER_SECRET}
HAIXUN_NODE_WORKER_ID: ${HAIXUN_NODE_WORKER_ID:-}
HAIXUN_WORKER_POLL_MS: ${HAIXUN_WORKER_POLL_MS:-3000}
depends_on:
api:
condition: service_healthy
web:
build:
context: ..
dockerfile: deploy/Dockerfile.web.static
restart: unless-stopped
ports:
- "${HAIXUN_WEB_PORT:-8080}:80"
depends_on:
api:
condition: service_healthy
init:
profiles: ["init"]
build:
context: ..
dockerfile: deploy/Dockerfile.api
entrypoint: ["/app/deploy/docker/entrypoint-init.sh"]
env_file:
- .env
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
volumes:
mongo_data:
redis_data:

View File

@ -0,0 +1,37 @@
services:
mongo:
image: mongo:7
container_name: gateway-mongo
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_DATABASE: gateway
volumes:
- mongo_data:/data/db
- ./mongo/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
redis:
image: redis:7-alpine
container_name: gateway-redis
restart: unless-stopped
ports:
- "6379:6379"
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
mongo_data:
redis_data:

View File

@ -0,0 +1,15 @@
#!/bin/sh
set -eu
: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}"
: "${HAIXUN_MONGO_DATABASE:=haixun}"
: "${HAIXUN_REDIS_ADDR:=redis:6379}"
: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}"
: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}"
: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}"
export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR
export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET
envsubst < /app/deploy/config/gateway.runtime.yaml.tpl > /tmp/gateway.runtime.yaml
exec /app/haixun-api -f /tmp/gateway.runtime.yaml

View File

@ -0,0 +1,22 @@
#!/bin/sh
set -eu
: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}"
: "${HAIXUN_MONGO_DATABASE:=haixun}"
: "${HAIXUN_REDIS_ADDR:=redis:6379}"
: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}"
: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}"
: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}"
: "${INIT_TENANT_ID:=default}"
: "${INIT_ADMIN_EMAIL:=admin@haixun.local}"
: "${INIT_ADMIN_PASSWORD:?INIT_ADMIN_PASSWORD is required}"
export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR
export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET
envsubst < /app/deploy/config/gateway.runtime.yaml.tpl > /tmp/gateway.runtime.yaml
exec /app/haixun-tool init \
-f /tmp/gateway.runtime.yaml \
-tenant "$INIT_TENANT_ID" \
-email "$INIT_ADMIN_EMAIL" \
-password "$INIT_ADMIN_PASSWORD"

View File

@ -0,0 +1,15 @@
#!/bin/sh
set -eu
: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}"
: "${HAIXUN_MONGO_DATABASE:=haixun}"
: "${HAIXUN_REDIS_ADDR:=redis:6379}"
: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}"
: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}"
: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}"
export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR
export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET
envsubst < /app/deploy/config/gateway.worker.runtime.yaml.tpl > /tmp/gateway.worker.runtime.yaml
exec /app/haixun-worker -f /tmp/gateway.worker.runtime.yaml

View File

@ -0,0 +1,31 @@
// Gateway MongoDB 初始化(僅在 data volume 首次建立時執行)
// 與 internal/model/notification/repository/* Index20260520001UP 對齊
// 既有 volume 請執行make mongo-index
db = db.getSiblingDB('gateway');
print('Creating indexes on notifications...');
db.notifications.createIndex(
{ tenant_id: 1, kind: 1, idempotency_key: 1 },
{ unique: true, name: 'idx_notifications_tenant_kind_idempotency' }
);
db.notifications.createIndex(
{ tenant_id: 1, uid: 1, occurred_at: -1 },
{ name: 'idx_notifications_tenant_uid_occurred' }
);
db.notifications.createIndex(
{ status: 1, attempts: 1, occurred_at: 1 },
{ name: 'idx_notifications_status_attempts_occurred' }
);
print('Creating indexes on notification_dlq...');
db.notification_dlq.createIndex(
{ tenant_id: 1, occurred_at: -1 },
{ name: 'idx_notification_dlq_tenant_occurred' }
);
print('Gateway Mongo init done.');

View File

@ -0,0 +1,55 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# Vite 產物:檔名含 hash可長期快取
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location /downloads/ {
add_header Cache-Control "public, max-age=86400";
try_files $uri =404;
}
location /illustrations/ {
add_header Cache-Control "public, max-age=86400";
try_files $uri =404;
}
# SPA 入口與路由:不快取,避免部署後仍載入舊版 shell
location = /index.html {
add_header Cache-Control "no-cache";
try_files $uri =404;
}
location /api/ {
proxy_pass http://api:8890;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@ -0,0 +1,381 @@
# Job 核心系統規劃
## 目標
建立一套通用 job system讓任何長任務、流程任務、定時任務未來都能共用。Job 不只是背景任務,而是「有模板、有設定、有狀態、有進度、有取消能力、有重跑策略、有排程能力」的工作單元。
## 核心設計
採用:
```text
Mongo = job/template/run/history 的真相來源
Redis = queue、distributed lock、schedule tick、短期 lease
```
```mermaid
flowchart LR
Api[GoAPI] --> Template[JobTemplate]
Api --> JobRun[JobRunMongo]
JobRun --> RedisQueue[RedisQueue]
Scheduler[SchedulerTick] --> RedisQueue
Worker[Worker] --> RedisQueue
Worker --> JobRun
Worker --> Step[JobStep]
```
## 核心概念
### JobTemplate
Template 定義「這種 job 要怎麼做」。例如:
```text
demo_long_task
external_worker_task
scheduled_report
multi_step_pipeline
```
Template 要回答:
- 這個 job 的輸入 payload schema 是什麼
- 有哪些 steps
- 最終狀態是什麼
- 可不可以重複執行
- 是否允許同 account / 同 target 同時跑
- retry policy 是什麼
- timeout 是多少
- 是否可被排程
- 是否支援取消,以及取消時 worker 要如何收斂
### JobRun
JobRun 是每一次執行實例。它引用 template保存當次 payload、狀態、進度、結果、錯誤與執行歷史。
### JobSchedule
JobSchedule 是「何時建立 JobRun」。支援
- cron
- enabled / disabled
- timezone
- payload template
- target scope例如 user/account/system
- nextRunAt / lastRunAt
### JobFlow
Flow 是多步驟流程。第一版不用做完整 DAG先支援線性 steps
```text
multi_step_pipeline:
1. prepare
2. execute
3. finalize
```
之後再擴成 DAG 或 conditional branch。
## Mongo Collections
### `job_templates`
```json
{
"_id": "...",
"type": "demo_long_task",
"version": 1,
"name": "示範長任務",
"description": "展示 job template、進度、取消、重跑與排程能力",
"enabled": true,
"repeatable": true,
"concurrencyPolicy": "reject_same_scope",
"dedupeKeys": ["scope_id", "target"],
"timeoutSeconds": 600,
"cancelPolicy": {
"supported": true,
"mode": "cooperative",
"graceSeconds": 30
},
"retryPolicy": {
"maxAttempts": 2,
"backoffSeconds": [30, 120]
},
"steps": [
{ "id": "prepare", "name": "準備資料", "workerType": "go", "timeoutSeconds": 60, "cancelable": true },
{ "id": "execute", "name": "執行任務", "workerType": "go", "timeoutSeconds": 300, "cancelable": true },
{ "id": "finalize", "name": "整理結果", "workerType": "go", "timeoutSeconds": 30, "cancelable": false }
],
"createAt": 0,
"updateAt": 0
}
```
### `job_runs`
```json
{
"_id": "...",
"templateType": "demo_long_task",
"templateVersion": 1,
"scope": "user",
"scopeId": "user_123",
"status": "pending",
"phase": "prepare",
"payload": {},
"progress": {
"summary": "等待 worker 執行",
"percentage": 20,
"steps": []
},
"result": null,
"error": null,
"attempt": 0,
"maxAttempts": 2,
"lockedBy": null,
"lockedUntil": null,
"cancelRequestedAt": null,
"cancelReason": null,
"scheduledAt": null,
"startedAt": null,
"completedAt": null,
"createAt": 0,
"updateAt": 0
}
```
### `job_schedules`
```json
{
"_id": "...",
"templateType": "demo_long_task",
"scope": "user",
"scopeId": "user_123",
"enabled": true,
"cron": "0 9 * * *",
"timezone": "Asia/Taipei",
"payloadTemplate": {},
"lastRunAt": null,
"nextRunAt": 0,
"createAt": 0,
"updateAt": 0
}
```
### `job_events`
用來觀察與 audit
```json
{
"_id": "...",
"jobId": "...",
"type": "status_changed",
"from": "pending",
"to": "running",
"message": "worker claimed job",
"metadata": {},
"createAt": 0
}
```
## Status Model
```text
pending = 已建立,等待 queue
queued = 已推進 Redis queue
running = worker 執行中
waiting_worker = 等外部 worker 回寫
cancel_requested = 使用者已要求取消,等待 worker cooperative stop
succeeded = 成功完成
failed = 最終失敗
cancelled = 使用者取消
expired = lock/timeout 過期後無法恢復
```
Step status
```text
pending | running | succeeded | failed | skipped | cancelled
```
## 取消語意
取消是第一版必做能力,採 cooperative cancellation
```mermaid
flowchart LR
User[User] --> ApiCancel[CancelAPI]
ApiCancel --> Run[JobRun cancel_requested]
Run --> RedisCancel[RedisCancelSignal]
Worker[Worker] -->|"poll cancel flag"| Run
Worker --> Stop[StopCurrentStep]
Stop --> Final[JobRun cancelled]
```
規則:
- `pending` / `queued`:取消後直接變 `cancelled`,並盡量從 Redis queue 移除若無法移除worker claim 時必須檢查狀態並跳過。
- `running`:狀態改為 `cancel_requested`,寫入 `cancelRequestedAt` / `cancelReason`worker 必須在 step 間或長任務 checkpoint 檢查取消旗標。
- `waiting_worker`:狀態改為 `cancel_requested`,同時寫 Redis cancel signal外部 worker 回寫前要檢查 job 狀態。
- `succeeded` / `failed` / `cancelled` / `expired`:不可取消,回傳 ResourceInvalidState。
- worker 收到取消後呼叫 `AcknowledgeCancel(jobId, workerId)`,釋放 lock寫入 `job_events`,狀態變 `cancelled`
- 若 `cancel_requested` 超過 template 的 `cancelPolicy.graceSeconds`scheduler/reaper 可標記為 `cancelled``expired`,第一版建議標記 `cancelled` 並記錄 timeout event。
## 狀態與 Lock 安全規則
第一版已把最容易出問題的 race condition 收斂在 repository / usecase
- `ClaimNext` 只能從 `pending` / `queued` conditional update 成 `running`。如果 API 同時取消Mongo update 會被拒絕。
- `RequestCancel` 只能從 cancellable 狀態 conditional update`pending` / `queued` 直接變 `cancelled``running` / `waiting_worker``cancel_requested`
- `CompleteRun` / `FailRun` / `UpdateProgress` 必須帶 `workerID`,並且只能更新 `lockedBy == workerID` 的 job。
- Redis `jobs:lock:<jobId>` 的 value 是 `workerID``ReleaseLock` / `RefreshLock` 使用 owner check避免舊 worker 誤刪新 worker 的 lock。
- Worker 長任務要定期 heartbeat呼叫 `RefreshRunLock(jobId, workerID, ttlSeconds)`。自訂 step handler 可用 `StepContext.Heartbeat`
之後新增狀態轉移時,不要直接使用裸 `Update`;若是生命週期狀態,應新增明確的 guarded repository 方法或使用現有 conditional update。
## Redis Keys
```text
jobs:queue:<workerType> # list 或 streamworker 消費
jobs:lock:<jobId> # lease lock
jobs:scheduler:lock # scheduler singleton lock
jobs:dedupe:<template>:<hash> # 防止同 scope 重複跑
jobs:cancel:<jobId> # cancel signalworker checkpoint 讀取
```
第一版建議用 Redis List 即可,之後需要 ack/replay 再升級 Redis Streams。
## API 規劃
```text
GET /api/v1/job/templates
GET /api/v1/job/templates/:type
PUT /api/v1/job/templates/:type
POST /api/v1/jobs
GET /api/v1/jobs/:id
GET /api/v1/jobs?page=1&pageSize=20
POST /api/v1/jobs/:id/cancel
POST /api/v1/jobs/:id/retry
GET /api/v1/job/schedules?page=1&pageSize=20
POST /api/v1/job/schedules
PUT /api/v1/job/schedules/:id
POST /api/v1/job/schedules/:id/enable
POST /api/v1/job/schedules/:id/disable
```
所有列表回應使用目前標準:
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 42,
"page": 1,
"pageSize": 10,
"totalPages": 5
},
"list": []
}
}
```
## 分層規劃
```text
internal/model/job/
domain/entity/template.go
domain/entity/run.go
domain/entity/schedule.go
domain/entity/event.go
domain/enum/status.go
domain/repository/*.go
domain/usecase/*.go
repository/mongo_*.go
repository/redis_queue.go
usecase/template_usecase.go
usecase/run_usecase.go
usecase/schedule_usecase.go
usecase/progress_usecase.go
usecase/worker_usecase.go
internal/logic/job/
create_job_logic.go
get_job_logic.go
list_jobs_logic.go
cancel_job_logic.go
retry_job_logic.go
template_logic.go
schedule_logic.go
internal/worker/job/
runner.go
scheduler.go
dispatcher.go
```
## Template 執行規則
### 可不可以重複做
`repeatable``concurrencyPolicy` 控制:
```text
repeatable=false
同 dedupe key 完成過就不再建立
repeatable=true + reject_same_scope
已有 running/pending job 時拒絕建立
repeatable=true + allow_parallel
允許平行跑
repeatable=true + replace_existing
取消舊 job建立新 job
```
### 最終狀態
Template 可定義成功條件:
```text
successWhen = all_steps_succeeded
```
第一版只支援 `all_steps_succeeded`。之後再加 `any_step_succeeded` 或 conditional flow。
### 取消能力
Template 用 `cancelPolicy` 控制取消:
```text
supported=false
API 不允許取消此 job
mode=cooperative
worker checkpoint 檢查取消旗標後收斂
graceSeconds=30
cancel_requested 超過 grace 後由 reaper 收斂
```
Step 用 `cancelable` 控制目前步驟是否可立即停止。若目前 step 不可取消worker 需要在 step 結束後停止後續 steps最後狀態仍為 `cancelled`
## 第一版建議實作順序
1. 建 `model/job` 的 enum/entity/repository interface。
2. 實作 Mongo repositoriestemplate/run/schedule/event。
3. 實作 Redis queue/lock repository。
4. 實作 `CreateRun`:讀 template、檢查 repeat/concurrency、建立 run、push queue。
5. 實作 `ClaimNext`worker 從 Redis 取 jobMongo 設 lock/status。
6. 實作 `RequestCancel`:狀態轉 `cancel_requested` 或直接 `cancelled`,寫 Redis cancel signal 與 event。
7. 實作 `AcknowledgeCancel`worker 收斂後釋放 lock狀態轉 `cancelled`
8. 實作 `UpdateProgress`、`Complete`、`Fail`、`Retry`。
9. 實作 schedule tick`job_schedules.nextRunAt <= now`,建立 JobRun。
10. 實作 API 與文件。

View File

@ -0,0 +1,512 @@
# 海巡獲客計畫:知識圖譜 + 雙軌爬取 + 島民交接
> 在既有「背景 Job + 島民交接」上,新增 Topic Knowledge GraphBrave 驅動)與**雙維度 Tag**(相關 + 近期)+ **雙軌海巡**,強化流程 B 的痛點發現、關鍵字精準度與**產品-痛點匹配**驗證。
## 北極星
海巡找到的貼文/留言,**你的產品是否真的解得了那個問題**(可置入、可回覆、可追蹤)。
流程 B 主線:
```text
Brave 知識圖譜擴散(周邊延伸)
→ 衍生雙維度搜尋 tag相關詞 + 近期求助詞)
→ 使用者手動勾選
→ 每個 tag 雙軌爬 Threads相關軌 + 近期軌7d 優先 / 30d 補充)
→ productFitScore 篩選
→ 島民協助撰寫獲客留言
```
## 已拍板決策
| 項目 | 決策 |
|------|------|
| 圖譜深度 | **3 層、範圍廣**:核心 → 成因/症狀 → 相鄰情境 |
| 進海巡的 tag | **使用者手動勾選**(圖譜 UI 多選;島民可 toggle不預設全選 |
| 近期窗口 | **7 天內為重點**;不足時補充至 **30 天**;超過 30 天排除 |
| Brave 預算 | 中等,每輪知識擴展 **1015 次查詢**(不足可 supplemental 1 輪) |
| 痛點 tag 候選 | 圖譜衍生 **≥12 候選**,其中痛點/求助類 **≥8** |
| 流程 A | 保留 `style-8d` 捷徑matrix + 留言收集疊加於 Phase 2 |
---
## 根因診斷(舊系統痛點少、關鍵字不準)
對照舊 Next.js[`lib/ai/prompts/research-map-placement.ts`](../../lib/ai/prompts/research-map-placement.ts)、[`lib/services/scan-tasks.ts`](../../lib/services/scan-tasks.ts)、[`lib/ai/analyze-topic.ts`](../../lib/ai/analyze-topic.ts)
| 現象 | 根因 | 新系統對策 |
|------|------|------------|
| 痛點只抓到 12 個 | Placement 壓 `suggestedTags`**24**`PLACEMENT_QUERY_MAX = 8` | 圖譜衍生 ≥8 痛點 tag + supplemental 補充迴圈 |
| 關鍵字不夠精準 | AI 憑種子詞推測,無外部知識 | Brave `knowledge_expand` 建 TKG節點附 `evidence[]` |
| 只有「最相關」 | Recency 只是加分,無獨立近期軌 | **Tag 層**分 `relevance` / `recency`**Crawl 層**雙軌必跑 |
| 沒有周邊延伸 | Brave 只做 `site:threads.net` | `knowledge_expand` 做領域知識(成因、懷孕、換季…)再衍生 tag |
新後端原則見 [`AGENTS.md`](../AGENTS.md)**複製模式,不複製舊業務**——移植 Playwright/過濾規則,用 Mongo + Job 重建。
---
## 架構總覽
```mermaid
flowchart TB
subgraph input [輸入]
Seed["種子詞"]
ProductBrief["product_brief"]
Persona["人設"]
end
subgraph tkg [知識圖譜]
ExpandJob["expand-graph job"]
BraveK["Brave knowledge_expand"]
TKG["topic_knowledge_graphs"]
end
subgraph derive [Tag衍生]
DeriveFn["deriveSearchTagsFromGraph"]
RelQ["relevanceQueries"]
RecQ["recencyQueries"]
end
subgraph select [使用者選擇]
GraphUI["圖譜 UI 勾選節點/tag"]
end
subgraph scan [海巡]
ScanJob["scan job 每tag雙軌"]
Posts["scan_posts"]
end
subgraph outcome [驗收]
Fit["productFitScore"]
Outreach["outreach + 島民留言"]
end
Seed --> ExpandJob
ProductBrief --> ExpandJob
Persona --> ExpandJob
ExpandJob --> BraveK --> TKG
TKG --> DeriveFn
DeriveFn --> RelQ
DeriveFn --> RecQ
RelQ --> GraphUI
RecQ --> GraphUI
GraphUI --> ScanJob --> Posts --> Fit --> Outreach
```
---
## Tag 產生完整流水線
Tag **不是** AI 一次吐 24 個,而是五段流水線產出:
```mermaid
flowchart LR
S["1 種子詞+brief"] --> A["2 AI核心地圖"]
A --> B["3 Brave knowledge_expand"]
B --> G["4 合成TKG三層"]
G --> D["5 deriveSearchTagsFromGraph"]
D --> R["relevanceQueries"]
D --> C["recencyQueries"]
```
| 步驟 | 做什麼 | 產出 |
|------|--------|------|
| 1 | 讀 `seed_query`、`product_brief`、`target_audience` | 輸入包 |
| 2 | AI 產核心 questions/pillars/exclusions | 研究地圖骨架 |
| 3 | Brave 1015 次**一般網搜**(非 threadsOnly | snippets → 候選節點 |
| 4 | AI 合成 TKGL0/L1/L2+ `productFitScore` + `evidence[]` | `topic_knowledge_graphs` |
| 5 | 每節點壓成 28 字真人搜尋詞,分兩套 | `derivedTags` |
### 雙維度 Tag相關 + 近期都要)
每個圖譜節點衍生:
| 維度 | 用途 | 寫法範例 |
|------|------|----------|
| **`relevanceQueries`** | 相關軌:短詞、高命中 | `敏感肌`、`屏障受損` |
| **`recencyQueries`** | 近期軌:求助語境 + 時間窗 | `敏感肌 請問`、`換季泛紅 推薦` |
- `recencyQueries` 在 Brave `threads_discover` 時加 `after:{7天前日期}`(參考舊 [`scan-web-discover.ts`](../../lib/services/scan-web-discover.ts) `buildPlacementKeywordQueries`
- 候選總量:**≥12 tag**(痛點/求助類 **≥8****使用者勾選後才 crawl**
---
## 痛點 Tag 保底機制
解決「只抓到一兩個痛點」:
```text
expand-graph 完成 → deriveSearchTagsFromGraph
IF 痛點/求助類 tag 數 < 8:
→ supplemental_round最多 1 次Brave +5 查詢)
→ 追加查詢例:{seed} 困擾、{seed} 求助、{L2節點} 請問、{seed} 推薦
→ AI 補節點 + 補 derivedTags
IF 仍 < 8:
→ job 標 warningUI + 島民提示「可重跑 expand 或手動加種子詞」
```
| 指標 | 舊系統 | 新系統 |
|------|--------|--------|
| Placement suggestedTags | 24 | 不沿用此上限 |
| 搜尋任務上限 | 8 | 候選 ≥12實 crawl = 勾選數 |
| 痛點類最低 | 無保證 | **≥8**(含 supplemental |
---
## Topic Knowledge GraphTKG
### Mongo collection`topic_knowledge_graphs`
`persona_id` + `seed_query`
```json
{
"seed": "敏感肌",
"nodes": [
{
"id": "n1",
"label": "敏感肌",
"nodeKind": "pain",
"type": "core",
"layer": 0,
"placementValue": "high",
"productFitScore": 95,
"selectedForScan": false,
"evidence": [],
"derivedTags": {
"relevance": ["敏感肌"],
"recency": ["敏感肌 請問", "敏感肌 推薦"]
}
},
{
"id": "n2",
"label": "懷孕嗅覺敏感",
"nodeKind": "cause",
"type": "cause",
"layer": 2,
"relation": "可能成因",
"placementValue": "medium",
"productFitScore": 40,
"selectedForScan": false,
"evidence": [{ "url": "...", "snippet": "..." }],
"derivedTags": {
"relevance": ["懷孕皮膚癢", "嗅覺敏感"],
"recency": ["懷孕 皮膚 癢 請益"]
}
},
{
"id": "n3",
"label": "屏障修復原理",
"nodeKind": "knowledge",
"type": "mechanism",
"layer": 1,
"productFitScore": 70,
"selectedForScan": false,
"derivedTags": {
"relevance": ["屏障受損"],
"recency": ["屏障受損 怎麼辦"]
}
}
],
"edges": [
{ "from": "n1", "to": "n2", "relation": "可能因" },
{ "from": "n1", "to": "n3", "relation": "機制" }
],
"braveSources": [{ "query": "敏感肌 懷孕 原因", "snippet": "...", "url": "..." }],
"painTagCount": 9,
"generatedAt": 0
}
```
### 三層擴散
```text
L0 核心:敏感肌
L1 直接相關:屏障受損、換季泛紅、刺癢
L2 周邊情境:懷孕荷爾蒙、嗅覺敏感、壓力熬夜、換洗臉產品過敏 …
```
### 節點語意
| 欄位 | 說明 |
|------|------|
| `nodeKind` | `pain`(痛點/求助)、`knowledge`(科普延伸)、`cause`、`symptom` |
| `placementValue` | 建議優先級,**不決定是否海巡** |
| `selectedForScan` | 使用者勾選後 `true`,才進 `scan` payload |
| `productFitScore` | 依 `product_brief`:產品解不解得了 |
| `derivedTags` | `relevance` + `recency` 兩套查詢詞 |
| `evidence[]` | L1/L2 必填Brave snippet 可追溯) |
- `knowledge` 節點:延伸話題/科普靈感,**預設不勾選**;若 snippet 含求助語境可升級為 `pain`
- `knowledge` 不強制進 placement crawl除非使用者勾選且 `productFitScore` 達標
---
## Brave 雙模式
| 模式 | `threadsOnly` | 用途 |
|------|---------------|------|
| `knowledge_expand` | `false` | 建 TKG找成因/周邊/知識 |
| `threads_discover` | `true` | 海巡時找 Threads 貼文 |
### L0/L1 查詢模板plan_queries上限 15/輪)
```text
{seed} 常見原因
{seed} 什麼情況會
{seed} 初期 症狀
{seed} 怎麼改善 困擾
{seed} 求助 推薦
```
### L2 周邊擴散查詢池(從 brief/受眾推導)
```text
{seed} 懷孕 相關
{seed} 壓力 熬夜
{seed} 換產品 過敏
{seed} 與 {受眾場景} 的關係
{L1節點} 原因
{L1節點} 困擾
```
Brave 回傳 title/snippet/url → AI 萃取節點與邊 → 寫入 TKG。實作`internal/library/knowledge/` + Brave adapter`BRAVE_SEARCH_API_KEY`;參考舊 [`lib/services/web-search.ts`](../../lib/services/web-search.ts))。
---
## 完整範例:敏感肌 Walkthrough
**輸入**
- 種子詞:`敏感肌`
- product_brief溫和修護、無香料、適合敏感/屏障受損肌
**Brave knowledge_expand節錄**
| 查詢 | snippet 線索 | 圖譜節點 |
|------|--------------|----------|
| `敏感肌 常見原因` | 屏障受損、過度清潔 | L1 symptom `屏障受損` |
| `敏感肌 懷孕` | 荷爾蒙、嗅覺/皮膚變敏感 | L2 cause `懷孕嗅覺敏感` |
| `換季 皮膚 泛紅` | 季節性刺激 | L1 symptom `換季泛紅` |
**衍生 tag候選勾選前不 crawl**
| 節點 | relevanceQuery | recencyQuery | productFit |
|------|----------------|--------------|------------|
| 敏感肌 | `敏感肌` | `敏感肌 請問` | 95 |
| 屏障受損 | `屏障受損` | `屏障受損 推薦` | 90 |
| 換季泛紅 | `換季泛紅` | `換季泛紅 請問` | 88 |
| 懷孕皮膚癢 | `懷孕皮膚癢` | `懷孕 皮膚 癢 請益` | 視產品而定 |
**使用者**:勾選 productFit 高的 4 個節點(可不勾懷孕若產品不適用)
**startScan**:每個勾選節點的 relevance + recency 詞都跑雙軌
| 軌道 | 行為 | 本例預期 |
|------|------|----------|
| 相關軌 | sort=relevance, limit≈12 | 高互動痛點貼文 |
| 近期軌 | 7d 優先,不足補 30d | 一週內求助帖 |
**合併** → gold / recent / relevant → `productFitScore` → 獲客台 → 島民 `generateOutreachReply` + fill
---
## 近期窗口
| 窗口 | 天數 | 行為 |
|------|------|------|
| **重點** | 7 天內 | 優先爬取、優先顯示、排序最高 |
| **補充** | 830 天 | 7 天內不足時才補,排序較低 |
| **排除** | >30 天 | 不進海巡與獲客清單 |
策略:
1. 每個勾選 tag 的**近期軌**先抓滿 7 天名額
2. 全輪痛點貼文不足目標時,自動放寬至 30 天
3. 獲客台預設篩「7 天內」,可切「含 30 天內補充」
---
## 雙軌海巡Tag + Crawl + UI 三層對齊)
**近期軌不是相關軌的副產品**——每個勾選 tag 的 relevance 與 recency 查詢都**必跑**。
| 層級 | 相關 | 近期 |
|------|------|------|
| **Tag** | `derivedTags.relevance` 短詞高命中 | `derivedTags.recency` 求助語境 + after 日期 |
| **Crawl** | 相關軌 sort=relevance, limit≈12 | 近期軌 7d 滿額 → 30d 補 |
| **UI** | 可篩 `priority=relevant` | 預設 7d + `priority=gold` 置頂 |
合併優先級:
1. 兩軌皆有 → `gold`
2. 僅近期軌 → `recent`
3. 僅相關軌 → `relevant`
過濾:移植 `hasPlacementIntent`、`looksLikeCasualChat`(舊 [`lib/topic-anchor.ts`](../../lib/topic-anchor.ts)、[`lib/scan-recency.ts`](../../lib/scan-recency.ts))。
### scan_posts 擴充欄位
- `placement_score`、`priority`gold/recent/relevant
- `product_fit_score`、`solved_by_product`
- `posted_at`、`search_tag`、`query_dimension`relevance/recency
- `graph_node_id`
- `replies[]`(可選,`scrape_replies: true`
---
## 產品匹配驗收
每篇海巡結果:
- **`productFitScore`**:痛點 vs `product_brief`
- **`solvedByProduct`**:獲客留言是否對應產品能力(生成時強制檢查)
獲客台 UI
- 預設排序7 天內 + 產品能解決
- 標示:可置入 / 需人工 / 超出產品範圍
- 獲客留言:**島民 fill 全文,不自動送出**
---
## 島民交接
### job.result.handoff
```json
{
"handoff": {
"flow": "placement",
"persona_id": "...",
"pain_tag_count": 9,
"summary": "12 候選 tag → 勾選 6 節點 → 38 篇;痛點 10核心 6 + 周邊 47 天內 8 篇",
"pain_breakdown": { "core": 6, "peripheral": 4, "recent_7d": 8 },
"top_peripheral_hits": ["懷孕皮膚癢", "換季泛紅"],
"next_route": "/personas/:id/outreach",
"needs_supplemental_expand": false,
"connection_required": false
}
}
```
JobMonitor → `islanderHandoffStore`[`buildIslanderContext`](../web/src/lib/islander/buildIslanderContext.ts) 注入【近期海巡交接】。
### Custom actions
| Action | 用途 |
|--------|------|
| `expandKnowledgeGraph` | 觸發 `expand-graph``supplemental=true` 補充迴圈 |
| `toggleGraphNode` | 勾選/取消節點 |
| `startScan` | `dual_track=true`,只爬 `selectedForScan` 節點 |
| `generateOutreachReply` | 產獲客留言 |
| `applyDraft` | fill 留言欄位 |
### 對話路徑(流程 B
```text
「幫我找敏感肌的痛點」
→ expand-graphBrave knowledge_expand + AI 合成 TKG
→ IF pain_tag_count < 8 島民要再補一輪 Brave supplemental_round
→ 研究頁:圖譜 + 雙維度 tag + productFitScore
→ 使用者手動勾選節點
→ startScan每詞雙軌相關 + 近期7d/30d
→ outreach → highlight gold/recent → generateOutreachReply + fill
```
---
## Job 模板
| Template | Steps | worker |
|----------|-------|--------|
| `expand-graph` | plan_queries → brave_knowledge → ai_synth → derive_tags → [supplemental?] → persist_tkg | go |
| `scan` | session → crawl_dual_track → replies? → store → filter → ai_fit → persist | node + go |
| `style-8d` | (既有) | node + go |
執行順序:**expand-graph → 勾選 tag → scan**。
---
## API 草案
```text
POST /api/v1/personas/:id/knowledge-graph/expand # ?supplemental=true
GET /api/v1/personas/:id/knowledge-graph
PATCH /api/v1/personas/:id/knowledge-graph/nodes # selectedForScan
POST /api/v1/personas/:id/scan-jobs # graph_id, selected_node_ids, dual_track
GET /api/v1/personas/:id/scan-posts # recent_7d, product_fit_min, priority
POST /api/v1/personas/:id/outreach-drafts/generate
```
Internal worker`POST /workers/scan-posts/batch`、Brave/AI 內部端點。
---
## 前端頁面
| 路徑 | 用途 | 島民 label |
|------|------|------------|
| `/personas/:id/research` | 圖譜、雙維度 tag、勾選 | 加入海巡、Brave 再擴展 |
| `/personas/:id/outreach` | 獲客貼文 + 留言 | 獲客留言、標記已處理 |
| `/personas/:id/matrix` | 流程 APhase 2 | 草稿內容 |
研究頁每節點展示:`relevanceQueries`、`recencyQueries`、`productFitScore`、勾選框、上次命中數。
---
## 實作分期
### Phase 0a — 知識圖譜 + Tag 流水線
- [ ] Go Brave adapter`knowledge_expand` / `threads_discover`
- [ ] `expand-graph` jobplan_queries → brave → ai_synth → derive_tags
- [ ] `supplemental_round`(痛點 tag < 8
- [ ] Mongo `topic_knowledge_graphs`(含 `derivedTags`、`painTagCount`
- [ ] `deriveSearchTagsFromGraph`relevance + recency 雙陣列)
- [ ] API expand / get / patch nodes
### Phase 0b — 島民 handoff
- [ ] handoff`pain_tag_count`、`needs_supplemental_expand`
- [ ] JobMonitor bridge
- [ ] custom actions + `ai.islander.system.md` 海巡專章
### Phase 1 — 雙軌 scan + 流程 B
- [ ] Node `crawl_dual_track`(每 tag 相關+近期7d/30d
- [ ] `productFitScore` + outreach UI
- [ ] **驗收**:敏感肌 → L2懷孕等→ 候選痛點 tag ≥8 → 勾選後貼文痛點 ≥8 → 7d 內 ≥5
### Phase 2 — 流程 A
- [ ] matrix + 留言收集 + 島民 fill
### Phase 3 — 自動化
- [ ] job_schedules、Brave 熔斷、Meta API 發留言
---
## 風險
| 議題 | 對策 |
|------|------|
| Brave 幻覺 | 節點必須有 `evidence[]` |
| 圖譜跑題 | exclusions + `productFitScore` |
| 查詢爆炸 | Brave ≤15/輪supplemental ≤5衍生 ≤20只爬勾選 |
| 醫療敏感 | `disclaimer`;留言不自動發 |
| 周邊節點產品不符 | 低 productFit 預設不勾;獲客台標 ✗ |
---
## 參考
- 舊海巡:[`lib/services/scan.ts`](../../lib/services/scan.ts)
- 舊網搜:[`lib/services/scan-web-discover.ts`](../../lib/services/scan-web-discover.ts)
- 舊研究地圖:[`lib/ai/analyze-topic.ts`](../../lib/ai/analyze-topic.ts)
- Job 系統:[`docs/job-system-plan.md`](./job-system-plan.md)
- 島民:[`internal/library/prompt/files/ai.islander.system.md`](../internal/library/prompt/files/ai.islander.system.md)
- 既有 8D[`worker/style-8d-worker.ts`](../worker/style-8d-worker.ts)

View File

@ -0,0 +1,20 @@
Name: haixun-backend
Host: 0.0.0.0
Port: 8890
Timeout: 120000
Mongo:
URI: mongodb://127.0.0.1:27017
Database: haixun_dev
TimeoutSeconds: 10
Redis:
Addr: 127.0.0.1:6379
DB: 0
Auth:
AccessSecret: haixun-dev-access-secret-change-me
RefreshSecret: haixun-dev-refresh-secret-change-me
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: true

View File

@ -0,0 +1,35 @@
Name: haixun-backend
Host: 0.0.0.0
Port: 8890
Timeout: 120000
Mongo:
URI: mongodb://mongo:27017
Database: haixun
TimeoutSeconds: 10
Redis:
Addr: redis:6379
DB: 0
Auth:
AccessSecret: change-me-in-prod
RefreshSecret: change-me-in-prod-too
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: false
InternalWorker:
Secret: change-me-worker-secret
JobWorker:
Enabled: false
WorkerType: go
JobScheduler:
Enabled: true
IntervalSeconds: 60
JobReaper:
Enabled: true
IntervalSeconds: 30

View File

@ -0,0 +1,35 @@
Name: haixun-worker
Host: 0.0.0.0
Port: 8891
Timeout: 120000
Mongo:
URI: mongodb://mongo:27017
Database: haixun
TimeoutSeconds: 10
Redis:
Addr: redis:6379
DB: 0
Auth:
AccessSecret: change-me-in-prod
RefreshSecret: change-me-in-prod-too
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: false
InternalWorker:
Secret: change-me-worker-secret
JobWorker:
Enabled: true
WorkerType: go
JobScheduler:
Enabled: false
IntervalSeconds: 60
JobReaper:
Enabled: false
IntervalSeconds: 30

View File

@ -0,0 +1,32 @@
Name: haixun-backend
Host: 0.0.0.0
Port: 8890
Timeout: 120000
Mongo:
URI: mongodb://127.0.0.1:27017
Database: haixun
TimeoutSeconds: 10
Redis:
Addr: 127.0.0.1:6379
DB: 0
Auth:
AccessSecret: haixun-dev-access-secret-change-me
RefreshSecret: haixun-dev-refresh-secret-change-me
AccessExpireSeconds: 900
RefreshExpireSeconds: 2592000
DevHeaderFallback: true
JobWorker:
Enabled: true
WorkerType: go
JobScheduler:
Enabled: true
IntervalSeconds: 60
JobReaper:
Enabled: true
IntervalSeconds: 30

34
haixun-backend/gateway.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"context"
"flag"
"fmt"
"haixun-backend/internal/config"
"haixun-backend/internal/handler"
"haixun-backend/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
)
var configFile = flag.String("f", "etc/gateway.yaml", "config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
sc := svc.NewServiceContext(c)
defer sc.Close(context.Background())
handler.RegisterHandlers(server, sc)
fmt.Printf("Starting backend backend at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@ -0,0 +1,83 @@
syntax = "v1"
type (
AIMessage {
Role string `json:"role" validate:"required,oneof=system user assistant"` // 訊息角色
Content string `json:"content" validate:"required"` // 訊息內容
}
AIChatReq {
Provider string `json:"provider" validate:"required,oneof=opencode-go xai"` // AI provider
Model string `json:"model" validate:"required"` // 模型 ID
System string `json:"system,optional"` // system prompt
Messages []AIMessage `json:"messages" validate:"required,min=1,dive"` // 對話訊息
Temperature *float64 `json:"temperature,optional"` // 溫度
MaxTokens *int `json:"max_tokens,optional"` // 最大輸出 token
}
AIProviderOption {
ID string `json:"id"`
Label string `json:"label"`
Streams bool `json:"streams"`
}
AIProvidersData {
Providers []AIProviderOption `json:"providers"`
}
AIProviderPath {
Provider string `path:"provider" validate:"required,oneof=opencode-go xai"` // 要查模型的 provider
}
AIProviderModelsData {
ID string `json:"id"`
Label string `json:"label"`
Models []string `json:"models"`
Streams bool `json:"streams"`
Error string `json:"error,optional"` // 拉 models 失敗時的摘要
}
AIChatData {
Text string `json:"text"`
FinishReason string `json:"finish_reason,optional"`
}
IslanderChatReq {
Messages []AIMessage `json:"messages" validate:"required,min=1,dive"` // 對話訊息
Context string `json:"context,optional"` // 目前頁面與站內導覽快照
}
)
@server (
group: ai
prefix: /api/v1/ai
tags: "AI - Provider Streaming"
summary: "Public AI provider catalog"
)
service gateway {
@handler listAiProviders
get /providers returns (AIProvidersData)
}
@server (
group: ai
prefix: /api/v1/ai
middleware: MemberAuth
tags: "AI - Provider Streaming (member)"
summary: "Chat/stream/models; member JWT via X-Member-Authorization; provider API key via Authorization"
)
service gateway {
@handler listAiProviderModels
post /providers/:provider/models (AIProviderPath) returns (AIProviderModelsData)
@handler chat
post /chat (AIChatReq) returns (AIChatData)
@handler chatStream
post /chat/stream (AIChatReq)
}
@server (
group: ai
prefix: /api/v1/ai
middleware: AuthJWT
tags: "AI - Islander Guide"
summary: "Floating islander chat; member JWT via Authorization; AI key from member settings"
)
service gateway {
@handler islanderChatStream
post /islander/chat/stream (IslanderChatReq)
}

View File

@ -0,0 +1,62 @@
syntax = "v1"
type (
AuthRegisterReq {
TenantID string `json:"tenant_id" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=128"`
DisplayName string `json:"display_name,optional"`
Language string `json:"language,optional"`
}
AuthLoginReq {
TenantID string `json:"tenant_id" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=128"`
}
AuthRefreshReq {
RefreshToken string `json:"refresh_token" validate:"required"`
}
AuthTokenData {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
UID string `json:"uid"`
TokenType string `json:"token_type"`
}
LogoutData {
OK bool `json:"ok"`
}
)
@server(
group: auth
prefix: /api/v1/auth
tags: "Auth"
summary: "Native member auth and JWT token endpoints"
)
service gateway {
@handler register
post /register (AuthRegisterReq) returns (AuthTokenData)
@handler login
post /login (AuthLoginReq) returns (AuthTokenData)
@handler refresh
post /refresh (AuthRefreshReq) returns (AuthTokenData)
}
@server(
group: auth
prefix: /api/v1/auth
middleware: AuthJWT
tags: "Auth"
summary: "Logout requires member Bearer JWT"
)
service gateway {
@handler logout
post /logout returns (LogoutData)
}

View File

@ -0,0 +1,445 @@
syntax = "v1"
type (
ResearchItemData {
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
Snippet string `json:"snippet,omitempty"`
Query string `json:"query,omitempty"`
}
ResearchMapData {
AudienceSummary string `json:"audience_summary,omitempty"`
ContentGoal string `json:"content_goal,omitempty"`
Questions []string `json:"questions,omitempty"`
Pillars []string `json:"pillars,omitempty"`
Exclusions []string `json:"exclusions,omitempty"`
ResearchItems []ResearchItemData `json:"research_items,omitempty"`
ExpandStrategy string `json:"expand_strategy,omitempty"`
PatrolKeywords []string `json:"patrol_keywords,omitempty"`
}
KnowledgeGraphEvidenceData {
URL string `json:"url,omitempty"`
Snippet string `json:"snippet,omitempty"`
Query string `json:"query,omitempty"`
}
BraveSourceData {
Query string `json:"query,omitempty"`
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
Snippet string `json:"snippet,omitempty"`
}
BrandProductData {
ID string `json:"id"`
Label string `json:"label"`
ProductContext string `json:"product_context"`
MatchTags []string `json:"match_tags,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
BrandData {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
TopicName string `json:"topic_name,omitempty"`
SeedQuery string `json:"seed_query,omitempty"`
Brief string `json:"brief,omitempty"`
ProductBrief string `json:"product_brief,omitempty"`
ProductContext string `json:"product_context,omitempty"`
ProductID string `json:"product_id,omitempty"`
Products []BrandProductData `json:"products,omitempty"`
TargetAudience string `json:"target_audience,omitempty"`
Goals string `json:"goals,omitempty"`
ResearchMap ResearchMapData `json:"research_map,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ListBrandProductsData {
List []BrandProductData `json:"list"`
}
CreateBrandProductReq {
Label string `json:"label" validate:"required"`
ProductContext string `json:"product_context" validate:"required"`
MatchTags []string `json:"match_tags,optional"`
}
UpdateBrandProductReq {
Label *string `json:"label,optional"`
ProductContext *string `json:"product_context,optional"`
MatchTags []string `json:"match_tags,optional"`
}
BrandProductPath {
ID string `path:"id" validate:"required"`
ProductID string `path:"productId" validate:"required"`
}
CreateBrandProductHandlerReq {
BrandPath
CreateBrandProductReq
}
UpdateBrandProductHandlerReq {
BrandProductPath
UpdateBrandProductReq
}
ListBrandsData {
List []BrandData `json:"list"`
}
CreateBrandReq {
DisplayName string `json:"display_name,optional"`
}
BrandPath {
ID string `path:"id" validate:"required"`
}
UpdateBrandReq {
DisplayName *string `json:"display_name,optional"`
TopicName *string `json:"topic_name,optional"`
SeedQuery *string `json:"seed_query,optional"`
Brief *string `json:"brief,optional"`
ProductBrief *string `json:"product_brief,optional"`
ProductContext *string `json:"product_context,optional"`
ProductID *string `json:"product_id,optional"`
TargetAudience *string `json:"target_audience,optional"`
Goals *string `json:"goals,optional"`
AudienceSummary *string `json:"audience_summary,optional"`
ContentGoal *string `json:"content_goal,optional"`
Questions []string `json:"questions,optional"`
Pillars []string `json:"pillars,optional"`
Exclusions []string `json:"exclusions,optional"`
PatrolKeywords []string `json:"patrol_keywords,optional"`
}
KnowledgeGraphNodeData {
ID string `json:"id"`
Label string `json:"label"`
NodeKind string `json:"node_kind"`
Type string `json:"type"`
Layer int `json:"layer"`
Relation string `json:"relation,omitempty"`
PlacementValue string `json:"placement_value,omitempty"`
ProductFitScore int `json:"product_fit_score"`
SelectedForScan bool `json:"selected_for_scan"`
RelevanceTags []string `json:"relevance_tags"`
RecencyTags []string `json:"recency_tags"`
Evidence []KnowledgeGraphEvidenceData `json:"evidence,omitempty"`
}
KnowledgeGraphEdgeData {
From string `json:"from"`
To string `json:"to"`
Relation string `json:"relation"`
}
KnowledgeGraphData {
ID string `json:"id"`
BrandID string `json:"brand_id"`
Seed string `json:"seed"`
Nodes []KnowledgeGraphNodeData `json:"nodes"`
Edges []KnowledgeGraphEdgeData `json:"edges"`
BraveSources []BraveSourceData `json:"brave_sources,omitempty"`
ExpandStrategy string `json:"expand_strategy,omitempty"`
PainTagCount int `json:"pain_tag_count"`
GeneratedAt int64 `json:"generated_at"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ExpandKnowledgeGraphReq {
SeedQuery string `json:"seed_query" validate:"required"`
Supplemental bool `json:"supplemental,optional"`
RegenerateMap bool `json:"regenerate_map,optional"`
ExpandStrategy string `json:"expand_strategy,optional"` // brave | llm | hybrid
}
ExpandKnowledgeGraphData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
KnowledgeGraphNodeUpdate {
NodeID string `json:"node_id" validate:"required"`
SelectedForScan *bool `json:"selected_for_scan,optional"`
RelevanceTags []string `json:"relevance_tags,optional"`
RecencyTags []string `json:"recency_tags,optional"`
}
PatchKnowledgeGraphNodesReq {
Updates []KnowledgeGraphNodeUpdate `json:"updates" validate:"required"`
}
StartBrandScanJobReq {
GraphID string `json:"graph_id,optional"`
NodeIDs []string `json:"node_ids,optional"`
DualTrack bool `json:"dual_track,optional"`
PatrolMode bool `json:"patrol_mode,optional"`
PatrolKeywords []string `json:"patrol_keywords,optional"`
}
StartBrandScanJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
ListBrandScanPostsReq {
Priority string `form:"priority,optional"`
Recent7d bool `form:"recent_7d,optional"`
ProductFitMin int `form:"product_fit_min,optional"`
Limit int `form:"limit,optional"`
}
ScanPostData {
ID string `json:"id"`
GraphNodeID string `json:"graph_node_id"`
SearchTag string `json:"search_tag"`
QueryDimension string `json:"query_dimension"`
ExternalID string `json:"external_id"`
Permalink string `json:"permalink"`
Author string `json:"author"`
Text string `json:"text"`
Priority string `json:"priority"`
PlacementScore int `json:"placement_score"`
ProductFitScore int `json:"product_fit_score"`
SolvedByProduct bool `json:"solved_by_product"`
Source string `json:"source"`
ScanJobID string `json:"scan_job_id"`
OutreachStatus string `json:"outreach_status,omitempty"`
PublishedReplyID string `json:"published_reply_id,omitempty"`
PublishedPermalink string `json:"published_permalink,omitempty"`
OutreachUpdateAt int64 `json:"outreach_update_at,omitempty"`
PostedAt string `json:"posted_at,omitempty"`
Replies []ScanReplyData `json:"replies,omitempty"`
LatestDraft *GenerateOutreachDraftsData `json:"latest_draft,omitempty"`
CreateAt int64 `json:"create_at"`
}
ListBrandScanPostsData {
List []ScanPostData `json:"list"`
Total int `json:"total"`
}
GenerateOutreachDraftsReq {
ScanPostID string `json:"scan_post_id" validate:"required"`
TopicID string `json:"topic_id,optional"`
Count int `json:"count,optional"`
VoicePersonaID string `json:"voice_persona_id,optional"`
ProductID string `json:"product_id,optional"`
}
OutreachDraftItemData {
Text string `json:"text"`
Angle string `json:"angle"`
Rationale string `json:"rationale"`
}
GenerateOutreachDraftsData {
ID string `json:"id"`
ScanPostID string `json:"scan_post_id"`
Relevance float64 `json:"relevance"`
Reason string `json:"reason"`
Drafts []OutreachDraftItemData `json:"drafts"`
CreateAt int64 `json:"create_at"`
}
PublishOutreachDraftReq {
ScanPostID string `json:"scan_post_id" validate:"required"`
Text string `json:"text" validate:"required"`
Confirm bool `json:"confirm"`
}
PublishOutreachDraftData {
ScanPostID string `json:"scan_post_id"`
ReplyID string `json:"reply_id"`
Permalink string `json:"permalink"`
OutreachStatus string `json:"outreach_status"`
PublishedPermalink string `json:"published_permalink"`
Message string `json:"message"`
}
PatchScanPostOutreachReq {
OutreachStatus *string `json:"outreach_status,optional"`
}
ContentMatrixRowData {
SortOrder int `json:"sort_order"`
SearchTag string `json:"search_tag"`
Angle string `json:"angle"`
Hook string `json:"hook"`
Text string `json:"text"`
ReferenceNotes string `json:"reference_notes"`
SourcePermalinks []string `json:"source_permalinks"`
Rationale string `json:"rationale"`
}
ContentMatrixData {
ID string `json:"id,omitempty"`
BrandID string `json:"brand_id"`
Rows []ContentMatrixRowData `json:"rows"`
GeneratedAt int64 `json:"generated_at"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
}
GenerateContentMatrixReq {
Count int `json:"count,optional"`
}
ScanReplyData {
ExternalID string `json:"external_id,omitempty"`
Author string `json:"author,omitempty"`
Text string `json:"text"`
Permalink string `json:"permalink,omitempty"`
LikeCount int `json:"like_count,omitempty"`
PostedAt string `json:"posted_at,omitempty"`
}
BrandScanScheduleData {
ID string `json:"id,omitempty"`
BrandID string `json:"brand_id"`
Cron string `json:"cron"`
Timezone string `json:"timezone"`
Enabled bool `json:"enabled"`
NextRunAt int64 `json:"next_run_at,omitempty"`
LastRunAt int64 `json:"last_run_at,omitempty"`
}
UpsertBrandScanScheduleReq {
Cron string `json:"cron,optional"`
Timezone string `json:"timezone,optional"`
Enabled bool `json:"enabled"`
}
UpdateBrandHandlerReq {
BrandPath
UpdateBrandReq
}
ExpandKnowledgeGraphHandlerReq {
BrandPath
ExpandKnowledgeGraphReq
}
PatchKnowledgeGraphNodesHandlerReq {
BrandPath
PatchKnowledgeGraphNodesReq
}
StartBrandScanJobHandlerReq {
BrandPath
StartBrandScanJobReq
}
ListBrandScanPostsHandlerReq {
BrandPath
ListBrandScanPostsReq
}
GenerateOutreachDraftsHandlerReq {
BrandPath
GenerateOutreachDraftsReq
}
PublishOutreachDraftHandlerReq {
BrandPath
PublishOutreachDraftReq
}
PatchScanPostOutreachHandlerReq {
BrandPath
PostID string `path:"postId"`
PatchScanPostOutreachReq
}
GenerateContentMatrixHandlerReq {
BrandPath
GenerateContentMatrixReq
}
UpsertBrandScanScheduleHandlerReq {
BrandPath
UpsertBrandScanScheduleReq
}
)
@server(
group: brand
prefix: /api/v1/brands
middleware: AuthJWT
tags: "Brand"
summary: "Brand profiles for placement workflow. Requires Bearer JWT."
)
service gateway {
@handler listBrands
get / returns (ListBrandsData)
@handler createBrand
post / (CreateBrandReq) returns (BrandData)
@handler getBrand
get /:id (BrandPath) returns (BrandData)
@handler updateBrand
patch /:id (UpdateBrandHandlerReq) returns (BrandData)
@handler deleteBrand
delete /:id (BrandPath)
@handler listBrandProducts
get /:id/products (BrandPath) returns (ListBrandProductsData)
@handler createBrandProduct
post /:id/products (CreateBrandProductHandlerReq) returns (BrandProductData)
@handler updateBrandProduct
patch /:id/products/:productId (UpdateBrandProductHandlerReq) returns (BrandProductData)
@handler deleteBrandProduct
delete /:id/products/:productId (BrandProductPath)
@handler expandKnowledgeGraph
post /:id/knowledge-graph/expand (ExpandKnowledgeGraphHandlerReq) returns (ExpandKnowledgeGraphData)
@handler getKnowledgeGraph
get /:id/knowledge-graph (BrandPath) returns (KnowledgeGraphData)
@handler patchKnowledgeGraphNodes
patch /:id/knowledge-graph/nodes (PatchKnowledgeGraphNodesHandlerReq) returns (KnowledgeGraphData)
@handler startBrandScanJob
post /:id/scan-jobs (StartBrandScanJobHandlerReq) returns (StartBrandScanJobData)
@handler listBrandScanPosts
get /:id/scan-posts (ListBrandScanPostsHandlerReq) returns (ListBrandScanPostsData)
@handler generateOutreachDrafts
post /:id/outreach-drafts/generate (GenerateOutreachDraftsHandlerReq) returns (GenerateOutreachDraftsData)
@handler publishOutreachDraft
post /:id/outreach-drafts/publish (PublishOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
@handler patchScanPostOutreach
patch /:id/scan-posts/:postId (PatchScanPostOutreachHandlerReq) returns (ScanPostData)
@handler getBrandContentMatrix
get /:id/content-matrix (BrandPath) returns (ContentMatrixData)
@handler generateBrandContentMatrix
post /:id/content-matrix/generate (GenerateContentMatrixHandlerReq) returns (ContentMatrixData)
@handler getBrandScanSchedule
get /:id/scan-schedule (BrandPath) returns (BrandScanScheduleData)
@handler upsertBrandScanSchedule
put /:id/scan-schedule (UpsertBrandScanScheduleHandlerReq) returns (BrandScanScheduleData)
}

View File

@ -0,0 +1,15 @@
syntax = "v1"
type ErrorDetail {
BizCode string `json:"biz_code,optional"`
Scope int64 `json:"scope,optional"`
Category int64 `json:"category,optional"`
Detail int64 `json:"detail,optional"`
}
type Status {
Code int64 `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,optional"`
Error ErrorDetail `json:"error,optional"`
}

View File

@ -0,0 +1,256 @@
syntax = "v1"
type (
CopySuggestedTagData {
Tag string `json:"tag"`
Reason string `json:"reason,omitempty"`
SearchIntent string `json:"search_intent,omitempty"`
SearchType string `json:"search_type,omitempty"`
}
CopySimilarAccountData {
Username string `json:"username"`
Reason string `json:"reason,omitempty"`
Source string `json:"source,omitempty"`
Confidence string `json:"confidence,omitempty"`
ProfileUrl string `json:"profile_url,omitempty"`
AuthorVerified bool `json:"author_verified,omitempty"`
FollowerCount int `json:"follower_count,omitempty"`
EngagementScore int `json:"engagement_score,omitempty"`
LikeCount int `json:"like_count,omitempty"`
ReplyCount int `json:"reply_count,omitempty"`
PostCount int `json:"post_count,omitempty"`
}
CopyMissionResearchMapData {
AudienceSummary string `json:"audience_summary,omitempty"`
ContentGoal string `json:"content_goal,omitempty"`
Questions []string `json:"questions,omitempty"`
Pillars []string `json:"pillars,omitempty"`
Exclusions []string `json:"exclusions,omitempty"`
SuggestedTags []CopySuggestedTagData `json:"suggested_tags,omitempty"`
SimilarAccounts []CopySimilarAccountData `json:"similar_accounts,omitempty"`
BenchmarkNotes string `json:"benchmark_notes,omitempty"`
}
CopyMissionData {
ID string `json:"id"`
PersonaID string `json:"persona_id"`
Label string `json:"label,omitempty"`
SeedQuery string `json:"seed_query,omitempty"`
Brief string `json:"brief,omitempty"`
ResearchMap CopyMissionResearchMapData `json:"research_map,omitempty"`
SelectedTags []string `json:"selected_tags,omitempty"`
LastScanJobID string `json:"last_scan_job_id,omitempty"`
Status string `json:"status,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ListCopyMissionsData {
List []CopyMissionData `json:"list"`
}
CreateCopyMissionReq {
Label string `json:"label" validate:"required"`
SeedQuery string `json:"seed_query" validate:"required"`
Brief string `json:"brief" validate:"required"`
}
UpdateCopyMissionReq {
Label *string `json:"label,optional"`
SeedQuery *string `json:"seed_query,optional"`
Brief *string `json:"brief,optional"`
AudienceSummary *string `json:"audience_summary,optional"`
ContentGoal *string `json:"content_goal,optional"`
Questions []string `json:"questions,optional"`
Pillars []string `json:"pillars,optional"`
Exclusions []string `json:"exclusions,optional"`
BenchmarkNotes *string `json:"benchmark_notes,optional"`
SelectedTags []string `json:"selected_tags,optional"`
Status *string `json:"status,optional"`
}
CopyMissionScanScheduleData {
ID string `json:"id,omitempty"`
PersonaID string `json:"persona_id"`
MissionID string `json:"mission_id"`
Cron string `json:"cron"`
Timezone string `json:"timezone"`
Enabled bool `json:"enabled"`
NextRunAt int64 `json:"next_run_at,omitempty"`
LastRunAt int64 `json:"last_run_at,omitempty"`
}
UpsertCopyMissionScanScheduleReq {
Cron string `json:"cron,optional"`
Timezone string `json:"timezone,optional"`
Enabled bool `json:"enabled"`
}
UpsertCopyMissionScanScheduleHandlerReq {
CopyMissionPath
UpsertCopyMissionScanScheduleReq
}
CopyMissionPath {
PersonaID string `path:"personaId" validate:"required"`
ID string `path:"id" validate:"required"`
}
PersonaCopyMissionsPath {
PersonaID string `path:"personaId" validate:"required"`
}
CreateCopyMissionHandlerReq {
PersonaCopyMissionsPath
CreateCopyMissionReq
}
UpdateCopyMissionHandlerReq {
CopyMissionPath
UpdateCopyMissionReq
}
StartCopyMissionAnalyzeJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
StartCopyMissionScanJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
StartCopyMissionMatrixJobReq {
Count int `json:"count,optional"`
}
StartCopyMissionMatrixJobHandlerReq {
CopyMissionPath
StartCopyMissionMatrixJobReq
}
StartCopyMissionMatrixJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
StartCopyMissionCopyDraftJobReq {
ScanPostID string `json:"scan_post_id" validate:"required"`
}
StartCopyMissionCopyDraftJobHandlerReq {
CopyMissionPath
StartCopyMissionCopyDraftJobReq
}
StartCopyMissionCopyDraftJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
ListCopyMissionScanPostsReq {
Limit int `form:"limit,optional"`
}
ListCopyMissionScanPostsHandlerReq {
CopyMissionPath
ListCopyMissionScanPostsReq
}
GenerateCopyMissionMatrixReq {
Count int `json:"count,optional"`
}
GenerateCopyMissionMatrixHandlerReq {
CopyMissionPath
GenerateCopyMissionMatrixReq
}
GenerateCopyMissionMatrixData {
Drafts []CopyDraftData `json:"drafts"`
Message string `json:"message"`
}
ListCopyMissionCopyDraftsData {
List []CopyDraftData `json:"list"`
Total int `json:"total"`
}
CopyMissionInspirationSourceData {
Query string `json:"query,omitempty"`
Title string `json:"title,omitempty"`
Snippet string `json:"snippet,omitempty"`
URL string `json:"url,omitempty"`
}
CopyMissionInspirationData {
Label string `json:"label"`
SeedQuery string `json:"seed_query"`
Brief string `json:"brief"`
TrendReason string `json:"trend_reason,omitempty"`
TrendKeywords []string `json:"trend_keywords,omitempty"`
Sources []CopyMissionInspirationSourceData `json:"sources,omitempty"`
WebSearchUsed bool `json:"web_search_used"`
Message string `json:"message"`
}
)
@server(
group: copy_mission
prefix: /api/v1/personas
middleware: AuthJWT
tags: "CopyMission"
summary: "Copy ninja missions (Flow A). Requires Bearer JWT."
)
service gateway {
@handler listCopyMissions
get /:personaId/copy-missions (PersonaCopyMissionsPath) returns (ListCopyMissionsData)
@handler inspireCopyMission
post /:personaId/copy-mission-inspiration (PersonaCopyMissionsPath) returns (CopyMissionInspirationData)
@handler createCopyMission
post /:personaId/copy-missions (CreateCopyMissionHandlerReq) returns (CopyMissionData)
@handler getCopyMission
get /:personaId/copy-missions/:id (CopyMissionPath) returns (CopyMissionData)
@handler updateCopyMission
patch /:personaId/copy-missions/:id (UpdateCopyMissionHandlerReq) returns (CopyMissionData)
@handler deleteCopyMission
delete /:personaId/copy-missions/:id (CopyMissionPath)
@handler startCopyMissionAnalyzeJob
post /:personaId/copy-missions/:id/analyze-jobs (CopyMissionPath) returns (StartCopyMissionAnalyzeJobData)
@handler startCopyMissionScanJob
post /:personaId/copy-missions/:id/scan-jobs (CopyMissionPath) returns (StartCopyMissionScanJobData)
@handler listCopyMissionScanPosts
get /:personaId/copy-missions/:id/scan-posts (ListCopyMissionScanPostsHandlerReq) returns (ListPersonaViralScanPostsData)
@handler generateCopyMissionMatrix
post /:personaId/copy-missions/:id/matrix-drafts (GenerateCopyMissionMatrixHandlerReq) returns (GenerateCopyMissionMatrixData)
@handler startCopyMissionMatrixJob
post /:personaId/copy-missions/:id/matrix-jobs (StartCopyMissionMatrixJobHandlerReq) returns (StartCopyMissionMatrixJobData)
@handler startCopyMissionCopyDraftJob
post /:personaId/copy-missions/:id/copy-draft-jobs (StartCopyMissionCopyDraftJobHandlerReq) returns (StartCopyMissionCopyDraftJobData)
@handler listCopyMissionCopyDrafts
get /:personaId/copy-missions/:id/copy-drafts (CopyMissionPath) returns (ListCopyMissionCopyDraftsData)
@handler getCopyMissionScanSchedule
get /:personaId/copy-missions/:id/scan-schedule (CopyMissionPath) returns (CopyMissionScanScheduleData)
@handler upsertCopyMissionScanSchedule
put /:personaId/copy-missions/:id/scan-schedule (UpsertCopyMissionScanScheduleHandlerReq) returns (CopyMissionScanScheduleData)
}

View File

@ -0,0 +1,30 @@
syntax = "v1"
info (
title: "Haixun Backend"
desc: "Haixun service-oriented backend core"
author: "haixun"
version: "0.1.0"
host: "127.0.0.1:8890"
schemes: "http,https"
consumes: "application/json"
produces: "application/json"
)
import (
"common.api"
"normal.api"
"setting.api"
"ai.api"
"job.api"
"auth.api"
"member.api"
"permission.api"
"threads_account.api"
"persona.api"
"copy_mission.api"
"brand.api"
"placement_topic.api"
"worker_internal.api"
)

View File

@ -0,0 +1,248 @@
syntax = "v1"
type (
JobTemplatePath {
Type string `path:"type" validate:"required"` // template type
}
JobIDPath {
ID string `path:"id" validate:"required"` // job run id
}
JobScheduleIDPath {
ID string `path:"id" validate:"required"` // schedule id
}
ListJobsReq {
Scope string `form:"scope,optional"` // filter by scope
ScopeID string `form:"scope_id,optional"` // filter by scope id
Page int64 `form:"page,optional"` // page number starting at 1
PageSize int64 `form:"pageSize,optional"` // page size
}
ListJobSchedulesReq {
Scope string `form:"scope,optional"` // filter by scope
ScopeID string `form:"scope_id,optional"` // filter by scope id
Page int64 `form:"page,optional"` // page number starting at 1
PageSize int64 `form:"pageSize,optional"` // page size
}
ListJobEventsReq {
ID string `path:"id" validate:"required"` // job run id
Limit int64 `form:"limit,optional"` // max events
}
CancelJobReq {
ID string `path:"id" validate:"required"` // job run id
Reason string `json:"reason,optional"` // cancel reason
}
CreateJobReq {
TemplateType string `json:"template_type" validate:"required"` // job template type
Scope string `json:"scope" validate:"required,oneof=user account system persona brand"` // job scope
ScopeID string `json:"scope_id" validate:"required"` // scope id
Payload map[string]interface{} `json:"payload,optional"` // job payload
}
UpsertJobTemplateReq {
Type string `path:"type" validate:"required"` // template type
Version int `json:"version,optional"` // template version
Name string `json:"name" validate:"required"` // display name
Description string `json:"description,optional"` // description
Enabled bool `json:"enabled"` // enabled flag
Repeatable bool `json:"repeatable"` // repeatable flag
ConcurrencyPolicy string `json:"concurrency_policy,optional"` // concurrency policy
DedupeKeys []string `json:"dedupe_keys,optional"` // dedupe keys
TimeoutSeconds int `json:"timeout_seconds,optional"` // timeout seconds
CancelPolicy JobCancelPolicyData `json:"cancel_policy,optional"` // cancel policy
RetryPolicy JobRetryPolicyData `json:"retry_policy,optional"` // retry policy
Steps []JobTemplateStepData `json:"steps" validate:"required,min=1,dive"` // steps
}
CreateJobScheduleReq {
TemplateType string `json:"template_type" validate:"required"` // template type
Scope string `json:"scope" validate:"required,oneof=user account system persona brand"` // scope
ScopeID string `json:"scope_id" validate:"required"` // scope id
Cron string `json:"cron" validate:"required"` // cron expression
Timezone string `json:"timezone,optional"` // timezone
PayloadTemplate map[string]interface{} `json:"payload_template,optional"` // payload template
Enabled bool `json:"enabled"` // enabled flag
}
UpdateJobScheduleReq {
ID string `path:"id" validate:"required"` // schedule id
Cron string `json:"cron,optional"` // cron expression
Timezone string `json:"timezone,optional"` // timezone
PayloadTemplate map[string]interface{} `json:"payload_template,optional"` // payload template
Enabled *bool `json:"enabled,optional"` // enabled flag
}
JobCancelPolicyData {
Supported bool `json:"supported"`
Mode string `json:"mode"`
GraceSeconds int `json:"grace_seconds"`
}
JobRetryPolicyData {
MaxAttempts int `json:"max_attempts"`
BackoffSeconds []int `json:"backoff_seconds"`
}
JobTemplateStepData {
ID string `json:"id"`
Name string `json:"name"`
WorkerType string `json:"worker_type"`
TimeoutSeconds int `json:"timeout_seconds"`
Cancelable bool `json:"cancelable"`
}
JobTemplateData {
Type string `json:"type"`
Version int `json:"version"`
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Repeatable bool `json:"repeatable"`
ConcurrencyPolicy string `json:"concurrency_policy"`
DedupeKeys []string `json:"dedupe_keys"`
TimeoutSeconds int `json:"timeout_seconds"`
CancelPolicy JobCancelPolicyData `json:"cancel_policy"`
RetryPolicy JobRetryPolicyData `json:"retry_policy"`
Steps []JobTemplateStepData `json:"steps"`
}
JobTemplateListData {
List []JobTemplateData `json:"list"`
}
JobStepProgressData {
ID string `json:"id"`
Status string `json:"status"`
StartedAt *int64 `json:"started_at,optional"`
EndedAt *int64 `json:"ended_at,optional"`
Message string `json:"message,optional"`
}
JobProgressData {
Summary string `json:"summary"`
Percentage int `json:"percentage"`
Steps []JobStepProgressData `json:"steps"`
}
JobData {
ID string `json:"id"`
TemplateType string `json:"template_type"`
TemplateVersion int `json:"template_version"`
Scope string `json:"scope"`
ScopeID string `json:"scope_id"`
Status string `json:"status"`
Phase string `json:"phase"`
WorkerType string `json:"worker_type"`
Payload map[string]interface{} `json:"payload"`
Progress JobProgressData `json:"progress"`
Result map[string]interface{} `json:"result,optional"`
Error string `json:"error,optional"`
Attempt int `json:"attempt"`
MaxAttempts int `json:"max_attempts"`
CancelRequestedAt *int64 `json:"cancel_requested_at,optional"`
CancelReason string `json:"cancel_reason,optional"`
StartedAt *int64 `json:"started_at,optional"`
CompletedAt *int64 `json:"completed_at,optional"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
JobListData {
Pagination PaginationData `json:"pagination"`
List []JobData `json:"list"`
}
JobScheduleData {
ID string `json:"id"`
TemplateType string `json:"template_type"`
Scope string `json:"scope"`
ScopeID string `json:"scope_id"`
Enabled bool `json:"enabled"`
Cron string `json:"cron"`
Timezone string `json:"timezone"`
PayloadTemplate map[string]interface{} `json:"payload_template"`
LastRunAt *int64 `json:"last_run_at,optional"`
NextRunAt int64 `json:"next_run_at"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
JobScheduleListData {
Pagination PaginationData `json:"pagination"`
List []JobScheduleData `json:"list"`
}
JobEventData {
ID string `json:"id"`
JobID string `json:"job_id"`
Type string `json:"type"`
From string `json:"from,optional"`
To string `json:"to,optional"`
Message string `json:"message"`
Metadata map[string]interface{} `json:"metadata,optional"`
CreateAt int64 `json:"create_at"`
}
JobEventListData {
List []JobEventData `json:"list"`
}
)
@server(
group: job
prefix: /api/v1
middleware: AuthJWT
tags: "Job - Core"
summary: "Generic job templates, runs, schedules, cancel, and retry. Requires Bearer JWT."
)
service gateway {
@handler listJobTemplates
get /job/templates returns (JobTemplateListData)
@handler getJobTemplate
get /job/templates/:type (JobTemplatePath) returns (JobTemplateData)
@handler upsertJobTemplate
put /job/templates/:type (UpsertJobTemplateReq) returns (JobTemplateData)
@handler createJob
post /jobs (CreateJobReq) returns (JobData)
@handler getJob
get /jobs/:id (JobIDPath) returns (JobData)
@handler listJobs
get /jobs (ListJobsReq) returns (JobListData)
@handler listJobEvents
get /jobs/:id/events (ListJobEventsReq) returns (JobEventListData)
@handler cancelJob
post /jobs/:id/cancel (CancelJobReq) returns (JobData)
@handler retryJob
post /jobs/:id/retry (JobIDPath) returns (JobData)
@handler listJobSchedules
get /job/schedules (ListJobSchedulesReq) returns (JobScheduleListData)
@handler createJobSchedule
post /job/schedules (CreateJobScheduleReq) returns (JobScheduleData)
@handler updateJobSchedule
put /job/schedules/:id (UpdateJobScheduleReq) returns (JobScheduleData)
@handler enableJobSchedule
post /job/schedules/:id/enable (JobScheduleIDPath) returns (JobScheduleData)
@handler disableJobSchedule
post /job/schedules/:id/disable (JobScheduleIDPath) returns (JobScheduleData)
@handler deleteJobSchedule
delete /job/schedules/:id (JobScheduleIDPath)
}

View File

@ -0,0 +1,74 @@
syntax = "v1"
type (
MemberMeData {
TenantID string `json:"tenant_id"`
UID string `json:"uid"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"`
Avatar string `json:"avatar,omitempty"`
Phone string `json:"phone,omitempty"`
Language string `json:"language,omitempty"`
Currency string `json:"currency,omitempty"`
Status string `json:"status"`
Origin string `json:"origin"`
Roles []string `json:"roles,omitempty"`
BusinessEmail string `json:"business_email,omitempty"`
BusinessEmailVerified bool `json:"business_email_verified"`
BusinessPhone string `json:"business_phone,omitempty"`
BusinessPhoneVerified bool `json:"business_phone_verified"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
UpdateMemberMeReq {
DisplayName string `json:"display_name,optional"`
Avatar string `json:"avatar,optional"`
Language string `json:"language,optional"`
Currency string `json:"currency,optional"`
Phone string `json:"phone,optional"`
}
MemberPlacementSettingsData {
WebSearchProvider string `json:"web_search_provider"` // brave | exa
BraveAPIKey string `json:"brave_api_key,omitempty"`
BraveAPIKeyConfigured bool `json:"brave_api_key_configured"`
ExaAPIKey string `json:"exa_api_key,omitempty"`
ExaAPIKeyConfigured bool `json:"exa_api_key_configured"`
BraveCountry string `json:"brave_country"`
BraveSearchLang string `json:"brave_search_lang"`
ExaUserLocation string `json:"exa_user_location"`
ExpandStrategy string `json:"expand_strategy"` // brave | llm | hybrid
}
UpdateMemberPlacementSettingsReq {
WebSearchProvider *string `json:"web_search_provider,optional"`
BraveAPIKey *string `json:"brave_api_key,optional"`
ExaAPIKey *string `json:"exa_api_key,optional"`
BraveCountry *string `json:"brave_country,optional"`
BraveSearchLang *string `json:"brave_search_lang,optional"`
ExaUserLocation *string `json:"exa_user_location,optional"`
ExpandStrategy *string `json:"expand_strategy,optional"`
}
)
@server(
group: member
prefix: /api/v1/members
middleware: AuthJWT
tags: "Member"
summary: "Current member profile endpoints. Requires Bearer JWT or dev headers."
)
service gateway {
@handler getMemberMe
get /me returns (MemberMeData)
@handler updateMemberMe
patch /me (UpdateMemberMeReq) returns (MemberMeData)
@handler getMemberPlacementSettings
get /me/placement-settings returns (MemberPlacementSettingsData)
@handler updateMemberPlacementSettings
patch /me/placement-settings (UpdateMemberPlacementSettingsReq) returns (MemberPlacementSettingsData)
}

View File

@ -0,0 +1,16 @@
syntax = "v1"
type HealthData {
Pong string `json:"pong"`
}
@server(
group: normal
prefix: /api/v1
tags: "Normal - Public"
summary: "Health check"
)
service gateway {
@handler health
get /health () returns (HealthData)
}

View File

@ -0,0 +1,52 @@
syntax = "v1"
type (
PermissionCatalogQuery {
Status string `form:"status,optional" validate:"omitempty,oneof=open close"`
Type string `form:"type,optional" validate:"omitempty,oneof=backend_user frontend_user"`
Tree bool `form:"tree,optional"`
}
PermissionNode {
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
Name string `json:"name"`
HTTPMethods string `json:"http_methods,omitempty"`
HTTPPath string `json:"http_path,omitempty"`
Status string `json:"status"`
Type string `json:"type"`
Children []PermissionNode `json:"children,omitempty"`
}
PermissionCatalogData {
Tree []PermissionNode `json:"tree,omitempty"`
List []PermissionNode `json:"list,omitempty"`
}
MePermissionsQuery {
IncludeTree bool `form:"include_tree,optional"`
}
MePermissionsData {
UID string `json:"uid"`
TenantID string `json:"tenant_id"`
Roles []string `json:"roles"`
Permissions map[string]string `json:"permissions"`
Tree []PermissionNode `json:"tree,omitempty"`
}
)
@server(
group: permission
prefix: /api/v1/permissions
middleware: AuthJWT
tags: "Permission"
summary: "Permission catalog and current member permissions. Requires Bearer JWT."
)
service gateway {
@handler getPermissionCatalog
get /catalog (PermissionCatalogQuery) returns (PermissionCatalogData)
@handler getMePermissions
get /me (MePermissionsQuery) returns (MePermissionsData)
}

View File

@ -0,0 +1,231 @@
syntax = "v1"
type (
CopyResearchMapData {
AudienceSummary string `json:"audience_summary,omitempty"`
ContentGoal string `json:"content_goal,omitempty"`
Questions []string `json:"questions,omitempty"`
Pillars []string `json:"pillars,omitempty"`
Exclusions []string `json:"exclusions,omitempty"`
SuggestedTags []string `json:"suggested_tags,omitempty"`
BenchmarkNotes string `json:"benchmark_notes,omitempty"`
}
PersonaData {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
Persona string `json:"persona,omitempty"`
Brief string `json:"brief,omitempty"`
StyleProfile string `json:"style_profile,omitempty"`
StyleBenchmark string `json:"style_benchmark,omitempty"`
SeedQuery string `json:"seed_query,omitempty"`
CopyResearchMap CopyResearchMapData `json:"copy_research_map,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ListPersonasData {
List []PersonaData `json:"list"`
}
CreatePersonaReq {
DisplayName string `json:"display_name,optional"`
}
PersonaPath {
ID string `path:"id" validate:"required"`
}
UpdatePersonaReq {
DisplayName *string `json:"display_name,optional"`
Persona *string `json:"persona,optional"`
Brief *string `json:"brief,optional"`
StyleProfile *string `json:"style_profile,optional"`
StyleBenchmark *string `json:"style_benchmark,optional"`
}
StartPersonaStyleAnalysisReq {
BenchmarkUsername string `json:"benchmark_username" validate:"required"`
}
StartPersonaStyleAnalysisData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
UpdatePersonaHandlerReq {
PersonaPath
UpdatePersonaReq
}
StartPersonaStyleAnalysisHandlerReq {
PersonaPath
StartPersonaStyleAnalysisReq
}
StartPersonaViralScanJobReq {
Keywords []string `json:"keywords,optional"`
}
StartPersonaViralScanJobData {
JobID string `json:"job_id"`
Status string `json:"status"`
Message string `json:"message"`
}
StartPersonaViralScanJobHandlerReq {
PersonaPath
StartPersonaViralScanJobReq
}
ListPersonaViralScanPostsReq {
Limit int `form:"limit,optional"`
}
ViralScanPostData {
ID string `json:"id"`
SearchTag string `json:"search_tag"`
Permalink string `json:"permalink"`
Author string `json:"author"`
AuthorVerified bool `json:"author_verified,omitempty"`
FollowerCount int `json:"follower_count,omitempty"`
Text string `json:"text"`
LikeCount int `json:"like_count"`
ReplyCount int `json:"reply_count"`
EngagementScore int `json:"engagement_score"`
Source string `json:"source"`
ScanJobID string `json:"scan_job_id"`
Replies []ScanReplyData `json:"replies,omitempty"`
CreateAt int64 `json:"create_at"`
}
ListPersonaViralScanPostsData {
List []ViralScanPostData `json:"list"`
Total int `json:"total"`
}
ListPersonaViralScanPostsHandlerReq {
PersonaPath
ListPersonaViralScanPostsReq
}
CopyDraftData {
ID string `json:"id"`
PersonaID string `json:"persona_id"`
CopyMissionID string `json:"copy_mission_id,omitempty"`
ScanPostID string `json:"scan_post_id,omitempty"`
DraftType string `json:"draft_type"`
SortOrder int `json:"sort_order,omitempty"`
Text string `json:"text"`
Angle string `json:"angle,omitempty"`
Hook string `json:"hook,omitempty"`
Rationale string `json:"rationale,omitempty"`
ReferenceNotes string `json:"reference_notes,omitempty"`
Sources []string `json:"sources,omitempty"`
Status string `json:"status,omitempty"`
PublishedMediaID string `json:"published_media_id,omitempty"`
PublishedPermalink string `json:"published_permalink,omitempty"`
PublishedAt int64 `json:"published_at,omitempty"`
CreateAt int64 `json:"create_at"`
}
ListPersonaCopyDraftsData {
List []CopyDraftData `json:"list"`
Total int `json:"total"`
}
GeneratePersonaCopyDraftReq {
ScanPostID string `json:"scan_post_id" validate:"required"`
}
GeneratePersonaCopyDraftHandlerReq {
PersonaPath
GeneratePersonaCopyDraftReq
}
GeneratePersonaCopyDraftData {
Draft CopyDraftData `json:"draft"`
Message string `json:"message"`
}
CopyDraftPath {
ID string `path:"id" validate:"required"`
DraftID string `path:"draftId" validate:"required"`
}
UpdateCopyDraftReq {
Text *string `json:"text,optional"`
Hook *string `json:"hook,optional"`
Angle *string `json:"angle,optional"`
Status *string `json:"status,optional"`
}
UpdateCopyDraftHandlerReq {
CopyDraftPath
UpdateCopyDraftReq
}
PublishCopyDraftReq {
Text string `json:"text,optional"`
Confirm bool `json:"confirm"`
}
PublishCopyDraftHandlerReq {
CopyDraftPath
PublishCopyDraftReq
}
PublishCopyDraftData {
DraftID string `json:"draft_id"`
MediaID string `json:"media_id"`
Permalink string `json:"permalink,omitempty"`
Status string `json:"status"`
Message string `json:"message"`
}
)
@server(
group: persona
prefix: /api/v1/personas
middleware: AuthJWT
tags: "Persona"
summary: "Reusable persona profiles with 8D style strategy. Requires Bearer JWT."
)
service gateway {
@handler listPersonas
get / returns (ListPersonasData)
@handler createPersona
post / (CreatePersonaReq) returns (PersonaData)
@handler getPersona
get /:id (PersonaPath) returns (PersonaData)
@handler updatePersona
patch /:id (UpdatePersonaHandlerReq) returns (PersonaData)
@handler deletePersona
delete /:id (PersonaPath)
@handler startPersonaStyleAnalysis
post /:id/style-analysis (StartPersonaStyleAnalysisHandlerReq) returns (StartPersonaStyleAnalysisData)
@handler startPersonaViralScanJob
post /:id/viral-scan-jobs (StartPersonaViralScanJobHandlerReq) returns (StartPersonaViralScanJobData)
@handler listPersonaViralScanPosts
get /:id/viral-scan-posts (ListPersonaViralScanPostsHandlerReq) returns (ListPersonaViralScanPostsData)
@handler listPersonaCopyDrafts
get /:id/copy-drafts (PersonaPath) returns (ListPersonaCopyDraftsData)
@handler generatePersonaCopyDraft
post /:id/copy-drafts/generate (GeneratePersonaCopyDraftHandlerReq) returns (GeneratePersonaCopyDraftData)
@handler updatePersonaCopyDraft
patch /:id/copy-drafts/:draftId (UpdateCopyDraftHandlerReq) returns (CopyDraftData)
@handler publishPersonaCopyDraft
post /:id/copy-drafts/:draftId/publish (PublishCopyDraftHandlerReq) returns (PublishCopyDraftData)
}

View File

@ -0,0 +1,185 @@
syntax = "v1"
type (
PlacementTopicData {
ID string `json:"id"`
BrandID string `json:"brand_id"`
BrandDisplayName string `json:"brand_display_name,omitempty"`
TopicName string `json:"topic_name,omitempty"`
SeedQuery string `json:"seed_query,omitempty"`
Brief string `json:"brief,omitempty"`
ProductID string `json:"product_id,omitempty"`
ResearchMap ResearchMapData `json:"research_map,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ListPlacementTopicsData {
List []PlacementTopicData `json:"list"`
}
CreatePlacementTopicReq {
BrandID string `json:"brand_id" validate:"required"`
TopicName string `json:"topic_name" validate:"required"`
SeedQuery string `json:"seed_query" validate:"required"`
Brief string `json:"brief" validate:"required"`
ProductID string `json:"product_id,optional"`
}
UpdatePlacementTopicReq {
BrandID *string `json:"brand_id,optional"`
TopicName *string `json:"topic_name,optional"`
SeedQuery *string `json:"seed_query,optional"`
Brief *string `json:"brief,optional"`
ProductID *string `json:"product_id,optional"`
AudienceSummary *string `json:"audience_summary,optional"`
ContentGoal *string `json:"content_goal,optional"`
Questions []string `json:"questions,optional"`
Pillars []string `json:"pillars,optional"`
Exclusions []string `json:"exclusions,optional"`
PatrolKeywords []string `json:"patrol_keywords,optional"`
}
PlacementTopicPath {
ID string `path:"id" validate:"required"`
}
UpdatePlacementTopicHandlerReq {
PlacementTopicPath
UpdatePlacementTopicReq
}
CreatePlacementTopicHandlerReq {
CreatePlacementTopicReq
}
ExpandPlacementTopicGraphHandlerReq {
PlacementTopicPath
ExpandKnowledgeGraphReq
}
PatchPlacementTopicGraphNodesHandlerReq {
PlacementTopicPath
PatchKnowledgeGraphNodesReq
}
StartPlacementTopicScanJobHandlerReq {
PlacementTopicPath
StartBrandScanJobReq
}
ListPlacementTopicScanPostsHandlerReq {
PlacementTopicPath
ListBrandScanPostsReq
}
GeneratePlacementTopicOutreachDraftsHandlerReq {
PlacementTopicPath
GenerateOutreachDraftsReq
}
PublishPlacementTopicOutreachDraftHandlerReq {
PlacementTopicPath
PublishOutreachDraftReq
}
PatchPlacementTopicScanPostOutreachHandlerReq {
PlacementTopicPath
PostID string `path:"postId"`
PatchScanPostOutreachReq
}
DeletePlacementTopicScanPostHandlerReq {
PlacementTopicPath
PostID string `path:"postId" validate:"required"`
}
BatchDeletePlacementTopicScanPostsReq {
PostIDs []string `json:"post_ids" validate:"required"`
}
BatchDeletePlacementTopicScanPostsData {
DeletedCount int `json:"deleted_count"`
}
BatchDeletePlacementTopicScanPostsHandlerReq {
PlacementTopicPath
BatchDeletePlacementTopicScanPostsReq
}
GeneratePlacementTopicContentMatrixHandlerReq {
PlacementTopicPath
GenerateContentMatrixReq
}
UpsertPlacementTopicScanScheduleHandlerReq {
PlacementTopicPath
UpsertBrandScanScheduleReq
}
)
@server(
group: placement_topic
prefix: /api/v1/placement/topics
middleware: AuthJWT
tags: "Placement Topic"
summary: "找 TA 主題每個主題關聯一個品牌一個品牌可有多個主題。Requires Bearer JWT."
)
service gateway {
@handler listPlacementTopics
get / returns (ListPlacementTopicsData)
@handler createPlacementTopic
post / (CreatePlacementTopicHandlerReq) returns (PlacementTopicData)
@handler getPlacementTopic
get /:id (PlacementTopicPath) returns (PlacementTopicData)
@handler updatePlacementTopic
patch /:id (UpdatePlacementTopicHandlerReq) returns (PlacementTopicData)
@handler deletePlacementTopic
delete /:id (PlacementTopicPath)
@handler expandPlacementTopicGraph
post /:id/knowledge-graph/expand (ExpandPlacementTopicGraphHandlerReq) returns (ExpandKnowledgeGraphData)
@handler getPlacementTopicGraph
get /:id/knowledge-graph (PlacementTopicPath) returns (KnowledgeGraphData)
@handler patchPlacementTopicGraphNodes
patch /:id/knowledge-graph/nodes (PatchPlacementTopicGraphNodesHandlerReq) returns (KnowledgeGraphData)
@handler startPlacementTopicScanJob
post /:id/scan-jobs (StartPlacementTopicScanJobHandlerReq) returns (StartBrandScanJobData)
@handler listPlacementTopicScanPosts
get /:id/scan-posts (ListPlacementTopicScanPostsHandlerReq) returns (ListBrandScanPostsData)
@handler generatePlacementTopicOutreachDrafts
post /:id/outreach-drafts/generate (GeneratePlacementTopicOutreachDraftsHandlerReq) returns (GenerateOutreachDraftsData)
@handler publishPlacementTopicOutreachDraft
post /:id/outreach-drafts/publish (PublishPlacementTopicOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
@handler patchPlacementTopicScanPostOutreach
patch /:id/scan-posts/:postId (PatchPlacementTopicScanPostOutreachHandlerReq) returns (ScanPostData)
@handler deletePlacementTopicScanPost
delete /:id/scan-posts/:postId (DeletePlacementTopicScanPostHandlerReq)
@handler batchDeletePlacementTopicScanPosts
post /:id/scan-posts/batch-delete (BatchDeletePlacementTopicScanPostsHandlerReq) returns (BatchDeletePlacementTopicScanPostsData)
@handler getPlacementTopicContentMatrix
get /:id/content-matrix (PlacementTopicPath) returns (ContentMatrixData)
@handler generatePlacementTopicContentMatrix
post /:id/content-matrix/generate (GeneratePlacementTopicContentMatrixHandlerReq) returns (ContentMatrixData)
@handler getPlacementTopicScanSchedule
get /:id/scan-schedule (PlacementTopicPath) returns (BrandScanScheduleData)
@handler upsertPlacementTopicScanSchedule
put /:id/scan-schedule (UpsertPlacementTopicScanScheduleHandlerReq) returns (BrandScanScheduleData)
}

View File

@ -0,0 +1,68 @@
syntax = "v1"
type (
SettingPath {
Scope string `path:"scope" validate:"required,oneof=user account system"` // 設定範圍,可選 user / account / system
ScopeID string `path:"scope_id" validate:"required"` // 範圍 ID例如 user_id、account_id 或 global
Page int64 `form:"page,optional"` // 頁碼,從 1 開始
PageSize int64 `form:"pageSize,optional"` // 每頁筆數server 會限制最大值
}
SettingKeyPath {
Scope string `path:"scope" validate:"required,oneof=user account system"` // 設定範圍,可選 user / account / system
ScopeID string `path:"scope_id" validate:"required"` // 範圍 ID例如 user_id、account_id 或 global
Key string `path:"key" validate:"required"` // 設定 key例如 ai.default
}
SettingUpsertReq {
Scope string `path:"scope" validate:"required,oneof=user account system"` // 設定範圍,可選 user / account / system
ScopeID string `path:"scope_id" validate:"required"` // 範圍 ID例如 user_id、account_id 或 global
Key string `path:"key" validate:"required"` // 設定 key例如 ai.default
Value map[string]interface{} `json:"value" validate:"required"` // 設定內容 JSON object
Version int `json:"version,optional"` // schema version未帶入時預設 1
}
SettingData {
ID string `json:"id"`
Scope string `json:"scope"`
ScopeID string `json:"scope_id"`
Key string `json:"key"`
Value map[string]interface{} `json:"value"`
Version int `json:"version"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
SettingListData {
Pagination PaginationData `json:"pagination"`
List []SettingData `json:"list"`
}
PaginationData {
Total int64 `json:"total"`
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
TotalPages int64 `json:"totalPages"`
}
)
@server(
group: setting
prefix: /api/v1/settings
middleware: AuthJWT
tags: "Setting - General"
summary: "Manage settings by scope, scope_id, and key. Requires Bearer JWT."
)
service gateway {
@handler listSettings
get /:scope/:scope_id (SettingPath) returns (SettingListData)
@handler getSetting
get /:scope/:scope_id/:key (SettingKeyPath) returns (SettingData)
@handler upsertSetting
put /:scope/:scope_id/:key (SettingUpsertReq) returns (SettingData)
@handler deleteSetting
delete /:scope/:scope_id/:key (SettingKeyPath)
}

View File

@ -0,0 +1,157 @@
syntax = "v1"
type (
ThreadsAccountData {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
Username string `json:"username,omitempty"`
ThreadsUserID string `json:"threads_user_id,omitempty"`
PersonaID string `json:"persona_id,omitempty"` // deprecated: persona is chosen per publish, not bound to account
BrowserConnected bool `json:"browser_connected"`
ApiConnected bool `json:"api_connected"`
Status string `json:"status"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
}
ListThreadsAccountsData {
List []ThreadsAccountData `json:"list"`
ActiveAccountID string `json:"active_account_id"`
}
CreateThreadsAccountReq {
DisplayName string `json:"display_name,optional"`
Activate *bool `json:"activate,optional"`
}
UpdateThreadsAccountReq {
DisplayName *string `json:"display_name,optional"`
PersonaID *string `json:"persona_id,optional"` // deprecated: use persona_id in publish payload instead
}
ThreadsAccountPath {
ID string `path:"id" validate:"required"`
}
ThreadsAccountConnectionPrefs {
SearchViaApi bool `json:"search_via_api"`
SearchSourceMode string `json:"search_source_mode"`
PublishViaApi bool `json:"publish_via_api"`
DevMode bool `json:"dev_mode"`
ScrapeReplies bool `json:"scrape_replies"`
RepliesPerPost int `json:"replies_per_post"`
PublishHeaded bool `json:"publish_headed"`
PlaywrightDebug bool `json:"playwright_debug"`
}
ThreadsAccountConnectionData {
AccountID string `json:"account_id"`
AccountName string `json:"account_name"`
Username string `json:"username,omitempty"`
BrowserConnected bool `json:"browser_connected"`
ApiConnected bool `json:"api_connected"`
Prefs ThreadsAccountConnectionPrefs `json:"prefs"`
}
UpdateThreadsAccountConnectionReq {
SearchViaApi *bool `json:"search_via_api,optional"`
SearchSourceMode *string `json:"search_source_mode,optional"`
PublishViaApi *bool `json:"publish_via_api,optional"`
DevMode *bool `json:"dev_mode,optional"`
ScrapeReplies *bool `json:"scrape_replies,optional"`
RepliesPerPost *int `json:"replies_per_post,optional"`
PublishHeaded *bool `json:"publish_headed,optional"`
PlaywrightDebug *bool `json:"playwright_debug,optional"`
}
ImportThreadsAccountSessionReq {
StorageState string `json:"storageState" validate:"required"`
}
ImportThreadsAccountSessionData {
Success bool `json:"success"`
Valid bool `json:"valid"`
Synced bool `json:"synced"`
AccountID string `json:"account_id"`
Username string `json:"username,omitempty"`
Message string `json:"message"`
UpdateAt int64 `json:"update_at"`
}
ThreadsAccountAiSettingsData {
AccountID string `json:"account_id"`
Provider string `json:"provider"`
Model string `json:"model"`
ResearchProvider string `json:"research_provider,omitempty"`
ResearchModel string `json:"research_model,omitempty"`
ApiKeys map[string]string `json:"api_keys"`
ApiKeysConfigured map[string]interface{} `json:"api_keys_configured"`
}
UpdateThreadsAccountAiSettingsReq {
Provider *string `json:"provider,optional"`
Model *string `json:"model,optional"`
ResearchProvider *string `json:"research_provider,optional"`
ResearchModel *string `json:"research_model,optional"`
ApiKeys map[string]string `json:"api_keys,optional"`
}
UpdateThreadsAccountHandlerReq {
ThreadsAccountPath
UpdateThreadsAccountReq
}
UpdateThreadsAccountConnectionHandlerReq {
ThreadsAccountPath
UpdateThreadsAccountConnectionReq
}
ImportThreadsAccountSessionHandlerReq {
ThreadsAccountPath
ImportThreadsAccountSessionReq
}
UpdateThreadsAccountAiSettingsHandlerReq {
ThreadsAccountPath
UpdateThreadsAccountAiSettingsReq
}
)
@server(
group: threads_account
prefix: /api/v1/threads-accounts
middleware: AuthJWT
tags: "ThreadsAccount"
summary: "Threads operating account endpoints. Requires Bearer JWT."
)
service gateway {
@handler listThreadsAccounts
get / returns (ListThreadsAccountsData)
@handler createThreadsAccount
post / (CreateThreadsAccountReq) returns (ThreadsAccountData)
@handler getThreadsAccount
get /:id (ThreadsAccountPath) returns (ThreadsAccountData)
@handler updateThreadsAccount
patch /:id (UpdateThreadsAccountHandlerReq) returns (ThreadsAccountData)
@handler activateThreadsAccount
post /:id/activate (ThreadsAccountPath)
@handler getThreadsAccountConnection
get /:id/connection (ThreadsAccountPath) returns (ThreadsAccountConnectionData)
@handler updateThreadsAccountConnection
patch /:id/connection (UpdateThreadsAccountConnectionHandlerReq) returns (ThreadsAccountConnectionData)
@handler importThreadsAccountSession
post /:id/session/import (ImportThreadsAccountSessionHandlerReq) returns (ImportThreadsAccountSessionData)
@handler getThreadsAccountAiSettings
get /:id/ai-settings (ThreadsAccountPath) returns (ThreadsAccountAiSettingsData)
@handler updateThreadsAccountAiSettings
put /:id/ai-settings (UpdateThreadsAccountAiSettingsHandlerReq) returns (ThreadsAccountAiSettingsData)
}

View File

@ -0,0 +1,143 @@
syntax = "v1"
type (
ClaimWorkerJobReq {
WorkerType string `json:"worker_type" validate:"required"`
WorkerID string `json:"worker_id" validate:"required"`
}
WorkerJobPath {
ID string `path:"id" validate:"required"`
}
WorkerJobReq {
WorkerJobPath
WorkerID string `json:"worker_id" validate:"required"`
}
WorkerHeartbeatReq {
WorkerJobPath
WorkerID string `json:"worker_id" validate:"required"`
TTLSeconds int `json:"ttl_seconds,optional"`
}
WorkerProgressReq {
WorkerJobPath
WorkerID string `json:"worker_id" validate:"required"`
Phase string `json:"phase,optional"`
Summary string `json:"summary,optional"`
Percentage int `json:"percentage,optional"`
Steps []JobStepProgressData `json:"steps,optional"`
}
WorkerCompleteReq {
WorkerJobPath
WorkerID string `json:"worker_id" validate:"required"`
Result map[string]interface{} `json:"result,optional"`
}
WorkerFailReq {
WorkerJobPath
WorkerID string `json:"worker_id" validate:"required"`
Error string `json:"error" validate:"required"`
Phase string `json:"phase,optional"`
}
WorkerCancelCheckData {
Cancelled bool `json:"cancelled"`
}
WorkerOKData {
OK bool `json:"ok"`
}
StorePersonaStyleProfileReq {
ID string `path:"id" validate:"required"`
TenantID string `json:"tenant_id" validate:"required"`
OwnerUID string `json:"owner_uid" validate:"required"`
StyleProfile string `json:"style_profile" validate:"required"`
StyleBenchmark string `json:"style_benchmark,optional"`
}
StorePersonaStyleProfileData {
ID string `json:"id"`
UpdateAt int64 `json:"update_at"`
}
WorkerThreadsAccountSessionReq {
ID string `path:"id" validate:"required"`
TenantID string `json:"tenant_id" validate:"required"`
OwnerUID string `json:"owner_uid" validate:"required"`
}
WorkerThreadsAccountSessionData {
AccountID string `json:"account_id"`
StorageState string `json:"storage_state"`
UpdateAt int64 `json:"update_at"`
}
AnalyzeStyle8DPostReq {
Text string `json:"text"`
Permalink string `json:"permalink,optional"`
LikeCount int `json:"like_count,optional"`
ReplyCount int `json:"reply_count,optional"`
}
AnalyzeStyle8DReq {
ID string `path:"id" validate:"required"`
WorkerID string `json:"worker_id" validate:"required"`
TenantID string `json:"tenant_id" validate:"required"`
OwnerUID string `json:"owner_uid" validate:"required"`
PersonaID string `json:"persona_id" validate:"required"`
ThreadsAccountID string `json:"threads_account_id" validate:"required"`
Username string `json:"username" validate:"required"`
Posts []AnalyzeStyle8DPostReq `json:"posts" validate:"required"`
Steps []JobStepProgressData `json:"steps,optional"`
}
AnalyzeStyle8DData {
PersonaID string `json:"persona_id"`
PostCount int `json:"post_count"`
StyleProfile string `json:"style_profile"`
StyleBenchmark string `json:"style_benchmark"`
}
)
@server(
group: job
prefix: /api/v1/internal
middleware: WorkerSecret
tags: "Internal Worker"
summary: "Internal worker endpoints protected by X-Worker-Secret when InternalWorker.Secret is configured."
)
service gateway {
@handler claimWorkerJob
post /workers/jobs/claim (ClaimWorkerJobReq) returns (JobData)
@handler refreshWorkerJobLock
post /workers/jobs/:id/heartbeat (WorkerHeartbeatReq) returns (WorkerOKData)
@handler checkWorkerJobCancel
post /workers/jobs/:id/cancel-check (WorkerJobReq) returns (WorkerCancelCheckData)
@handler ackWorkerJobCancel
post /workers/jobs/:id/cancel-ack (WorkerJobReq) returns (JobData)
@handler updateWorkerJobProgress
post /workers/jobs/:id/progress (WorkerProgressReq) returns (JobData)
@handler completeWorkerJob
post /workers/jobs/:id/complete (WorkerCompleteReq) returns (JobData)
@handler failWorkerJob
post /workers/jobs/:id/fail (WorkerFailReq) returns (JobData)
@handler storePersonaStyleProfileFromWorker
patch /workers/personas/:id/style-profile (StorePersonaStyleProfileReq) returns (StorePersonaStyleProfileData)
@handler getWorkerThreadsAccountSession
post /workers/threads-accounts/:id/session (WorkerThreadsAccountSessionReq) returns (WorkerThreadsAccountSessionData)
@handler analyzeStyle8DFromWorker
post /workers/jobs/:id/analyze-style8d (AnalyzeStyle8DReq) returns (AnalyzeStyle8DData)
}

View File

@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl {{.version}}
package {{.PkgName}}
import (
"net/http"
"haixun-backend/internal/response"
{{if .HasRequest}}"github.com/zeromicro/go-zero/rest/httpx"
{{end}}{{.ImportPackages}}
)
{{if .HasDoc}}{{.Doc}}{{end}}
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
{{if .HasRequest}}var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}data, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
{{if .HasResp}}response.Write(r.Context(), w, data, err){{else}}response.Write(r.Context(), w, nil, err){{end}}
}
}

69
haixun-backend/go.mod Normal file
View File

@ -0,0 +1,69 @@
module haixun-backend
go 1.22
require (
github.com/go-playground/validator/v10 v10.27.0
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/google/uuid v1.6.0
github.com/redis/go-redis/v9 v9.14.0
github.com/robfig/cron/v3 v3.0.1
github.com/zeromicro/go-zero v1.9.2
go.mongodb.org/mongo-driver v1.17.4
golang.org/x/crypto v0.33.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/grafana/pyroscope-go v1.2.7 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/prometheus/client_golang v1.21.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

196
haixun-backend/go.sum Normal file
View File

@ -0,0 +1,196 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac=
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeromicro/go-zero v1.9.2 h1:ZXOXBIcazZ1pWAMiHyVnDQ3Sxwy7DYPzjE89Qtj9vqM=
github.com/zeromicro/go-zero v1.9.2/go.mod h1:k8YBMEFZKjTd4q/qO5RCW+zDgUlNyAs5vue3P4/Kmn0=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=

View File

@ -0,0 +1,72 @@
package bootstrap
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/model/member/domain/entity"
domrepo "haixun-backend/internal/model/member/domain/repository"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
type AdminOptions struct {
TenantID string
Email string
Password string
DisplayName string
}
func EnsureAdminMember(ctx context.Context, repo domrepo.Repository, opts AdminOptions) (*entity.Member, bool, error) {
tenantID := strings.TrimSpace(opts.TenantID)
email := normalizeEmail(opts.Email)
if tenantID == "" || email == "" || opts.Password == "" {
return nil, false, app.For(code.Member).InputMissingRequired("tenant_id, email, and password are required")
}
if len(opts.Password) < 8 {
return nil, false, app.For(code.Member).InputInvalidFormat("password must be at least 8 characters")
}
existing, err := repo.FindByEmail(ctx, tenantID, email)
if err == nil {
if err := repo.SetRoles(ctx, tenantID, existing.UID, []string{"admin"}); err != nil {
return nil, false, err
}
existing.Roles = []string{"admin"}
return existing, false, nil
}
if e := app.FromError(err); e == nil || e.Category() != code.ResNotFound {
return nil, false, err
}
hash, err := bcrypt.GenerateFromPassword([]byte(opts.Password), bcrypt.DefaultCost)
if err != nil {
return nil, false, app.For(code.Member).SysInternal("hash password failed").WithCause(err)
}
displayName := strings.TrimSpace(opts.DisplayName)
if displayName == "" {
displayName = "Admin"
}
member, err := repo.Create(ctx, &entity.Member{
TenantID: tenantID,
UID: uuid.NewString(),
Email: email,
DisplayName: displayName,
Language: "zh-TW",
Status: entity.StatusOpen,
Origin: entity.OriginNative,
PasswordHash: string(hash),
Roles: []string{"admin"},
})
if err != nil {
return nil, false, err
}
return member, true, nil
}
func normalizeEmail(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}

View File

@ -0,0 +1,126 @@
package bootstrap
import (
"context"
"testing"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/model/member/domain/entity"
domrepo "haixun-backend/internal/model/member/domain/repository"
"golang.org/x/crypto/bcrypt"
)
type memoryMemberRepo struct {
byEmail map[string]*entity.Member
}
func memberKey(tenantID, email string) string {
return tenantID + ":" + normalizeEmail(email)
}
func (m *memoryMemberRepo) EnsureIndexes(context.Context) error { return nil }
func (m *memoryMemberRepo) Create(_ context.Context, member *entity.Member) (*entity.Member, error) {
if m.byEmail == nil {
m.byEmail = map[string]*entity.Member{}
}
key := memberKey(member.TenantID, member.Email)
if _, ok := m.byEmail[key]; ok {
return nil, app.For(code.Member).ResConflict("member already exists")
}
cp := *member
m.byEmail[key] = &cp
return &cp, nil
}
func (m *memoryMemberRepo) FindByUID(_ context.Context, tenantID, uid string) (*entity.Member, error) {
for _, item := range m.byEmail {
if item.TenantID == tenantID && item.UID == uid {
cp := *item
return &cp, nil
}
}
return nil, app.For(code.Member).ResNotFound("member not found")
}
func (m *memoryMemberRepo) FindByEmail(_ context.Context, tenantID, email string) (*entity.Member, error) {
item, ok := m.byEmail[memberKey(tenantID, email)]
if !ok {
return nil, app.For(code.Member).ResNotFound("member not found")
}
cp := *item
return &cp, nil
}
func (m *memoryMemberRepo) UpdateProfile(context.Context, string, string, domrepo.ProfileUpdate) (*entity.Member, error) {
return nil, app.For(code.Member).SysNotImplemented("not implemented")
}
func (m *memoryMemberRepo) SetActiveThreadsAccountID(_ context.Context, tenantID, uid, accountID string) error {
for _, item := range m.byEmail {
if item.TenantID == tenantID && item.UID == uid {
item.ActiveThreadsAccountID = accountID
return nil
}
}
return app.For(code.Member).ResNotFound("member not found")
}
func (m *memoryMemberRepo) SetRoles(_ context.Context, tenantID, uid string, roles []string) error {
for _, item := range m.byEmail {
if item.TenantID == tenantID && item.UID == uid {
item.Roles = append([]string(nil), roles...)
return nil
}
}
return app.For(code.Member).ResNotFound("member not found")
}
func TestEnsureAdminMemberCreatesAdmin(t *testing.T) {
repo := &memoryMemberRepo{byEmail: map[string]*entity.Member{}}
member, created, err := EnsureAdminMember(context.Background(), repo, AdminOptions{
TenantID: "default",
Email: "admin@haixun.local",
Password: "Admin-Pass-1!",
})
if err != nil {
t.Fatalf("EnsureAdminMember: %v", err)
}
if !created {
t.Fatal("expected created=true")
}
if member.Roles[0] != "admin" {
t.Fatalf("roles=%v", member.Roles)
}
if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte("Admin-Pass-1!")); err != nil {
t.Fatalf("password hash mismatch: %v", err)
}
}
func TestEnsureAdminMemberUpgradesExisting(t *testing.T) {
repo := &memoryMemberRepo{byEmail: map[string]*entity.Member{
memberKey("default", "admin@haixun.local"): {
TenantID: "default",
UID: "uid-1",
Email: "admin@haixun.local",
Roles: []string{"user"},
},
}}
_, created, err := EnsureAdminMember(context.Background(), repo, AdminOptions{
TenantID: "default",
Email: "admin@haixun.local",
Password: "Admin-Pass-1!",
})
if err != nil {
t.Fatalf("EnsureAdminMember: %v", err)
}
if created {
t.Fatal("expected created=false")
}
member := repo.byEmail[memberKey("default", "admin@haixun.local")]
if len(member.Roles) != 1 || member.Roles[0] != "admin" {
t.Fatalf("roles=%v", member.Roles)
}
}

View File

@ -0,0 +1,104 @@
package bootstrap
import (
"context"
"fmt"
"haixun-backend/internal/config"
libmongo "haixun-backend/internal/library/mongo"
jobrepo "haixun-backend/internal/model/job/repository"
memberrepo "haixun-backend/internal/model/member/repository"
permissionrepo "haixun-backend/internal/model/permission/repository"
permissionuc "haixun-backend/internal/model/permission/usecase"
settingrepo "haixun-backend/internal/model/setting/repository"
)
type InitOptions struct {
TenantID string
AdminEmail string
AdminPass string
DisplayName string
}
type InitReport struct {
IndexesEnsured bool
PermissionsSeeded bool
RolePermissionsSeeded bool
AdminUID string
AdminCreated bool
}
func Init(ctx context.Context, cfg config.Config, opts InitOptions) (*InitReport, error) {
if cfg.Mongo.URI == "" || cfg.Mongo.Database == "" {
return nil, fmt.Errorf("mongo URI and database are required")
}
if opts.TenantID == "" {
return nil, fmt.Errorf("tenant_id is required")
}
if opts.AdminEmail == "" || opts.AdminPass == "" {
return nil, fmt.Errorf("admin email and password are required")
}
mongoClient, err := libmongo.NewClient(ctx, cfg.Mongo)
if err != nil {
return nil, fmt.Errorf("connect mongo: %w", err)
}
defer func() { _ = mongoClient.Close(ctx) }()
db := mongoClient.Database()
report := &InitReport{}
settingRepository := settingrepo.NewMongoRepository(db)
memberRepository := memberrepo.NewMongoRepository(db)
permissionRepository := permissionrepo.NewMongoPermissionRepository(db)
rolePermissionRepository := permissionrepo.NewMongoRolePermissionRepository(db)
jobTemplateRepository := jobrepo.NewMongoTemplateRepository(db)
jobRunRepository := jobrepo.NewMongoRunRepository(db)
jobScheduleRepository := jobrepo.NewMongoScheduleRepository(db)
jobEventRepository := jobrepo.NewMongoEventRepository(db)
repos := []struct {
name string
fn func(context.Context) error
}{
{"settings", settingRepository.EnsureIndexes},
{"members", memberRepository.EnsureIndexes},
{"permissions", permissionRepository.EnsureIndexes},
{"role_permissions", rolePermissionRepository.EnsureIndexes},
{"job_templates", jobTemplateRepository.EnsureIndexes},
{"job_runs", jobRunRepository.EnsureIndexes},
{"job_schedules", jobScheduleRepository.EnsureIndexes},
{"job_events", jobEventRepository.EnsureIndexes},
}
for _, repo := range repos {
if err := repo.fn(ctx); err != nil {
return nil, fmt.Errorf("ensure %s indexes: %w", repo.name, err)
}
}
report.IndexesEnsured = true
permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository)
if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil {
return nil, fmt.Errorf("seed permissions catalog: %w", err)
}
report.PermissionsSeeded = true
if err := permissionUseCase.EnsureDefaultRolePermissions(ctx, opts.TenantID); err != nil {
return nil, fmt.Errorf("seed role permissions: %w", err)
}
report.RolePermissionsSeeded = true
admin, created, err := EnsureAdminMember(ctx, memberRepository, AdminOptions{
TenantID: opts.TenantID,
Email: opts.AdminEmail,
Password: opts.AdminPass,
DisplayName: opts.DisplayName,
})
if err != nil {
return nil, err
}
report.AdminUID = admin.UID
report.AdminCreated = created
return report, nil
}

View File

@ -0,0 +1,58 @@
package config
import "github.com/zeromicro/go-zero/rest"
type MongoConf struct {
URI string
Database string
TimeoutSeconds int `json:",default=10"`
}
type RedisConf struct {
Addr string `json:",optional"`
DB int `json:",optional"`
}
type JobWorkerConf struct {
Enabled bool `json:",default=true"`
WorkerType string `json:",default=go"`
WorkerID string `json:",optional"`
}
type JobSchedulerConf struct {
Enabled bool `json:",default=true"`
IntervalSeconds int `json:",default=60"`
}
type JobReaperConf struct {
Enabled bool `json:",default=true"`
IntervalSeconds int `json:",default=30"`
}
type AuthConf struct {
AccessSecret string `json:",optional"`
RefreshSecret string `json:",optional"`
AccessExpireSeconds int64 `json:",default=900"`
RefreshExpireSeconds int64 `json:",default=2592000"`
DevHeaderFallback bool `json:",default=true"`
}
type InternalWorkerConf struct {
Secret string `json:",optional"`
}
type BraveConf struct {
APIKey string `json:",optional"`
}
type Config struct {
rest.RestConf
Mongo MongoConf `json:",optional"`
Redis RedisConf `json:",optional"`
Auth AuthConf `json:",optional"`
InternalWorker InternalWorkerConf `json:",optional"`
JobWorker JobWorkerConf `json:",optional"`
JobScheduler JobSchedulerConf `json:",optional"`
JobReaper JobReaperConf `json:",optional"`
Brave BraveConf `json:",optional"`
}

View File

@ -0,0 +1,36 @@
package ai
import (
"net/http"
"haixun-backend/internal/logic/ai"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ChatHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AIChatReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
token, err := ai.BearerToken(r)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
l := ai.NewChatLogic(r.Context(), svcCtx)
data, err := l.Chat(&req, token)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,72 @@
package ai
import (
"encoding/json"
"fmt"
"net/http"
"haixun-backend/internal/logic/ai"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ChatStreamHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AIChatReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
token, err := ai.BearerToken(r)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
l := ai.NewChatStreamLogic(r.Context(), svcCtx)
stream, err := l.ChatStream(&req, token)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
flusher, ok := w.(http.Flusher)
if !ok {
response.Write(r.Context(), w, nil, response.WrapRequestError(fmt.Errorf("server does not support streaming")))
return
}
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
for event := range stream {
writeSSE(w, event.Type, event)
flusher.Flush()
if event.Type == "done" || event.Type == "error" {
return
}
}
writeSSE(w, "done", map[string]string{"finish_reason": "stop"})
flusher.Flush()
}
}
func writeSSE(w http.ResponseWriter, eventName string, data any) {
payload, err := json.Marshal(data)
if err != nil {
payload = []byte(`{"type":"error","error":"failed to serialize SSE payload"}`)
eventName = "error"
}
_, _ = fmt.Fprintf(w, "event: %s\n", eventName)
_, _ = fmt.Fprintf(w, "data: %s\n\n", payload)
}

View File

@ -0,0 +1,55 @@
package ai
import (
"fmt"
"net/http"
"haixun-backend/internal/logic/ai"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func IslanderChatStreamHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.IslanderChatReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ai.NewIslanderChatStreamLogic(r.Context(), svcCtx)
stream, err := l.ChatStream(&req)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
flusher, ok := w.(http.Flusher)
if !ok {
response.Write(r.Context(), w, nil, response.WrapRequestError(fmt.Errorf("server does not support streaming")))
return
}
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
for event := range stream {
writeSSE(w, event.Type, event)
flusher.Flush()
if event.Type == "done" || event.Type == "error" {
return
}
}
writeSSE(w, "done", map[string]string{"finish_reason": "stop"})
flusher.Flush()
}
}

View File

@ -0,0 +1,36 @@
package ai
import (
"net/http"
"haixun-backend/internal/logic/ai"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListAiProviderModelsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AIProviderPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
token, err := ai.BearerToken(r)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
l := ai.NewListAIProviderModelsLogic(r.Context(), svcCtx)
data, err := l.ListAIProviderModels(&req, token)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,17 @@
package ai
import (
"net/http"
"haixun-backend/internal/logic/ai"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
)
func ListAiProvidersHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := ai.NewListAIProvidersLogic(r.Context(), svcCtx)
data, err := l.ListAIProviders()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,29 @@
package auth
import (
"net/http"
"haixun-backend/internal/logic/auth"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AuthLoginReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := auth.NewLoginLogic(r.Context(), svcCtx)
data, err := l.Login(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,17 @@
package auth
import (
"net/http"
"haixun-backend/internal/logic/auth"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
)
func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := auth.NewLogoutLogic(r.Context(), svcCtx)
data, err := l.Logout(r.Header.Get("Authorization"))
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,29 @@
package auth
import (
"net/http"
"haixun-backend/internal/logic/auth"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RefreshHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AuthRefreshReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := auth.NewRefreshLogic(r.Context(), svcCtx)
data, err := l.Refresh(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,29 @@
package auth
import (
"net/http"
"haixun-backend/internal/logic/auth"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AuthRegisterReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := auth.NewRegisterLogic(r.Context(), svcCtx)
data, err := l.Register(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreateBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateBrandReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewCreateBrandLogic(r.Context(), svcCtx)
data, err := l.CreateBrand(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreateBrandProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateBrandProductHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewCreateBrandProductLogic(r.Context(), svcCtx)
data, err := l.CreateBrandProduct(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewDeleteBrandLogic(r.Context(), svcCtx)
err := l.DeleteBrand(&req)
response.Write(r.Context(), w, nil, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteBrandProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandProductPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewDeleteBrandProductLogic(r.Context(), svcCtx)
err := l.DeleteBrandProduct(&req)
response.Write(r.Context(), w, nil, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ExpandKnowledgeGraphHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ExpandKnowledgeGraphHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewExpandKnowledgeGraphLogic(r.Context(), svcCtx)
data, err := l.ExpandKnowledgeGraph(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GenerateBrandContentMatrixHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GenerateContentMatrixHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGenerateBrandContentMatrixLogic(r.Context(), svcCtx)
data, err := l.GenerateBrandContentMatrix(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GenerateOutreachDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GenerateOutreachDraftsHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGenerateOutreachDraftsLogic(r.Context(), svcCtx)
data, err := l.GenerateOutreachDrafts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetBrandContentMatrixHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGetBrandContentMatrixLogic(r.Context(), svcCtx)
data, err := l.GetBrandContentMatrix(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGetBrandLogic(r.Context(), svcCtx)
data, err := l.GetBrand(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetBrandScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGetBrandScanScheduleLogic(r.Context(), svcCtx)
data, err := l.GetBrandScanSchedule(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetKnowledgeGraphHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewGetKnowledgeGraphLogic(r.Context(), svcCtx)
data, err := l.GetKnowledgeGraph(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListBrandProductsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewListBrandProductsLogic(r.Context(), svcCtx)
data, err := l.ListBrandProducts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListBrandScanPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListBrandScanPostsHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewListBrandScanPostsLogic(r.Context(), svcCtx)
data, err := l.ListBrandScanPosts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
)
func ListBrandsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := brand.NewListBrandsLogic(r.Context(), svcCtx)
data, err := l.ListBrands()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PatchKnowledgeGraphNodesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PatchKnowledgeGraphNodesHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewPatchKnowledgeGraphNodesLogic(r.Context(), svcCtx)
data, err := l.PatchKnowledgeGraphNodes(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PatchScanPostOutreachHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PatchScanPostOutreachHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewPatchScanPostOutreachLogic(r.Context(), svcCtx)
data, err := l.PatchScanPostOutreach(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PublishOutreachDraftHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PublishOutreachDraftHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewPublishOutreachDraftLogic(r.Context(), svcCtx)
data, err := l.PublishOutreachDraft(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func StartBrandScanJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.StartBrandScanJobHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewStartBrandScanJobLogic(r.Context(), svcCtx)
data, err := l.StartBrandScanJob(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpdateBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateBrandHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewUpdateBrandLogic(r.Context(), svcCtx)
data, err := l.UpdateBrand(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpdateBrandProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateBrandProductHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewUpdateBrandProductLogic(r.Context(), svcCtx)
data, err := l.UpdateBrandProduct(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package brand
import (
"net/http"
"haixun-backend/internal/logic/brand"
"haixun-backend/internal/response"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpsertBrandScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpsertBrandScanScheduleHandlerReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := brand.NewUpsertBrandScanScheduleLogic(r.Context(), svcCtx)
data, err := l.UpsertBrandScanSchedule(&req)
response.Write(r.Context(), w, data, err)
}
}

Some files were not shown because too many files have changed in this diff Show More