feat: 优化日志信息和数据库初始化脚本
refactor(admin): 重构字体管理页面组件结构 chore: 更新.gitignore和package.json配置 fix: 修复数据库连接和字体服务错误日志
This commit is contained in:
parent
1b70025aac
commit
6eb838d9f0
20 changed files with 13616 additions and 9717 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -52,11 +52,13 @@ web_modules/
|
|||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
.trae
|
||||
.trae/
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
|
||||
.vscode/settings.json
|
||||
# Optional prettier cache
|
||||
.prettiercache
|
||||
|
||||
.contentlayer
|
||||
|
||||
|
|
|
@ -1,313 +0,0 @@
|
|||
# Docker 构建网络问题解决方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
Docker 构建过程中无法拉取依赖包,常见错误包括:
|
||||
- `npm ERR! network request failed`
|
||||
- `yarn error An unexpected error occurred`
|
||||
- `timeout` 或连接超时
|
||||
- DNS 解析失败
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:使用国内镜像源(推荐)
|
||||
|
||||
#### 1. 修改 Dockerfile 使用国内镜像
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# 设置国内镜像源
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
RUN apk update
|
||||
|
||||
# 设置 npm 国内镜像
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
RUN npm config set disturl https://npmmirror.com/dist
|
||||
RUN npm config set electron_mirror https://npmmirror.com/mirrors/electron/
|
||||
RUN npm config set sass_binary_site https://npmmirror.com/mirrors/node-sass/
|
||||
RUN npm config set phantomjs_cdnurl https://npmmirror.com/mirrors/phantomjs/
|
||||
|
||||
# 或者设置 yarn 国内镜像
|
||||
RUN yarn config set registry https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
#### 2. 创建 .npmrc 文件
|
||||
|
||||
在项目根目录创建 `.npmrc` 文件:
|
||||
|
||||
```
|
||||
registry=https://registry.npmmirror.com
|
||||
disturl=https://npmmirror.com/dist
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
sass_binary_site=https://npmmirror.com/mirrors/node-sass/
|
||||
phantomjs_cdnurl=https://npmmirror.com/mirrors/phantomjs/
|
||||
```
|
||||
|
||||
#### 3. 创建 .yarnrc 文件
|
||||
|
||||
```
|
||||
registry "https://registry.npmmirror.com"
|
||||
disturl "https://npmmirror.com/dist"
|
||||
electron_mirror "https://npmmirror.com/mirrors/electron/"
|
||||
sass_binary_site "https://npmmirror.com/mirrors/node-sass/"
|
||||
phantomjs_cdnurl "https://npmmirror.com/mirrors/phantomjs/"
|
||||
```
|
||||
|
||||
### 方案二:Docker 网络配置
|
||||
|
||||
#### 1. 配置 Docker 代理
|
||||
|
||||
创建或编辑 `~/.docker/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"proxies": {
|
||||
"default": {
|
||||
"httpProxy": "http://127.0.0.1:7890",
|
||||
"httpsProxy": "http://127.0.0.1:7890",
|
||||
"noProxy": "localhost,127.0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 构建时传递代理参数
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg HTTP_PROXY=http://127.0.0.1:7890 \
|
||||
--build-arg HTTPS_PROXY=http://127.0.0.1:7890 \
|
||||
--build-arg NO_PROXY=localhost,127.0.0.1 \
|
||||
-t your-app .
|
||||
```
|
||||
|
||||
#### 3. 在 Dockerfile 中使用代理
|
||||
|
||||
```dockerfile
|
||||
# 接收构建参数
|
||||
ARG HTTP_PROXY
|
||||
ARG HTTPS_PROXY
|
||||
ARG NO_PROXY
|
||||
|
||||
# 设置环境变量
|
||||
ENV HTTP_PROXY=$HTTP_PROXY
|
||||
ENV HTTPS_PROXY=$HTTPS_PROXY
|
||||
ENV NO_PROXY=$NO_PROXY
|
||||
|
||||
# 安装依赖
|
||||
RUN npm install
|
||||
|
||||
# 清除代理环境变量(可选)
|
||||
ENV HTTP_PROXY=
|
||||
ENV HTTPS_PROXY=
|
||||
ENV NO_PROXY=
|
||||
```
|
||||
|
||||
### 方案三:使用多阶段构建优化
|
||||
|
||||
#### 1. 分离依赖安装和应用构建
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# 设置镜像源
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
# 只复制依赖文件
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
|
||||
# 安装依赖
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
# 构建阶段
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# 运行阶段
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 方案四:本地构建方案(当前使用)
|
||||
|
||||
基于你当前的 Dockerfile,这是最稳定的方案:
|
||||
|
||||
#### 1. 本地安装依赖和构建
|
||||
|
||||
```bash
|
||||
# 使用国内镜像安装依赖
|
||||
npm config set registry https://registry.npmmirror.com
|
||||
npm install
|
||||
|
||||
# 构建项目
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### 2. Docker 只负责部署
|
||||
|
||||
你当前的 Dockerfile 已经采用这种方案,直接复制构建好的文件。
|
||||
|
||||
### 方案五:使用 Docker Buildx 和缓存
|
||||
|
||||
#### 1. 启用 BuildKit
|
||||
|
||||
```bash
|
||||
export DOCKER_BUILDKIT=1
|
||||
```
|
||||
|
||||
#### 2. 使用缓存挂载
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
COPY package.json yarn.lock* ./
|
||||
|
||||
# 使用缓存挂载
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci --only=production
|
||||
```
|
||||
|
||||
### 方案六:网络诊断和调试
|
||||
|
||||
#### 1. 测试网络连接
|
||||
|
||||
```bash
|
||||
# 在容器内测试网络
|
||||
docker run --rm -it node:20-alpine sh
|
||||
|
||||
# 测试 DNS
|
||||
nslookup registry.npmjs.org
|
||||
|
||||
# 测试连接
|
||||
wget -O- https://registry.npmjs.org
|
||||
|
||||
# 测试国内镜像
|
||||
wget -O- https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
#### 2. 查看详细错误信息
|
||||
|
||||
```bash
|
||||
# 构建时显示详细日志
|
||||
docker build --progress=plain --no-cache -t your-app .
|
||||
|
||||
# npm 详细日志
|
||||
RUN npm install --verbose
|
||||
|
||||
# yarn 详细日志
|
||||
RUN yarn install --verbose
|
||||
```
|
||||
|
||||
### 方案七:离线安装
|
||||
|
||||
#### 1. 创建离线包
|
||||
|
||||
```bash
|
||||
# 下载所有依赖到本地
|
||||
npm pack
|
||||
npm bundle
|
||||
|
||||
# 或使用 yarn
|
||||
yarn pack
|
||||
```
|
||||
|
||||
#### 2. 在 Dockerfile 中使用离线包
|
||||
|
||||
```dockerfile
|
||||
COPY node_modules ./node_modules
|
||||
COPY package.json ./
|
||||
# 跳过 npm install
|
||||
```
|
||||
|
||||
## 推荐解决流程
|
||||
|
||||
### 第一步:尝试国内镜像源
|
||||
1. 创建 `.npmrc` 文件设置国内镜像
|
||||
2. 重新构建 Docker 镜像
|
||||
|
||||
### 第二步:如果仍然失败,使用代理
|
||||
1. 配置 Docker 代理
|
||||
2. 使用构建参数传递代理设置
|
||||
|
||||
### 第三步:最后方案 - 本地构建
|
||||
1. 在本地安装依赖和构建
|
||||
2. Docker 只负责部署(你当前的方案)
|
||||
|
||||
## 常用镜像源列表
|
||||
|
||||
### npm 镜像源
|
||||
- 淘宝镜像:`https://registry.npmmirror.com`
|
||||
- 腾讯镜像:`https://mirrors.cloud.tencent.com/npm/`
|
||||
- 华为镜像:`https://mirrors.huaweicloud.com/repository/npm/`
|
||||
- 中科大镜像:`https://npmreg.proxy.ustclug.org/`
|
||||
|
||||
### yarn 镜像源
|
||||
```bash
|
||||
yarn config set registry https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
### pnpm 镜像源
|
||||
```bash
|
||||
pnpm config set registry https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
## 验证方案
|
||||
|
||||
### 测试镜像源速度
|
||||
```bash
|
||||
# 测试 npm 镜像
|
||||
npm config get registry
|
||||
npm ping
|
||||
|
||||
# 测试下载速度
|
||||
time npm install lodash
|
||||
```
|
||||
|
||||
### 测试 Docker 构建
|
||||
```bash
|
||||
# 清除缓存重新构建
|
||||
docker system prune -a
|
||||
docker build --no-cache -t test-app .
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
1. **优先使用国内镜像源** - 最简单有效的解决方案
|
||||
2. **配置 Docker 代理** - 适用于有稳定代理的环境
|
||||
3. **本地构建方案** - 最稳定但需要本地环境配置
|
||||
4. **多阶段构建** - 优化构建过程和镜像大小
|
||||
5. **离线安装** - 适用于完全无网络环境
|
||||
|
||||
根据你的具体情况选择合适的方案,通常国内镜像源就能解决大部分问题。
|
|
@ -1,14 +1,51 @@
|
|||
'use client';
|
||||
|
||||
import { Table, Pagination, Button, Group, Select, Input, Box, LoadingOverlay, Center, Text } from '@mantine/core';
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { ReactNode, useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import { modals } from '@mantine/modals';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { requestGet, requestPost } from '@/utils/request';
|
||||
import { CATEGORY, LANGUAGES, TAGS } from '@/lib/config';
|
||||
|
||||
const columns = [
|
||||
// 常量配置
|
||||
const PAGE_SIZE = 10;
|
||||
const DEBOUNCE_DELAY = 300;
|
||||
|
||||
// TypeScript 类型定义
|
||||
interface FontItem {
|
||||
id: string;
|
||||
name: string;
|
||||
tags: string[];
|
||||
category: string;
|
||||
languages: string[];
|
||||
fontSubfamily: string[];
|
||||
fontFamily: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface PageData {
|
||||
page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ColumnConfig {
|
||||
dataIndex: keyof FontItem | 'operations';
|
||||
title: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
data: {
|
||||
dataList: FontItem[];
|
||||
page: number;
|
||||
pageTotal: number;
|
||||
};
|
||||
}
|
||||
|
||||
const COLUMNS: ColumnConfig[] = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '字体名',
|
||||
|
@ -46,33 +83,173 @@ const columns = [
|
|||
},
|
||||
];
|
||||
|
||||
type TableItems = Record<string, string | string[]>;
|
||||
const pageSize = 10;
|
||||
export default function Manage() {
|
||||
// 筛选器组件
|
||||
interface FontFiltersProps {
|
||||
keywords: string;
|
||||
setKeywords: (value: string) => void;
|
||||
category: string | null;
|
||||
setCategory: (value: string | null) => void;
|
||||
tags: string | null;
|
||||
setTags: (value: string | null) => void;
|
||||
languages: string | null;
|
||||
setLanguages: (value: string | null) => void;
|
||||
}
|
||||
|
||||
const FontFilters = ({
|
||||
keywords,
|
||||
setKeywords,
|
||||
category,
|
||||
setCategory,
|
||||
tags,
|
||||
setTags,
|
||||
languages,
|
||||
setLanguages,
|
||||
}: FontFiltersProps) => (
|
||||
<Group>
|
||||
<Input
|
||||
value={keywords}
|
||||
onChange={(e) => setKeywords(e.currentTarget.value)}
|
||||
placeholder="搜索字体名称"
|
||||
/>
|
||||
<Select
|
||||
placeholder="分类"
|
||||
data={CATEGORY}
|
||||
value={category}
|
||||
onChange={setCategory}
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
placeholder="标签"
|
||||
value={tags}
|
||||
onChange={setTags}
|
||||
data={TAGS}
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
placeholder="语言"
|
||||
data={LANGUAGES}
|
||||
value={languages}
|
||||
onChange={setLanguages}
|
||||
clearable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
// 操作按钮组件
|
||||
interface FontActionsProps {
|
||||
fontFamily: string;
|
||||
fontId: string;
|
||||
onUpdate: (id: string) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const FontActions = ({ fontFamily, fontId, onUpdate, onDelete }: FontActionsProps) => {
|
||||
const router = useRouter();
|
||||
const [dataSource, setDataSource] = useState<TableItems[]>([]);
|
||||
|
||||
const [pageData, setPageData] = useState({
|
||||
page: 1,
|
||||
total: 0,
|
||||
});
|
||||
const [tags, setTags] = useState<string | null>(null);
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const [languages, setLanguages] = useState<string | null>(null);
|
||||
const [keywords, setKeywords] = useState<string>('');
|
||||
return (
|
||||
<Group justify="center" wrap="nowrap">
|
||||
<Button variant="light" size="xs" onClick={() => router.push(`/admin/detail/${fontFamily}`)}>详情</Button>
|
||||
<Button variant="filled" size="xs" onClick={() => router.push(`/admin/update/${fontFamily}`)}>编辑</Button>
|
||||
<Button variant="filled" color="green" size="xs" onClick={() => onUpdate(fontId)}>更新</Button>
|
||||
<Button variant="light" color="red" onClick={() => onDelete(fontId)} size="xs">删除</Button>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
// 表格行组件
|
||||
interface FontTableRowProps {
|
||||
element: FontItem;
|
||||
onUpdate: (id: string) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const FontTableRow = ({ element, onUpdate, onDelete }: FontTableRowProps) => (
|
||||
<Table.Tr key={element.id}>
|
||||
{COLUMNS.map((col: ColumnConfig) => {
|
||||
let content: ReactNode = (<div className="whitespace-nowrap">{element[col.dataIndex as keyof FontItem]}</div>);
|
||||
|
||||
if (col.dataIndex === 'operations') {
|
||||
content = (
|
||||
<FontActions
|
||||
fontFamily={element.fontFamily}
|
||||
fontId={element.id}
|
||||
onUpdate={onUpdate}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (col.dataIndex === 'created_at' || col.dataIndex === 'updated_at') {
|
||||
content = (
|
||||
<div className="whitespace-nowrap">
|
||||
{new Date(element[col.dataIndex] as string).toLocaleString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (col.dataIndex === 'fontSubfamily') {
|
||||
content = (
|
||||
<>{element[col.dataIndex]?.length}</>
|
||||
);
|
||||
}
|
||||
|
||||
return <Table.Td key={col.dataIndex}>{content}</Table.Td>;
|
||||
})}
|
||||
</Table.Tr>
|
||||
);
|
||||
|
||||
// 自定义Hook:字体列表数据管理
|
||||
const useFontList = (filters: {
|
||||
tags?: string | null;
|
||||
keywords: string;
|
||||
languages?: string | null;
|
||||
category?: string | null;
|
||||
}) => {
|
||||
const [dataSource, setDataSource] = useState<FontItem[]>([]);
|
||||
const [pageData, setPageData] = useState<PageData>({ page: 1, total: 0 });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const requestData = async (page:number) => {
|
||||
setLoading(true);
|
||||
const res = await requestPost('/api/fonts/list', { pageData: { page, size: pageSize }, tags: tags || undefined, keywords: keywords || undefined, languages: languages || undefined, category: category || undefined });
|
||||
setDataSource(res.data.dataList);
|
||||
setPageData({ page: res.data.page, total: res.data.pageTotal });
|
||||
setLoading(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
requestData(1);
|
||||
}, [keywords, category, languages, tags]);
|
||||
|
||||
const deleteFont = (id:string) => modals.openConfirmModal({
|
||||
// 使用防抖搜索
|
||||
const [debouncedKeywords] = useDebouncedValue(filters.keywords, DEBOUNCE_DELAY);
|
||||
|
||||
const requestData = useCallback(async (page: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res: ApiResponse = await requestPost('/api/fonts/list', {
|
||||
pageData: { page, size: PAGE_SIZE },
|
||||
tags: filters.tags || undefined,
|
||||
keywords: debouncedKeywords || undefined,
|
||||
languages: filters.languages || undefined,
|
||||
category: filters.category || undefined,
|
||||
});
|
||||
setDataSource(res.data.dataList);
|
||||
setPageData({ page: res.data.page, total: res.data.pageTotal });
|
||||
} catch (error) {
|
||||
notifications.show({
|
||||
title: '错误',
|
||||
message: '获取数据失败,请重试',
|
||||
color: 'red',
|
||||
position: 'top-right',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [filters.tags, debouncedKeywords, filters.languages, filters.category]);
|
||||
|
||||
const autoUpdate = useCallback(async (id: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await requestGet(`/api/fonts/auto_update/${id}`);
|
||||
requestData(pageData.page);
|
||||
notifications.show({ title: '提示', message: '更新成功', color: 'green', position: 'top-center' });
|
||||
} catch {
|
||||
notifications.show({ title: '提示', message: '更新失败,请待会儿重试', color: 'red', position: 'top-center' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [pageData.page, requestData]);
|
||||
|
||||
const deleteFont = useCallback((id: string) => modals.openConfirmModal({
|
||||
title: '提示',
|
||||
centered: true,
|
||||
children: (
|
||||
|
@ -83,105 +260,120 @@ export default function Manage() {
|
|||
labels: { confirm: '确定', cancel: '取消' },
|
||||
confirmProps: { color: 'red' },
|
||||
onConfirm: async () => {
|
||||
await requestPost('/api/fonts/delete', { id });
|
||||
notifications.show({ title: '提示', message: '删除成功!', color: 'green', position: 'top-right' });
|
||||
requestData(pageData.page);
|
||||
},
|
||||
});
|
||||
|
||||
const autoUpdate = async (id:string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await requestGet(`/api/fonts/auto_update/${id}`);
|
||||
setLoading(false);
|
||||
requestData(pageData.page);
|
||||
notifications.show({ title: '提示', message: '更新成功', color: 'green', position: 'top-center' });
|
||||
} catch {
|
||||
setLoading(false);
|
||||
notifications.show({ title: '提示', message: '更新失败,请待会儿重试', color: 'red', position: 'top-center' });
|
||||
}
|
||||
};
|
||||
const rows = useMemo(() => dataSource?.map((element) => (
|
||||
<Table.Tr key={element.id as string}>
|
||||
{
|
||||
columns.map((col) => {
|
||||
let content:ReactNode = (<div className="whitespace-nowrap">{ element[col.dataIndex]} </div>);
|
||||
if (col.dataIndex === 'operations') {
|
||||
content = (
|
||||
<Group justify="center" wrap="nowrap">
|
||||
<Button variant="light" size="xs" onClick={() => router.push(`/admin/detail/${element.fontFamily}`)}>详情</Button>
|
||||
<Button variant="filled" size="xs" onClick={() => router.push(`/admin/update/${element.fontFamily}`)}>编辑</Button>
|
||||
<Button variant="filled" color="green" size="xs" onClick={() => autoUpdate(element.id as string)}>更新</Button>
|
||||
<Button variant="light" color="red" onClick={() => deleteFont(element.id as string)} size="xs">删除</Button>
|
||||
</Group>);
|
||||
}
|
||||
if (col.dataIndex === 'created_at' || col.dataIndex === 'updated_at') {
|
||||
content = (
|
||||
<div className="whitespace-nowrap">
|
||||
{new Date(element[col.dataIndex] as string).toLocaleString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (col.dataIndex === 'fontSubfamily') {
|
||||
content = (
|
||||
<>{element[col.dataIndex]?.length}</>
|
||||
);
|
||||
}
|
||||
return <Table.Td key={col.dataIndex}>{content}</Table.Td>;
|
||||
})
|
||||
try {
|
||||
await requestPost('/api/fonts/delete', { id });
|
||||
notifications.show({ title: '提示', message: '删除成功!', color: 'green', position: 'top-right' });
|
||||
requestData(pageData.page);
|
||||
} catch {
|
||||
notifications.show({ title: '错误', message: '删除失败,请重试', color: 'red', position: 'top-right' });
|
||||
}
|
||||
</Table.Tr>
|
||||
)),
|
||||
[dataSource]);
|
||||
},
|
||||
}), [pageData.page, requestData]);
|
||||
|
||||
useEffect(() => {
|
||||
requestData(1);
|
||||
}, [requestData]);
|
||||
|
||||
return {
|
||||
dataSource,
|
||||
pageData,
|
||||
loading,
|
||||
requestData,
|
||||
autoUpdate,
|
||||
deleteFont,
|
||||
};
|
||||
};
|
||||
|
||||
export default function Manage() {
|
||||
const [tags, setTags] = useState<string | null>(null);
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const [languages, setLanguages] = useState<string | null>(null);
|
||||
const [keywords, setKeywords] = useState<string>('');
|
||||
|
||||
// 使用自定义Hook管理字体列表数据
|
||||
const {
|
||||
dataSource,
|
||||
pageData,
|
||||
loading,
|
||||
requestData,
|
||||
autoUpdate,
|
||||
deleteFont,
|
||||
} = useFontList({ tags, keywords, languages, category });
|
||||
const rows = useMemo(() => dataSource?.map((element) => (
|
||||
<FontTableRow
|
||||
key={element.id}
|
||||
element={element}
|
||||
onUpdate={autoUpdate}
|
||||
onDelete={deleteFont}
|
||||
/>
|
||||
)), [dataSource, autoUpdate, deleteFont]);
|
||||
|
||||
// 空状态组件
|
||||
const EmptyState = () => (
|
||||
<Center py={60}>
|
||||
<Box ta="center">
|
||||
<Text size="lg" c="dimmed" mb="xs">
|
||||
暂无字体数据
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{keywords || category || tags || languages
|
||||
? '没有找到符合条件的字体,请尝试调整筛选条件'
|
||||
: '还没有添加任何字体,请先添加字体'}
|
||||
</Text>
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box pos="relative">
|
||||
<LoadingOverlay visible={loading} />
|
||||
<Group>
|
||||
<Input
|
||||
value={keywords}
|
||||
onChange={(e) => setKeywords(e.currentTarget.value)}
|
||||
placeholder="搜索字体名称"
|
||||
/>
|
||||
<Select
|
||||
placeholder="分类"
|
||||
data={CATEGORY}
|
||||
value={category}
|
||||
onChange={setCategory}
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
placeholder="标签"
|
||||
value={tags}
|
||||
onChange={setTags}
|
||||
data={TAGS}
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
placeholder="语言"
|
||||
data={LANGUAGES}
|
||||
value={languages}
|
||||
onChange={setLanguages}
|
||||
clearable
|
||||
/>
|
||||
</Group>
|
||||
<Table.ScrollContainer minWidth={400}>
|
||||
<Table highlightOnHover withRowBorders={false} horizontalSpacing="lg" verticalSpacing="lg">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{columns.map((col, i) => (
|
||||
<Table.Th className="whitespace-nowrap" key={i}>{col.title}</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody className="min-h-96">
|
||||
{rows}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
<Center mt={24}>
|
||||
<Pagination value={pageData.page} total={pageData.total} onChange={requestData} />
|
||||
</Center>
|
||||
<LoadingOverlay
|
||||
visible={loading}
|
||||
overlayProps={{ radius: 'sm', blur: 2 }}
|
||||
loaderProps={{ color: 'blue', type: 'bars' }}
|
||||
/>
|
||||
<FontFilters
|
||||
keywords={keywords}
|
||||
setKeywords={setKeywords}
|
||||
category={category}
|
||||
setCategory={setCategory}
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
languages={languages}
|
||||
setLanguages={setLanguages}
|
||||
/>
|
||||
|
||||
{!loading && (!dataSource || dataSource.length === 0) ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
<Table.ScrollContainer minWidth={400}>
|
||||
<Table highlightOnHover withRowBorders={false} horizontalSpacing="lg" verticalSpacing="lg">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{COLUMNS.map((col: ColumnConfig, i: number) => (
|
||||
<Table.Th className="whitespace-nowrap" key={i}>{col.title}</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody className="min-h-96">
|
||||
{rows}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
|
||||
{pageData.total > 0 && (
|
||||
<Center mt={24}>
|
||||
<Pagination
|
||||
value={pageData.page}
|
||||
total={pageData.total}
|
||||
onChange={requestData}
|
||||
size="sm"
|
||||
withEdges
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
|
|||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('CSS API 错误:', error);
|
||||
Logger.error('CSS生成页面 - CSS生成接口错误:', error);
|
||||
return new NextResponse('/* 内部服务器错误 */', {
|
||||
status: 500,
|
||||
headers: {
|
||||
|
@ -296,7 +296,7 @@ async function generateCSSResponseOriginal(
|
|||
const staticBaseRaw = APP_CONFIG.fontStaticUrl;
|
||||
if (!staticBaseRaw) {
|
||||
Logger.error(
|
||||
'缺少字体静态资源基础 URL。请在环境变量中设置 NEXT_PUBLIC_FONT_STATIC_URL 或 OSS_ENDPOINT。'
|
||||
'CSS生成页面 - 缺少字体静态资源基础 URL。请在环境变量中设置 NEXT_PUBLIC_FONT_STATIC_URL 或 OSS_ENDPOINT。'
|
||||
);
|
||||
throw new Error('缺少字体静态资源基础 URL');
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ async function generateCSSResponseOriginal(
|
|||
|
||||
return cssContent;
|
||||
} catch (error: any) {
|
||||
Logger.error('生成 CSS 响应时出错:', error);
|
||||
Logger.error('CSS生成页面 - 生成CSS响应时出错:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export async function GET(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Auto update font error:', error);
|
||||
Logger.error('字体管理页面 - 自动更新字体接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -61,7 +61,7 @@ export async function POST(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in create API:', error);
|
||||
Logger.error('字体管理页面 - 创建字体接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -32,7 +32,7 @@ export async function POST(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in delete API:', error);
|
||||
Logger.error('字体管理页面 - 删除字体接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -39,7 +39,7 @@ export async function GET(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in detail API:', error);
|
||||
Logger.error('字体详情页面 - 获取字体详情接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
@ -94,7 +94,7 @@ export async function POST(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in detail_by_name API:', error);
|
||||
Logger.error('字体详情页面 - 根据名称获取字体详情接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -35,7 +35,7 @@ export async function POST(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in list API:', error);
|
||||
Logger.error('字体列表页面 - 获取字体列表接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
@ -60,7 +60,7 @@ export async function GET(): Promise<NextResponse<IResponseData<any>>> {
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in list all API:', error);
|
||||
Logger.error('字体列表页面 - 获取所有字体接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -33,7 +33,7 @@ export async function POST(
|
|||
message: 'success',
|
||||
});
|
||||
} catch (error: any) {
|
||||
Logger.error('Error in update API:', error);
|
||||
Logger.error('字体管理页面 - 更新字体接口错误:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
|
|
|
@ -18,7 +18,6 @@ async function fetchFonts(): Promise<TypefaceOutput[]> {
|
|||
const typefaceService = new TypefaceService();
|
||||
// findAll() 不传参数时返回所有字体数组
|
||||
const result = await typefaceService.findAll();
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
// 将数据库返回的数据转换为TypefaceOutput格式
|
||||
return result.map(font => ({
|
||||
|
@ -27,9 +26,10 @@ async function fetchFonts(): Promise<TypefaceOutput[]> {
|
|||
updated_at: new Date(font.updatedAt),
|
||||
})) as TypefaceOutput[];
|
||||
}
|
||||
Logger.info('获取字体列表成功,字体列表长度:', result.dataList.length);
|
||||
return [];
|
||||
} catch (error) {
|
||||
Logger.error('Failed to fetch fonts:', error);
|
||||
Logger.error('字体展示页面 - 获取字体列表失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import type { Config } from 'drizzle-kit';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
// 加载环境变量
|
||||
dotenv.config({ path: '.env.development' });
|
||||
|
||||
// 构建 PostgreSQL 连接字符串
|
||||
function buildConnectionString(): string {
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
"build": "NODE_ENV=production next build",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"init-db": "node scripts/init-db.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
|
@ -28,6 +29,8 @@
|
|||
"adm-zip": "^0.5.15",
|
||||
"ali-oss": "^6.21.0",
|
||||
"contentlayer2": "^0.5.0",
|
||||
"csv-parse": "^6.1.0",
|
||||
"csv-parser": "^3.2.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
|
@ -58,6 +61,7 @@
|
|||
"remark-gfm": "^4.0.0",
|
||||
"remark-github-blockquote-alert": "^1.2.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"uuid": "^11.1.0",
|
||||
"wf-cn-font-split": "5.2.6",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
|
|
87
scripts/init-db.js
Normal file
87
scripts/init-db.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
const { Client } = require('pg');
|
||||
|
||||
// 从环境变量读取数据库配置
|
||||
require('dotenv').config({ path: '.env.development' });
|
||||
|
||||
const dbConfig = {
|
||||
host: process.env.DATA_BASE_HOST,
|
||||
port: process.env.DATA_BASE_PORT,
|
||||
user: process.env.DATA_BASE_USER,
|
||||
password: process.env.DATA_BASE_PASSWORD,
|
||||
database: process.env.DATA_BASE_NAME,
|
||||
};
|
||||
|
||||
async function initDatabase() {
|
||||
const client = new Client(dbConfig);
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 创建用户专属schema
|
||||
const schemaName = 'schema_' + process.env.DATA_BASE_USER;
|
||||
|
||||
console.log(`🔧 创建schema: ${schemaName}`);
|
||||
await client.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}";`);
|
||||
|
||||
// 创建wf_table_fonts表
|
||||
console.log('🔧 创建wf_table_fonts表');
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS "${schemaName}"."wf_table_fonts" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"font_family" varchar(255) NOT NULL,
|
||||
"font_subfamily" varchar(255)[] NOT NULL,
|
||||
"version" varchar(255) NOT NULL,
|
||||
"copyright" text,
|
||||
"description" text,
|
||||
"designer" text,
|
||||
"license" text,
|
||||
"category" varchar(255),
|
||||
"tags" varchar(255) DEFAULT '其他',
|
||||
"languages" varchar(255) DEFAULT '简体中文',
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
await client.query(createTableSQL);
|
||||
|
||||
// 创建索引
|
||||
console.log('🔧 创建索引');
|
||||
const indexes = [
|
||||
`CREATE INDEX IF NOT EXISTS "wf_table_fonts_name" ON "${schemaName}"."wf_table_fonts" ("name");`,
|
||||
`CREATE INDEX IF NOT EXISTS "wf_table_fonts_category" ON "${schemaName}"."wf_table_fonts" ("category");`,
|
||||
`CREATE INDEX IF NOT EXISTS "wf_table_fonts_tags" ON "${schemaName}"."wf_table_fonts" ("tags");`,
|
||||
`CREATE INDEX IF NOT EXISTS "wf_table_fonts_languages" ON "${schemaName}"."wf_table_fonts" ("languages");`
|
||||
];
|
||||
|
||||
for (const indexSQL of indexes) {
|
||||
await client.query(indexSQL);
|
||||
}
|
||||
|
||||
console.log('✅ 数据库初始化完成');
|
||||
|
||||
// 检查表是否创建成功
|
||||
const result = await client.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = '${schemaName}' AND table_name = 'wf_table_fonts';
|
||||
`);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
console.log('✅ wf_table_fonts表创建成功');
|
||||
} else {
|
||||
console.log('❌ wf_table_fonts表创建失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库初始化失败:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行初始化
|
||||
initDatabase();
|
|
@ -57,7 +57,7 @@ export async function testConnection() {
|
|||
Logger.info('Database connection established successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
Logger.error('Database connection failed:', error);
|
||||
Logger.error('数据库服务 - 数据库连接失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ export async function closeDatabase() {
|
|||
await client.end();
|
||||
Logger.info('Database connection closed');
|
||||
} catch (error) {
|
||||
Logger.error('Error closing database connection:', error);
|
||||
Logger.error('数据库服务 - 关闭数据库连接错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { pgTable, varchar, text, uuid, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
import { varchar, text, uuid, timestamp, index, pgSchema } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const typefaces = pgTable(
|
||||
// 创建用户专属schema
|
||||
const schemaName = `schema_${process.env.DATA_BASE_USER}`;
|
||||
const userSchema = pgSchema(schemaName);
|
||||
|
||||
export const typefaces = userSchema.table(
|
||||
'wf_table_fonts',
|
||||
{
|
||||
id: uuid('id').primaryKey().notNull(),
|
||||
|
|
|
@ -44,7 +44,7 @@ const transports = [
|
|||
|
||||
// 创建 logger 实例
|
||||
const Logger = winston.createLogger({
|
||||
level: 'info',
|
||||
level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
|
||||
levels,
|
||||
format,
|
||||
transports,
|
||||
|
|
|
@ -124,7 +124,7 @@ async function uploadSingleFile(
|
|||
} catch (error: any) {
|
||||
lastError = error as Error;
|
||||
Logger.warn(
|
||||
`Upload attempt ${attempt} failed: ${error?.message || 'Unknown error'}`,
|
||||
`字体服务 - 上传尝试 ${attempt} 失败: ${error?.message || 'Unknown error'}`,
|
||||
);
|
||||
if (attempt < retries) {
|
||||
await new Promise((resolve) => { setTimeout(resolve, 1000 * attempt); });
|
||||
|
@ -133,7 +133,7 @@ async function uploadSingleFile(
|
|||
}
|
||||
|
||||
throw new Error(
|
||||
`Upload failed after ${retries} attempts: ${lastError?.message || 'Unknown error'}`,
|
||||
`字体服务 - 上传失败,已重试 ${retries} 次: ${lastError?.message || 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ export class TypefaceService {
|
|||
endpoint: OSS_CONFIG.endpoint,
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error('Failed to initialize OSS client', error);
|
||||
Logger.error('字体服务 - OSS客户端初始化失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ export class TypefaceService {
|
|||
|
||||
return new Paging(transformedData, Number(count), page, size);
|
||||
} catch (error) {
|
||||
Logger.error('Failed to fetch fonts', error);
|
||||
Logger.error('字体服务 - 加载字体数据错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ export class TypefaceService {
|
|||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
Logger.error('Failed to fetch font', error);
|
||||
Logger.error('加载单个字体错误', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ export class TypefaceService {
|
|||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
Logger.error('Failed to fetch font by name', error);
|
||||
Logger.error('字体服务 - 根据名称获取字体失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -406,7 +406,7 @@ export class TypefaceService {
|
|||
|
||||
return updatedFont;
|
||||
} catch (error) {
|
||||
Logger.error('Failed to update font', error);
|
||||
Logger.error('字体服务 - 更新字体失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ export class TypefaceService {
|
|||
if (licenseFileBuffer) break;
|
||||
}
|
||||
} catch (licenseError) {
|
||||
Logger.warn(`Failed to get license file for ${subfamily}:`, licenseError);
|
||||
Logger.warn(`字体服务 - 获取许可证文件失败 ${subfamily}:`, licenseError);
|
||||
}
|
||||
|
||||
// 上传处理后的文件到 OSS
|
||||
|
@ -513,9 +513,9 @@ export class TypefaceService {
|
|||
licenseFileBuffer,
|
||||
licenseFileName,
|
||||
});
|
||||
Logger.info(`Successfully uploaded font variant to OSS: ${subfamily}`);
|
||||
Logger.info(`字体服务 - 成功上传字体变体到OSS: ${subfamily}`);
|
||||
} catch (uploadError) {
|
||||
Logger.error(`Failed to upload font variant to OSS: ${subfamily}`, uploadError);
|
||||
Logger.error(`字体服务 - OSS上传字体变体失败: ${subfamily}`, uploadError);
|
||||
// 继续处理,不因为上传失败而中断整个流程
|
||||
}
|
||||
|
||||
|
@ -526,17 +526,17 @@ export class TypefaceService {
|
|||
finalMetaInfo = metaInfo;
|
||||
}
|
||||
|
||||
Logger.info(`Successfully processed font variant: ${subfamily}`);
|
||||
Logger.info(`字体服务 - 成功处理字体变体: ${subfamily}`);
|
||||
|
||||
// 清理临时文件目录
|
||||
try {
|
||||
await rm(fontPackageDir, { recursive: true, force: true });
|
||||
Logger.info(`Cleaned up temporary directory: ${fontPackageDir}`);
|
||||
Logger.info(`字体服务 - 清理临时目录: ${fontPackageDir}`);
|
||||
} catch (cleanupError) {
|
||||
Logger.warn(`Failed to cleanup temporary directory: ${fontPackageDir}`, cleanupError);
|
||||
Logger.warn(`字体服务 - 清理临时目录失败: ${fontPackageDir}`, cleanupError);
|
||||
}
|
||||
} catch (variantError) {
|
||||
Logger.error(`Failed to process font variant: ${subfamily}`, variantError);
|
||||
Logger.error(`字体服务 - 处理字体变体失败: ${subfamily}`, variantError);
|
||||
// 继续处理下一个变体,不中断整个流程
|
||||
}
|
||||
}
|
||||
|
@ -577,7 +577,7 @@ export class TypefaceService {
|
|||
metaInfo: finalMetaInfo,
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error('Auto update failed', error);
|
||||
Logger.error('字体服务 - 自动更新失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
@ -723,9 +723,9 @@ export class TypefaceService {
|
|||
if (fontPackageDir) {
|
||||
try {
|
||||
await rm(fontPackageDir, { recursive: true, force: true });
|
||||
Logger.info(`Cleaned up temporary directory: ${fontPackageDir}`);
|
||||
Logger.info(`字体服务 - 清理临时目录: ${fontPackageDir}`);
|
||||
} catch (cleanupError) {
|
||||
Logger.warn(`Failed to cleanup temporary directory: ${fontPackageDir}`, cleanupError);
|
||||
Logger.warn(`字体服务 - 清理临时目录失败: ${fontPackageDir}`, cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -735,7 +735,7 @@ export class TypefaceService {
|
|||
|
||||
return result;
|
||||
} catch (error) {
|
||||
Logger.error('Font creation failed', error);
|
||||
Logger.error('字体服务 - 创建字体失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
@ -865,7 +865,7 @@ export class TypefaceService {
|
|||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
Logger.error('OSS upload failed', error);
|
||||
Logger.error('字体服务 - OSS上传失败:', error);
|
||||
throw new Error(`OSS upload failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
@ -887,7 +887,7 @@ export class TypefaceService {
|
|||
|
||||
results.push({ id, success: true });
|
||||
} catch (error: any) {
|
||||
Logger.error(`Failed to delete font ${id}`, error);
|
||||
Logger.error(`字体服务 - 删除字体失败 ${id}`, error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
errors.push({ id, error: errorMessage });
|
||||
results.push({ id, success: false, error: errorMessage });
|
||||
|
@ -910,7 +910,7 @@ export class TypefaceService {
|
|||
errors: undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error('Batch delete transaction failed', error);
|
||||
Logger.error('字体服务 - 批量删除事务失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
results,
|
||||
|
@ -956,7 +956,7 @@ export class TypefaceService {
|
|||
|
||||
results.push({ id: update.id, success: true });
|
||||
} catch (error: any) {
|
||||
Logger.error(`Failed to update font ${update.id}`, error);
|
||||
Logger.error(`字体服务 - 更新字体失败 ${update.id}`, error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
errors.push({ id: update.id, error: errorMessage });
|
||||
results.push({ id: update.id, success: false, error: errorMessage });
|
||||
|
@ -979,7 +979,7 @@ export class TypefaceService {
|
|||
errors: undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error('Batch update transaction failed', error);
|
||||
Logger.error('字体服务 - 批量更新事务失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
results,
|
||||
|
@ -1011,7 +1011,7 @@ export class TypefaceService {
|
|||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error('Failed to get statistics', error);
|
||||
Logger.error('字体服务 - 获取统计信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export class ConcurrentUploader {
|
|||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
this.results.push({ success: false, fileName, error: errorMessage });
|
||||
Logger.error(`Failed to upload ${fileName}:`, error);
|
||||
Logger.error(`字体服务 - 文件上传失败 ${fileName}:`, error);
|
||||
resolve(); // 不要 reject,让其他文件继续上传
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue