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