Files
blog/blog-admin/src/lib/util.ts
T
zqlit a74b3c7127
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
2026-10-04 08:45:40 +08:00

141 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Env } from '../types';
export const CN_OFFSET_MS = 8 * 60 * 60 * 1000;
/** 毫秒时间戳 → "YYYY-MM-DD HH:mm:ss"(Asia/Shanghai,中国无夏令时) */
export function formatDateCN(ms: number): string {
const d = new Date((ms || 0) + CN_OFFSET_MS);
const p = (n: number, len = 2) => String(n).padStart(len, '0');
return (
`${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` +
`${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}`
);
}
export function now(): number {
return Date.now();
}
// ------------------------------------------------------------------ 响应封装
export function json(data: unknown, init: ResponseInit = {}): Response {
const headers = new Headers(init.headers);
headers.set('Content-Type', 'application/json; charset=utf-8');
return new Response(JSON.stringify(data ?? {}), { ...init, headers });
}
/** Artalk 成功响应:RespData 是「裸数据」,RespSuccess 是 {"msg":"Success"} */
export function ok(data: unknown): Response {
return json(data);
}
export function okMsg(msg = 'Success', extra: Record<string, unknown> = {}): Response {
return json({ msg, ...extra });
}
/** Artalk 错误响应:{"msg": "..."} + 非 200 状态码 */
export function fail(status: number, msg: string, extra: Record<string, unknown> = {}): Response {
return json({ msg, ...extra }, { status });
}
// ------------------------------------------------------------------ 请求解析
export function getClientIP(req: Request): string {
return (
req.headers.get('CF-Connecting-IP') ||
(req.headers.get('X-Forwarded-For') || '').split(',')[0].trim() ||
'127.0.0.1'
);
}
export function getUserAgent(req: Request): string {
return req.headers.get('User-Agent') || '';
}
export async function readBody(req: Request): Promise<Record<string, any>> {
const ct = req.headers.get('Content-Type') || '';
try {
if (ct.includes('application/json')) {
const v = await req.json();
return v && typeof v === 'object' ? (v as Record<string, any>) : {};
}
if (ct.includes('form-urlencoded')) {
const text = await req.text();
const out: Record<string, any> = {};
for (const [k, v] of new URLSearchParams(text)) out[k] = v;
return out;
}
const text = await req.text();
if (!text) return {};
try {
const v = JSON.parse(text);
return v && typeof v === 'object' ? v : {};
} catch {
const out: Record<string, any> = {};
for (const [k, v] of new URLSearchParams(text)) out[k] = v;
return out;
}
} catch {
return {};
}
}
export function qp(url: URL, key: string): string {
return url.searchParams.get(key) ?? '';
}
export function qInt(url: URL, key: string, dft = 0): number {
const v = parseInt(url.searchParams.get(key) ?? '', 10);
return Number.isFinite(v) ? v : dft;
}
export function qBool(url: URL, key: string): boolean {
const v = (url.searchParams.get(key) ?? '').toLowerCase();
return v === '1' || v === 'true' || v === 'yes';
}
// ------------------------------------------------------------------ 校验
export function isEmail(s: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((s || '').trim());
}
export function isUrl(s: string): boolean {
try {
const u = new URL(s);
return u.protocol === 'http:' || u.protocol === 'https:';
} catch {
return false;
}
}
export function trimTo(s: string, max: number): string {
const v = String(s ?? '');
return v.length > max ? v.slice(0, max) : v;
}
// ------------------------------------------------------------------ CORS
export function corsHeaders(req: Request, env: Env): Headers {
const headers = new Headers();
const origin = req.headers.get('Origin') || '';
const allow = (env.ALLOWED_ORIGINS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
if (origin && (allow.includes(origin) || allow.includes('*'))) {
headers.set('Access-Control-Allow-Origin', origin);
} else if (!origin) {
headers.set('Access-Control-Allow-Origin', '*');
}
headers.set('Vary', 'Origin');
headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
headers.set(
'Access-Control-Allow-Headers',
'Content-Type,Authorization,X-Requested-With,X-Api-Version',
);
headers.set('Access-Control-Max-Age', '86400');
return headers;
}