;s
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
export const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL || "http://127.0.0.1:8000";
|
||||
export const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, "");
|
||||
|
||||
export function apiUrl(path: string): string {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
return `${API_BASE}${path.startsWith("/") ? path : `/${path}`}`;
|
||||
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
||||
if (API_BASE.endsWith("/api") && normalizedPath.startsWith("/api/")) {
|
||||
return `${API_BASE}${normalizedPath.slice(4)}`;
|
||||
}
|
||||
return `${API_BASE}${normalizedPath}`;
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* MinIO 客户端封装 - 上传、下载
|
||||
*/
|
||||
|
||||
import * as Minio from "minio";
|
||||
import { getMinioConfig } from "./config";
|
||||
|
||||
export function getMinioClient(): Minio.Client {
|
||||
const cfg = getMinioConfig();
|
||||
|
||||
return new Minio.Client({
|
||||
endPoint: cfg.endPoint,
|
||||
port: cfg.port,
|
||||
useSSL: cfg.useSSL,
|
||||
accessKey: cfg.accessKey,
|
||||
secretKey: cfg.secretKey,
|
||||
pathStyle: true,
|
||||
region: cfg.region,
|
||||
});
|
||||
}
|
||||
|
||||
export interface UploadResult {
|
||||
url: string;
|
||||
objectKey: string;
|
||||
}
|
||||
|
||||
export async function uploadBuffer(
|
||||
buffer: Buffer,
|
||||
objectKey: string,
|
||||
contentType: string = "application/octet-stream"
|
||||
): Promise<UploadResult> {
|
||||
const cfg = getMinioConfig();
|
||||
|
||||
if (!cfg.enabled) {
|
||||
throw new Error("MinIO 存储未启用");
|
||||
}
|
||||
|
||||
const client = getMinioClient();
|
||||
await client.putObject(cfg.bucket, objectKey, buffer, buffer.length, {
|
||||
"Content-Type": contentType,
|
||||
});
|
||||
|
||||
const url = `${cfg.publicUrl}/${objectKey}`;
|
||||
return { url, objectKey };
|
||||
}
|
||||
|
||||
export function generateObjectKey(ext: string): string {
|
||||
const cfg = getMinioConfig();
|
||||
const prefix = cfg.uploadPrefix.replace(/\/$/, "");
|
||||
return `${prefix}/${Date.now()}_${Math.random().toString(36).slice(2)}.${ext}`;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { getMinioConfig } from "@/config/services.config";
|
||||
export type { MinioConfig } from "@/config/services.config";
|
||||
@@ -1,8 +0,0 @@
|
||||
export { getMinioConfig } from "./config";
|
||||
export type { MinioConfig } from "./config";
|
||||
export {
|
||||
getMinioClient,
|
||||
uploadBuffer,
|
||||
generateObjectKey,
|
||||
} from "./client";
|
||||
export type { UploadResult } from "./client";
|
||||
@@ -1,49 +0,0 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
const XORPAY_AID = process.env.XORPAY_AID || "8220";
|
||||
const XORPAY_SECRET =
|
||||
process.env.XORPAY_SECRET || "afcacd99570945f88de62624aaa3578e";
|
||||
const XORPAY_QUERY_URL =
|
||||
process.env.XORPAY_QUERY_URL || "https://xorpay.com/api/query2";
|
||||
|
||||
export async function queryXorPayOrderStatus(
|
||||
orderId: string,
|
||||
timeoutMs = 5000
|
||||
): Promise<{ paid: boolean; status?: string; error?: string; url: string } | null> {
|
||||
const normalizedOrderId = orderId.trim();
|
||||
if (!normalizedOrderId || !XORPAY_AID || !XORPAY_SECRET) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sign = createHash("md5")
|
||||
.update(`${normalizedOrderId}${XORPAY_SECRET}`, "utf8")
|
||||
.digest("hex")
|
||||
.toLowerCase();
|
||||
|
||||
const url = new URL(
|
||||
`${XORPAY_QUERY_URL.replace(/\/$/, "")}/${encodeURIComponent(XORPAY_AID)}`
|
||||
);
|
||||
url.searchParams.set("order_id", normalizedOrderId);
|
||||
url.searchParams.set("sign", sign);
|
||||
|
||||
try {
|
||||
const res = await fetch(url.toString(), {
|
||||
cache: "no-store",
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const status = String(data?.status || "").trim().toLowerCase();
|
||||
return {
|
||||
paid: res.ok && (status === "payed" || status === "success"),
|
||||
status,
|
||||
url: url.toString(),
|
||||
error: res.ok ? undefined : `status=${res.status}`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
paid: false,
|
||||
url: url.toString(),
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
const ZPAY_PID = process.env.ZPAY_PID || "2025121809351743";
|
||||
const ZPAY_KEY = process.env.ZPAY_KEY || "tpEi7wWIWI2kXiYVTpIG6j7it0mjVW89";
|
||||
const ZPAY_QUERY_URL =
|
||||
process.env.ZPAY_QUERY_URL || "https://zpayz.cn/api.php";
|
||||
|
||||
export async function queryZPayOrderStatus(
|
||||
orderId: string,
|
||||
timeoutMs = 5000
|
||||
): Promise<{ paid: boolean; status?: string; error?: string; url: string } | null> {
|
||||
const normalizedOrderId = orderId.trim();
|
||||
if (!normalizedOrderId || !ZPAY_PID || !ZPAY_KEY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = new URL(ZPAY_QUERY_URL);
|
||||
url.searchParams.set("act", "order");
|
||||
url.searchParams.set("pid", ZPAY_PID);
|
||||
url.searchParams.set("key", ZPAY_KEY);
|
||||
url.searchParams.set("out_trade_no", normalizedOrderId);
|
||||
|
||||
try {
|
||||
const res = await fetch(url.toString(), {
|
||||
cache: "no-store",
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const code = String(data?.code || "").trim();
|
||||
const status = String(data?.status || "").trim();
|
||||
return {
|
||||
paid: res.ok && code === "1" && status === "1",
|
||||
status,
|
||||
url: url.toString(),
|
||||
error:
|
||||
res.ok && code === "1"
|
||||
? undefined
|
||||
: String(data?.msg || `status=${res.status}`),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
paid: false,
|
||||
url: url.toString(),
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { getPocketBaseConfig } from "@/config/services.config";
|
||||
|
||||
let cachedToken: { token: string; expiresAt: number } | null = null;
|
||||
const TOKEN_TTL_MS = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* PocketBase v0.23+ 使用 _superusers 集合替代旧 /api/admins 端点。
|
||||
* 此函数依次尝试新旧两个端点,兼容所有版本。
|
||||
*/
|
||||
export async function getAdminToken(): Promise<string | null> {
|
||||
if (cachedToken && Date.now() < cachedToken.expiresAt) {
|
||||
return cachedToken.token;
|
||||
}
|
||||
|
||||
const email = process.env.POCKETBASE_EMAIL;
|
||||
const password = process.env.POCKETBASE_PASSWORD;
|
||||
if (!email || !password) return null;
|
||||
|
||||
const pb = getPocketBaseConfig();
|
||||
const base = pb.url.replace(/\/$/, "");
|
||||
const body = JSON.stringify({ identity: email, password });
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
const endpoints = [
|
||||
`${base}/api/collections/_superusers/auth-with-password`,
|
||||
`${base}/api/admins/auth-with-password`,
|
||||
];
|
||||
|
||||
for (const url of endpoints) {
|
||||
try {
|
||||
const res = await fetch(url, { method: "POST", headers, body });
|
||||
if (!res.ok) continue;
|
||||
const data = await res.json();
|
||||
const token = data?.token;
|
||||
if (token) {
|
||||
cachedToken = { token, expiresAt: Date.now() + TOKEN_TTL_MS };
|
||||
return token;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* PocketBase 认证服务 - 邮箱登录、注册
|
||||
* 认证服务 - 前端只调用 FastAPI,不直连 PocketBase。
|
||||
*/
|
||||
|
||||
import { getPocketBaseConfig } from "@/config/services.config";
|
||||
import { PB_STORAGE_KEYS } from "./constants";
|
||||
import { apiUrl } from "@/app/lib/api-client";
|
||||
|
||||
@@ -15,48 +14,37 @@ export interface AuthUser {
|
||||
export interface AuthResult {
|
||||
token: string;
|
||||
record: AuthUser;
|
||||
is_new?: boolean;
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
function getPBUrl(): string {
|
||||
return getPocketBaseConfig().url;
|
||||
}
|
||||
|
||||
export async function pbLogin(
|
||||
identity: string,
|
||||
password: string
|
||||
): Promise<AuthResult> {
|
||||
const res = await fetch(apiUrl("/api/auth/login"), {
|
||||
async function authRequest(path: string, body: Record<string, unknown>): Promise<AuthResult> {
|
||||
const res = await fetch(apiUrl(path), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ email: identity, password }),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
throw new Error(err?.detail || err?.message || "登录失败");
|
||||
throw new Error(err?.detail || err?.message || "认证失败");
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!data?.token) throw new Error("登录响应异常");
|
||||
return { token: data.token, record: data.record };
|
||||
return {
|
||||
token: data.token,
|
||||
record: data.record,
|
||||
is_new: data.is_new,
|
||||
provider: data.provider,
|
||||
};
|
||||
}
|
||||
|
||||
export async function pbRegister(
|
||||
email: string,
|
||||
password: string,
|
||||
passwordConfirm: string
|
||||
): Promise<AuthResult> {
|
||||
const res = await fetch(apiUrl("/api/auth/register"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ email, password, passwordConfirm }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
throw new Error(err?.detail || err?.message || "注册失败");
|
||||
}
|
||||
const data = await res.json();
|
||||
return { token: data.token, record: data.record };
|
||||
export async function emailLogin(email: string): Promise<AuthResult> {
|
||||
return authRequest("/api/auth/email", { email });
|
||||
}
|
||||
|
||||
export async function googleLogin(idToken: string): Promise<AuthResult> {
|
||||
return authRequest("/api/auth/google", { id_token: idToken });
|
||||
}
|
||||
|
||||
export function pbSaveAuth(token: string, record: AuthUser): void {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { getPocketBaseConfig } from "@/config/services.config";
|
||||
export type { PocketBaseConfig } from "@/config/services.config";
|
||||
@@ -1,6 +1,6 @@
|
||||
export {
|
||||
pbLogin,
|
||||
pbRegister,
|
||||
emailLogin,
|
||||
googleLogin,
|
||||
pbSaveAuth,
|
||||
pbLogout,
|
||||
getStoredUser,
|
||||
@@ -9,4 +9,3 @@ export {
|
||||
} from "./auth";
|
||||
export type { AuthUser, AuthResult } from "./auth";
|
||||
export { PB_STORAGE_KEYS } from "./constants";
|
||||
export { getPocketBaseConfig } from "./config";
|
||||
|
||||
Reference in New Issue
Block a user