feat: add qiniu kodo storage configuration
This commit is contained in:
parent
d2e282030b
commit
4941f09dfa
@ -939,7 +939,7 @@ type AiModelHealthEvent = {
|
||||
type StorageConfig = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "s3" | "local" | "aliyun-oss";
|
||||
type: "s3" | "local" | "aliyun-oss" | "qiniu-kodo";
|
||||
endpoint?: string;
|
||||
region?: string;
|
||||
bucket?: string;
|
||||
@ -3785,7 +3785,7 @@ export function AdminSettingsClient() {
|
||||
const [storageForm, setStorageForm] = useState({
|
||||
id: "",
|
||||
name: "",
|
||||
type: "s3" as "s3" | "local" | "aliyun-oss",
|
||||
type: "s3" as "s3" | "local" | "aliyun-oss" | "qiniu-kodo",
|
||||
endpoint: "",
|
||||
region: "",
|
||||
bucket: "",
|
||||
@ -7252,6 +7252,10 @@ export function AdminSettingsClient() {
|
||||
body.secretAccessKey = String(body.secretAccessKey || "").trim();
|
||||
body.publicUrl = normalizeUrl(body.publicUrl);
|
||||
|
||||
if (body.type === "qiniu-kodo" && !body.publicUrl) {
|
||||
throw new Error("七牛云 Kodo 需要配置公开访问域名");
|
||||
}
|
||||
|
||||
// 兼容处理:如果是阿里云 OSS,转换为 s3 类型提交
|
||||
if (body.type === "aliyun-oss") {
|
||||
body.type = "s3";
|
||||
@ -7275,7 +7279,7 @@ export function AdminSettingsClient() {
|
||||
setShowStorageDialog(false);
|
||||
resetStorageForm();
|
||||
} catch (error) {
|
||||
toast.error("保存存储配置失败");
|
||||
toast.error(error instanceof Error ? error.message : "保存存储配置失败");
|
||||
}
|
||||
};
|
||||
|
||||
@ -16498,7 +16502,9 @@ export function AdminSettingsClient() {
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>存储配置</CardTitle>
|
||||
<CardDescription>配置 S3 兼容对象存储</CardDescription>
|
||||
<CardDescription>
|
||||
配置 S3 兼容对象存储、阿里云 OSS 或七牛云 Kodo
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Dialog
|
||||
onOpenChange={setShowStorageDialog}
|
||||
@ -16547,12 +16553,14 @@ export function AdminSettingsClient() {
|
||||
<SelectContent className="!bg-white !dark:bg-zinc-950">
|
||||
<SelectItem value="s3">S3 兼容</SelectItem>
|
||||
<SelectItem value="aliyun-oss">阿里云 OSS</SelectItem>
|
||||
<SelectItem value="qiniu-kodo">七牛云 Kodo</SelectItem>
|
||||
<SelectItem value="local">本地存储</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{(storageForm.type === "s3" ||
|
||||
storageForm.type === "aliyun-oss") && (
|
||||
storageForm.type === "aliyun-oss" ||
|
||||
storageForm.type === "qiniu-kodo") && (
|
||||
<>
|
||||
{storageForm.type === "aliyun-oss" && (
|
||||
<div className="mb-4 rounded-md border border-blue-100 bg-blue-50 p-3 text-blue-700 text-xs">
|
||||
@ -16566,8 +16574,28 @@ export function AdminSettingsClient() {
|
||||
<p>• 请确保 AccessKey 拥有对应 Bucket 的读写权限</p>
|
||||
</div>
|
||||
)}
|
||||
{storageForm.type === "qiniu-kodo" && (
|
||||
<div className="mb-4 rounded-md border border-emerald-100 bg-emerald-50 p-3 text-emerald-700 text-xs">
|
||||
<p className="mb-1 font-semibold">
|
||||
七牛云 Kodo 配置说明:
|
||||
</p>
|
||||
<p>
|
||||
• Endpoint 填写七牛 S3 兼容访问端点,例如:https://s3.cn-east-1.qiniucs.com
|
||||
</p>
|
||||
<p>
|
||||
• Region 填写七牛 S3 Region ID,例如:cn-east-1
|
||||
</p>
|
||||
<p>
|
||||
• Public URL 必填公开访问域名,例如:https://img.example.com
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label>端点 (Endpoint)</Label>
|
||||
<Label>
|
||||
{storageForm.type === "qiniu-kodo"
|
||||
? "七牛 S3 Endpoint"
|
||||
: "端点 (Endpoint)"}
|
||||
</Label>
|
||||
<Input
|
||||
onChange={(e) =>
|
||||
setStorageForm({
|
||||
@ -16591,7 +16619,11 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Access Key</Label>
|
||||
<Label>
|
||||
{storageForm.type === "qiniu-kodo"
|
||||
? "AccessKey"
|
||||
: "Access Key"}
|
||||
</Label>
|
||||
<Input
|
||||
onChange={(e) =>
|
||||
setStorageForm({
|
||||
@ -16604,7 +16636,11 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Secret Key</Label>
|
||||
<Label>
|
||||
{storageForm.type === "qiniu-kodo"
|
||||
? "SecretKey"
|
||||
: "Secret Key"}
|
||||
</Label>
|
||||
<Input
|
||||
onChange={(e) =>
|
||||
setStorageForm({
|
||||
@ -16618,7 +16654,11 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Region</Label>
|
||||
<Label>
|
||||
{storageForm.type === "qiniu-kodo"
|
||||
? "Region ID"
|
||||
: "Region"}
|
||||
</Label>
|
||||
<Input
|
||||
onChange={(e) =>
|
||||
setStorageForm({
|
||||
@ -16630,7 +16670,11 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Public URL</Label>
|
||||
<Label>
|
||||
{storageForm.type === "qiniu-kodo"
|
||||
? "公开访问域名 (Public URL)"
|
||||
: "Public URL"}
|
||||
</Label>
|
||||
<Input
|
||||
onChange={(e) =>
|
||||
setStorageForm({
|
||||
@ -16708,7 +16752,9 @@ export function AdminSettingsClient() {
|
||||
? (config.endpoint?.includes("aliyuncs.com")
|
||||
? "阿里云 OSS"
|
||||
: "S3") + ` | Bucket: ${config.bucket}`
|
||||
: "本地存储"}
|
||||
: config.type === "qiniu-kodo"
|
||||
? `七牛云 Kodo | Bucket: ${config.bucket}`
|
||||
: "本地存储"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -16718,10 +16764,12 @@ export function AdminSettingsClient() {
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
type:
|
||||
config.type === "s3" &&
|
||||
config.endpoint?.includes("aliyuncs.com")
|
||||
? "aliyun-oss"
|
||||
: (config.type as any),
|
||||
config.type === "qiniu-kodo"
|
||||
? "qiniu-kodo"
|
||||
: config.type === "s3" &&
|
||||
config.endpoint?.includes("aliyuncs.com")
|
||||
? "aliyun-oss"
|
||||
: (config.type as any),
|
||||
endpoint: config.endpoint || "",
|
||||
region: config.region || "",
|
||||
bucket: config.bucket || "",
|
||||
@ -16757,8 +16805,8 @@ export function AdminSettingsClient() {
|
||||
<CardHeader>
|
||||
<CardTitle>存储故障迁移</CardTitle>
|
||||
<CardDescription>
|
||||
主存储桶上传失败时,自动切换到备用 MinIO/S3/阿里云 OSS 等 S3
|
||||
兼容存储桶。
|
||||
主存储桶上传失败时,自动切换到备用 MinIO/S3/阿里云
|
||||
OSS/七牛云 Kodo 等对象存储桶。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
|
||||
@ -11,7 +11,7 @@ import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
const createStorageSchema = z.object({
|
||||
name: z.string().trim().min(1).max(128),
|
||||
type: z.enum(["s3", "local"]),
|
||||
type: z.enum(["s3", "local", "qiniu-kodo"]),
|
||||
endpoint: z.string().trim().optional(),
|
||||
region: z.string().trim().max(64).optional(),
|
||||
bucket: z.string().trim().max(128).optional(),
|
||||
@ -25,7 +25,7 @@ const createStorageSchema = z.object({
|
||||
const updateStorageSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().trim().min(1).max(128).optional(),
|
||||
type: z.enum(["s3", "local"]).optional(),
|
||||
type: z.enum(["s3", "local", "qiniu-kodo"]).optional(),
|
||||
endpoint: z.string().trim().optional(),
|
||||
region: z.string().trim().max(64).optional(),
|
||||
bucket: z.string().trim().max(128).optional(),
|
||||
|
||||
@ -4423,7 +4423,7 @@ export async function createStorageConfig({
|
||||
updatedBy,
|
||||
}: {
|
||||
name: string;
|
||||
type: "s3" | "local";
|
||||
type: "s3" | "local" | "qiniu-kodo";
|
||||
endpoint?: string;
|
||||
region?: string;
|
||||
bucket?: string;
|
||||
@ -4483,7 +4483,7 @@ export async function updateStorageConfig({
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
type?: "s3" | "local";
|
||||
type?: "s3" | "local" | "qiniu-kodo";
|
||||
endpoint?: string;
|
||||
region?: string;
|
||||
bucket?: string;
|
||||
|
||||
@ -392,8 +392,8 @@ export type AiModelHealthEvent = InferSelectModel<typeof aiModelHealthEvent>;
|
||||
export const storageConfig = pgTable("StorageConfig", {
|
||||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||||
name: varchar("name", { length: 128 }).notNull(), // 配置名称
|
||||
type: varchar("type", { enum: ["s3", "local"] }).notNull().default("s3"),
|
||||
endpoint: text("endpoint"), // S3 端点
|
||||
type: varchar("type", { enum: ["s3", "local", "qiniu-kodo"] }).notNull().default("s3"),
|
||||
endpoint: text("endpoint"), // S3/七牛 Kodo 兼容端点
|
||||
region: varchar("region", { length: 64 }), // 区域
|
||||
bucket: varchar("bucket", { length: 128 }), // 存储桶名称
|
||||
accessKeyId: text("accessKeyId"), // Access Key ID
|
||||
|
||||
@ -29,6 +29,7 @@ export type S3UploadConfig = {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
publicUrl?: string;
|
||||
provider?: "s3" | "qiniu-kodo";
|
||||
};
|
||||
|
||||
export type UploadedFileResult = {
|
||||
@ -116,7 +117,7 @@ export function createS3Client(config: S3UploadConfig): S3Client {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
forcePathStyle: !isCloudS3,
|
||||
forcePathStyle: config.provider === "qiniu-kodo" ? true : !isCloudS3,
|
||||
});
|
||||
}
|
||||
|
||||
@ -269,6 +270,8 @@ export async function deleteS3ObjectByPath(pathname: string, config: S3UploadCon
|
||||
|
||||
export function toDbS3UploadConfig(raw: unknown): S3UploadConfig | null {
|
||||
const value = raw as Record<string, unknown> | null | undefined;
|
||||
const provider =
|
||||
value?.type === "qiniu-kodo" ? "qiniu-kodo" : value?.type === "s3" ? "s3" : null;
|
||||
const endpoint = value?.endpoint ? normalizeEndpoint(String(value.endpoint)) : "";
|
||||
const bucket = value?.bucket ? String(value.bucket).trim() : "";
|
||||
const accessKeyId = value?.accessKeyId ? String(value.accessKeyId).trim() : "";
|
||||
@ -278,7 +281,7 @@ export function toDbS3UploadConfig(raw: unknown): S3UploadConfig | null {
|
||||
|
||||
if (
|
||||
!value ||
|
||||
value.type !== "s3" ||
|
||||
!provider ||
|
||||
!value.isEnabled ||
|
||||
!endpoint ||
|
||||
!bucket ||
|
||||
@ -295,6 +298,7 @@ export function toDbS3UploadConfig(raw: unknown): S3UploadConfig | null {
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
publicUrl: normalizePublicUrl(value.publicUrl ? String(value.publicUrl) : undefined),
|
||||
provider,
|
||||
};
|
||||
}
|
||||
|
||||
@ -622,7 +626,7 @@ export async function getStorageType(): Promise<
|
||||
const storageConfig = await getDefaultStorageConfig();
|
||||
if (
|
||||
storageConfig &&
|
||||
storageConfig.type === "s3" &&
|
||||
(storageConfig.type === "s3" || storageConfig.type === "qiniu-kodo") &&
|
||||
storageConfig.endpoint &&
|
||||
storageConfig.bucket
|
||||
) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user