You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 lines
8.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Go 到 Python 迁移指南
本文档详细说明了从Go项目迁移到Python项目的对应关系。
## 技术栈对照表
| Go | Python | 说明 |
|----|--------|------|
| Gin | FastAPI | Web框架 |
| Gorilla WebSocket | FastAPI原生WebSocket | WebSocket支持 |
| database/sql + pq | SQLAlchemy + psycopg2 | 数据库访问 |
| encoding/json | Pydantic + json | JSON序列化 |
| os.Getenv | python-dotenv + pydantic-settings | 环境变量 |
| log | logging | 日志 |
| time | datetime | 时间处理 |
| sync.Mutex | threading.Lock/asyncio.Lock | 并发锁 |
| context.Context | 直接使用async/await | 上下文 |
## 项目结构对照表
```
Go Python
├── adapter/ app/adapters/
│ ├── adapter.go base.py
│ └── tushare/
│ ├── adapter.go tushare_adapter.py
│ └── client.go (集成到adapter)
├── api/
│ ├── types.go app/models/types.py
│ ├── router.go app/api/routes.py
│ ├── admin_types.go app/models/admin_types.py
│ └── admin_router.go app/api/admin_routes.py
├── cmd/
│ ├── server/main.go app/main.py
│ └── sync/main.go scripts/sync_data.py
├── internal/
│ ├── handler/
│ │ ├── handler.go (合并到routes)
│ │ └── admin.go (合并到admin_routes)
│ ├── service/
│ │ ├── service.go (拆分到各service)
│ │ ├── stock.go app/services/stock_service.py
│ │ ├── futures.go app/services/futures_service.py
│ │ ├── admin.go app/services/admin_service.py
│ │ ├── config.go app/services/config_service.py
│ │ ├── adapter.go app/services/adapter_service.py
│ │ └── test.go app/services/test_service.py
│ ├── repository/
│ │ ├── repository.go (合并)
│ │ ├── stock.go app/repositories/stock_repository.py
│ │ └── futures.go app/repositories/futures_repository.py
│ ├── model/model.go app/repositories/models.py
│ ├── websocket/server.go app/websocket/server.py
│ └── monitor/monitor.go app/monitor/monitor.py
├── pkg/
│ ├── config/config.go app/core/config.py
│ ├── logger/logger.go app/core/logger.py
│ └── errors/errors.go app/core/errors.py
└── config.json config.json (相同)
```
## 类型系统对照表
### 基础类型
| Go | Python |
|----|--------|
| `type Frequency string` | `class Frequency(str, Enum)` |
| `type AdjustType string` | `class AdjustType(str, Enum)` |
| `type AssetClass string` | `class AssetClass(str, Enum)` |
| `struct KLineItem` | `class KLineItem(BaseModel)` |
| `struct KLineData` | `class KLineData(BaseModel)` |
| `interface Handler` | 通过FastAPI依赖注入实现 |
### 类型转换示例
**Go:**
```go
type KLineItem struct {
Time time.Time `json:"time"`
Open float64 `json:"open"`
Volume int64 `json:"volume"`
}
```
**Python:**
```python
class KLineItem(BaseModel):
time: datetime = Field(..., description="时间戳")
open: float = Field(..., description="开盘价")
volume: int = Field(..., description="成交量")
class Config:
json_encoders = {
datetime: lambda v: v.isoformat()
}
```
## 接口路由对照表
### 股票接口
| Go路由 | Python路由 | 方法 |
|--------|------------|------|
| `stock.GET("/klines/:symbol", r.queryStockKLines)` | `@router.get("/stock/klines/{symbol}")` | GET |
| `stock.GET("/symbols", r.listStockSymbols)` | `@router.get("/stock/symbols")` | GET |
| `stock.POST("/klines/batch", r.batchQueryStockKLines)` | `@router.post("/stock/klines/batch")` | POST |
| `stock.GET("/trading-dates", r.getStockTradingDates)` | `@router.get("/stock/trading-dates")` | GET |
### 参数绑定对比
**Go (Gin):**
```go
func (r *Router) queryStockKLines(c *gin.Context) {
var req KLineQueryRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{...})
return
}
req.Symbol = c.Param("symbol")
// ...
}
```
**Python (FastAPI):**
```python
@router.get("/stock/klines/{symbol}")
def query_stock_klines(
symbol: str, # 路径参数
start: str = Query(...), # 查询参数
end: str = Query(...),
db: Session = Depends(get_db) # 依赖注入
):
service = StockService(db)
req = KLineQueryRequest(symbol=symbol, start=start, end=end)
data = service.query_klines(req)
return Response(code=0, message="success", data=data)
```
## 数据库访问对照表
### 查询K线数据
**Go:**
```go
query := fmt.Sprintf(`
SELECT ts, open, high, low, close, volume, amount
FROM %s
WHERE symbol_id = $1 AND ts >= $2 AND ts <= $3
ORDER BY ts ASC
`, tableName)
rows, err := r.db.QueryContext(ctx, query, symbol, start, end)
```
**Python (SQLAlchemy):**
```python
query = self.db.query(kline_model).filter(
kline_model.symbol_id == symbol,
kline_model.ts >= start,
kline_model.ts <= end
).order_by(kline_model.ts.asc())
results = query.all()
```
### 批量插入
**Go:**
```go
query := fmt.Sprintf(`
INSERT INTO %s (symbol_id, ts, open, ...)
VALUES %s
ON CONFLICT (symbol_id, ts) DO UPDATE SET...
`, tableName, strings.Join(valueStrs, ","))
_, err := r.db.ExecContext(ctx, query, args...)
```
**Python (SQLAlchemy):**
```python
for item in items:
existing = self.db.query(kline_model).filter(...).first()
if existing:
# 更新
existing.open = item.open
...
else:
# 插入
new_record = kline_model(...)
self.db.add(new_record)
self.db.commit()
```
## WebSocket对照表
### Go (Gorilla)
```go
type Hub struct {
clients map[*Client]bool
subscriptions map[string]map[*Client]bool
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
// ...
}
}
}
```
### Python (FastAPI)
```python
class WebSocketManager:
def __init__(self):
self.clients: Dict[str, WSClient] = {}
self.subscriptions: Dict[str, Set[str]] = {}
self.lock = asyncio.Lock()
async def connect(self, websocket: WebSocket, client_id: str):
await websocket.accept()
async with self.lock:
self.clients[client_id] = WSClient(id=client_id, websocket=websocket)
```
## 配置管理对照表
### Go
```go
type Config struct {
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
}
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
// ...
json.Unmarshal(data, &cfg)
return &cfg, nil
}
```
### Python
```python
class Config(BaseModel):
server: ServerConfig = Field(default_factory=ServerConfig)
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
def load_config(config_path: str = "./config.json") -> Config:
with open(config_path, 'r') as f:
data = json.load(f)
return Config.model_validate(data)
```
## 启动方式对照表
### Go
```bash
go run ./cmd/server/main.go
```
### Python
```bash
# 方式1: 直接运行
python -m app.main
# 方式2: 使用uvicorn
uvicorn app.main:app --reload --port 8080
# 方式3: 生产环境
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker
```
## 数据同步工具对照表
### Go
```bash
go run ./cmd/sync -type stocks
go run ./cmd/sync -type klines -symbol 000001.SZ -start 20240301 -end 20240307
```
### Python
```bash
python scripts/sync_data.py --type stocks
python scripts/sync_data.py --type klines --symbol 000001.SZ --start 20240301 --end 20240307
```
## 依赖管理对照表
### Go (go.mod)
```go
require (
github.com/gin-gonic/gin v1.9.1
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.9
)
```
### Python (requirements.txt)
```
fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
psycopg2-binary==2.9.10
```
## 测试接口对照表
| 接口 | Go调用 | Python调用 |
|------|--------|------------|
| 健康检查 | `curl http://localhost:8080/v1/admin/health` | 相同 |
| 查询股票K线 | `curl "http://localhost:8080/v1/stock/klines/000001.SZ?start=20250301&end=20250307" -H "X-API-Key: key"` | 相同 |
| 批量查询 | `curl -X POST ... -d '{"symbols":["000001.SZ"],...}'` | 相同 |
所有API接口和响应格式与Go版本完全一致客户端无需任何修改即可切换到Python后端。