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

# 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后端。