442 lines
10 KiB
Markdown
442 lines
10 KiB
Markdown
|
|
# 常见错误速查与修复方案 (Common Errors Reference)
|
|||
|
|
|
|||
|
|
> 格式:错误信息 -> 常见原因 -> 修复命令/代码
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、CORS 错误
|
|||
|
|
|
|||
|
|
### 错误信息
|
|||
|
|
```
|
|||
|
|
Access to fetch at 'http://api.example.com' from origin 'http://localhost:3000'
|
|||
|
|
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 常见原因
|
|||
|
|
- 后端未配置 CORS 响应头
|
|||
|
|
- 预检请求 (OPTIONS) 未正确处理
|
|||
|
|
- 允许的 Origin 列表遗漏了前端地址
|
|||
|
|
- Nginx 反代层未透传 CORS 头
|
|||
|
|
|
|||
|
|
### 修复方案
|
|||
|
|
|
|||
|
|
**Nginx 配置**:
|
|||
|
|
```nginx
|
|||
|
|
server {
|
|||
|
|
location /api/ {
|
|||
|
|
# 处理预检请求
|
|||
|
|
if ($request_method = 'OPTIONS') {
|
|||
|
|
add_header 'Access-Control-Allow-Origin' '$http_origin';
|
|||
|
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
|
|||
|
|
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
|
|||
|
|
add_header 'Access-Control-Allow-Credentials' 'true';
|
|||
|
|
add_header 'Access-Control-Max-Age' 86400;
|
|||
|
|
return 204;
|
|||
|
|
}
|
|||
|
|
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
|
|||
|
|
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
|||
|
|
proxy_pass http://backend:8000;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Express 配置**:
|
|||
|
|
```typescript
|
|||
|
|
import cors from 'cors';
|
|||
|
|
app.use(cors({
|
|||
|
|
origin: ['http://localhost:3000', 'https://yourdomain.com'],
|
|||
|
|
credentials: true,
|
|||
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|||
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|||
|
|
}));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**FastAPI 配置**:
|
|||
|
|
```python
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=["http://localhost:3000", "https://yourdomain.com"],
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、React Hydration 错误
|
|||
|
|
|
|||
|
|
### 错误信息
|
|||
|
|
```
|
|||
|
|
Hydration failed because the initial UI does not match what was rendered on the server.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 常见触发场景
|
|||
|
|
- 使用 `Date.now()` 或 `Math.random()` 等在服务端和客户端产生不同值的表达式
|
|||
|
|
- 根据 `window` / `localStorage` 条件渲染内容
|
|||
|
|
- 嵌套了无效的 HTML 结构(如 `<p>` 内嵌 `<div>`)
|
|||
|
|
- 浏览器扩展注入了额外的 DOM 节点
|
|||
|
|
|
|||
|
|
### 修复方案
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 方案1:使用 useEffect 延迟客户端渲染
|
|||
|
|
function ClientOnlyTime() {
|
|||
|
|
const [time, setTime] = useState<string>('');
|
|||
|
|
useEffect(() => {
|
|||
|
|
setTime(new Date().toLocaleString());
|
|||
|
|
}, []);
|
|||
|
|
return <span>{time || '--'}</span>; // 服务端渲染占位符
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 方案2:使用 suppressHydrationWarning
|
|||
|
|
<time dateTime={dateString} suppressHydrationWarning>
|
|||
|
|
{formattedDate}
|
|||
|
|
</time>
|
|||
|
|
|
|||
|
|
// 方案3:使用 next/dynamic 禁用 SSR
|
|||
|
|
import dynamic from 'next/dynamic';
|
|||
|
|
const BrowserOnlyChart = dynamic(() => import('./Chart'), { ssr: false });
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、Next.js 常见错误
|
|||
|
|
|
|||
|
|
### 3.1 "use client" 边界错误
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: You're importing a component that needs useState. It only works in a
|
|||
|
|
Client Component but none of its parents are marked with "use client".
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:在使用 React hooks 的组件文件顶部添加 `"use client";`
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
"use client"; // 必须是文件的第一行
|
|||
|
|
import { useState } from 'react';
|
|||
|
|
export default function Counter() {
|
|||
|
|
const [count, setCount] = useState(0);
|
|||
|
|
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 Server/Client 组件混用
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Functions cannot be passed directly to Client Components unless you
|
|||
|
|
explicitly expose it by marking it with "use server".
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:不要将函数 prop 从 Server Component 传给 Client Component,改用 Server Actions:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// app/actions.ts
|
|||
|
|
"use server";
|
|||
|
|
export async function submitForm(data: FormData) {
|
|||
|
|
// 服务端逻辑
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// app/page.tsx (Server Component)
|
|||
|
|
import { submitForm } from './actions';
|
|||
|
|
import ClientForm from './ClientForm';
|
|||
|
|
export default function Page() {
|
|||
|
|
return <ClientForm action={submitForm} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 Dynamic Import 问题
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Element type is invalid: expected a string or a class/function but got: undefined
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:确认 dynamic import 的组件使用了 default export:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 确保组件使用 default export
|
|||
|
|
const DynamicComponent = dynamic(() => import('./MyComponent'), {
|
|||
|
|
loading: () => <p>加载中...</p>,
|
|||
|
|
ssr: false, // 如果组件依赖浏览器 API
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、Prisma/ORM 错误
|
|||
|
|
|
|||
|
|
### 4.1 Connection Pool Exhaustion
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Timed out fetching a new connection from the connection pool.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```prisma
|
|||
|
|
// schema.prisma - 调整连接池
|
|||
|
|
datasource db {
|
|||
|
|
provider = "postgresql"
|
|||
|
|
url = env("DATABASE_URL")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 连接字符串中设置池大小
|
|||
|
|
// DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=10"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 确保 Prisma Client 为单例
|
|||
|
|
// lib/prisma.ts
|
|||
|
|
import { PrismaClient } from '@prisma/client';
|
|||
|
|
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
|||
|
|
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
|||
|
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 Migration Drift
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: The current database is not managed by Prisma Migrate.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```bash
|
|||
|
|
# 重置迁移历史(开发环境)
|
|||
|
|
npx prisma migrate reset
|
|||
|
|
|
|||
|
|
# 将现有数据库与 schema 对齐
|
|||
|
|
npx prisma db pull # 从数据库生成 schema
|
|||
|
|
npx prisma migrate dev # 生成新迁移
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 类型不匹配
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Argument of type 'string' is not assignable to parameter of type 'number'.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:运行 `npx prisma generate` 重新生成类型,确保 schema 和代码类型一致。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、PostgreSQL 错误
|
|||
|
|
|
|||
|
|
### 5.1 连接超时
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: connect ETIMEDOUT / Connection refused
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**排查步骤**:
|
|||
|
|
```bash
|
|||
|
|
# 检查 PostgreSQL 是否在运行
|
|||
|
|
sudo systemctl status postgresql
|
|||
|
|
# 或 Docker 环境
|
|||
|
|
docker ps | grep postgres
|
|||
|
|
|
|||
|
|
# 检查监听端口
|
|||
|
|
ss -tlnp | grep 5432
|
|||
|
|
|
|||
|
|
# 检查 pg_hba.conf 是否允许远程连接
|
|||
|
|
# host all all 0.0.0.0/0 md5
|
|||
|
|
|
|||
|
|
# 检查 postgresql.conf
|
|||
|
|
# listen_addresses = '*'
|
|||
|
|
|
|||
|
|
# 测试连接
|
|||
|
|
psql -h localhost -U myuser -d mydb -c "SELECT 1;"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 死锁检测
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ERROR: deadlock detected
|
|||
|
|
DETAIL: Process 1234 waits for ShareLock on transaction 5678;
|
|||
|
|
blocked by process 9012.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**排查与修复**:
|
|||
|
|
```sql
|
|||
|
|
-- 查看当前锁等待
|
|||
|
|
SELECT pid, usename, query, state, wait_event_type, wait_event
|
|||
|
|
FROM pg_stat_activity
|
|||
|
|
WHERE state = 'active' AND wait_event IS NOT NULL;
|
|||
|
|
|
|||
|
|
-- 查看锁冲突
|
|||
|
|
SELECT blocked.pid AS blocked_pid,
|
|||
|
|
blocking.pid AS blocking_pid,
|
|||
|
|
blocked.query AS blocked_query
|
|||
|
|
FROM pg_catalog.pg_locks blocked
|
|||
|
|
JOIN pg_catalog.pg_locks blocking
|
|||
|
|
ON blocking.locktype = blocked.locktype
|
|||
|
|
AND blocking.relation = blocked.relation
|
|||
|
|
AND blocking.pid != blocked.pid
|
|||
|
|
WHERE NOT blocked.granted;
|
|||
|
|
|
|||
|
|
-- 终止阻塞进程(谨慎使用)
|
|||
|
|
SELECT pg_terminate_backend(<blocking_pid>);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 索引失效
|
|||
|
|
|
|||
|
|
**常见原因**:对列使用了函数、隐式类型转换、LIKE '%前缀'
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 错误:函数导致索引失效
|
|||
|
|
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
|
|||
|
|
|
|||
|
|
-- 修复:创建表达式索引
|
|||
|
|
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
|
|||
|
|
|
|||
|
|
-- 检查查询是否使用了索引
|
|||
|
|
EXPLAIN ANALYZE SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、Docker 错误
|
|||
|
|
|
|||
|
|
### 6.1 端口冲突
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Bind for 0.0.0.0:3000 failed: port is already allocated
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```bash
|
|||
|
|
# 查找占用端口的进程
|
|||
|
|
# Linux/Mac
|
|||
|
|
lsof -i :3000
|
|||
|
|
# Windows
|
|||
|
|
netstat -ano | findstr :3000
|
|||
|
|
|
|||
|
|
# 停止占用端口的容器
|
|||
|
|
docker ps | grep 3000
|
|||
|
|
docker stop <container_id>
|
|||
|
|
|
|||
|
|
# 或更换端口映射
|
|||
|
|
docker run -p 3001:3000 myapp
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 权限问题
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: EACCES: permission denied
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```dockerfile
|
|||
|
|
# Dockerfile 中设置正确的用户
|
|||
|
|
RUN addgroup --system app && adduser --system --ingroup app app
|
|||
|
|
RUN chown -R app:app /app
|
|||
|
|
USER app
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.3 网络不通
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Error: Could not resolve host / Connection refused between containers
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```yaml
|
|||
|
|
# docker-compose.yml - 确保服务在同一网络
|
|||
|
|
services:
|
|||
|
|
app:
|
|||
|
|
networks:
|
|||
|
|
- backend
|
|||
|
|
depends_on:
|
|||
|
|
- db
|
|||
|
|
db:
|
|||
|
|
networks:
|
|||
|
|
- backend
|
|||
|
|
networks:
|
|||
|
|
backend:
|
|||
|
|
driver: bridge
|
|||
|
|
|
|||
|
|
# 容器间使用服务名访问,不要使用 localhost
|
|||
|
|
# DATABASE_URL=postgresql://user:pass@db:5432/mydb
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.4 构建缓存失效
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 强制重新构建(不使用缓存)
|
|||
|
|
docker compose build --no-cache
|
|||
|
|
|
|||
|
|
# 清理悬空镜像和构建缓存
|
|||
|
|
docker system prune -f
|
|||
|
|
docker builder prune -f
|
|||
|
|
|
|||
|
|
# 优化 Dockerfile 层顺序以利用缓存
|
|||
|
|
# 把不常变化的放前面(安装依赖),常变化的放后面(复制代码)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、Git 错误
|
|||
|
|
|
|||
|
|
### 7.1 Merge Conflict
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
CONFLICT (content): Merge conflict in src/app.ts
|
|||
|
|
Automatic merge failed; fix conflicts and then commit the result.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```bash
|
|||
|
|
# 查看冲突文件
|
|||
|
|
git status
|
|||
|
|
|
|||
|
|
# 手动编辑冲突文件,选择保留的代码
|
|||
|
|
# 删除 <<<<<<<, =======, >>>>>>> 标记
|
|||
|
|
|
|||
|
|
# 标记为已解决
|
|||
|
|
git add src/app.ts
|
|||
|
|
git commit -m "resolve merge conflict in app.ts"
|
|||
|
|
|
|||
|
|
# 或放弃合并
|
|||
|
|
git merge --abort
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 Detached HEAD
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
You are in 'detached HEAD' state.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```bash
|
|||
|
|
# 查看当前位置
|
|||
|
|
git log --oneline -5
|
|||
|
|
|
|||
|
|
# 创建新分支保存当前改动
|
|||
|
|
git checkout -b my-fix-branch
|
|||
|
|
|
|||
|
|
# 或回到原来的分支
|
|||
|
|
git checkout main
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.3 大文件推送失败
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
remote: error: File large-file.zip is 150.00 MB; this exceeds the 100 MB limit.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
```bash
|
|||
|
|
# 从历史中移除大文件
|
|||
|
|
git filter-branch --force --index-filter \
|
|||
|
|
'git rm --cached --ignore-unmatch large-file.zip' HEAD
|
|||
|
|
|
|||
|
|
# 使用 Git LFS 管理大文件
|
|||
|
|
git lfs install
|
|||
|
|
git lfs track "*.zip"
|
|||
|
|
git add .gitattributes
|
|||
|
|
git add large-file.zip
|
|||
|
|
git commit -m "track large files with LFS"
|
|||
|
|
|
|||
|
|
# 添加 .gitignore 防止再次提交
|
|||
|
|
echo "*.zip" >> .gitignore
|
|||
|
|
```
|