19508dcd19
主要功能: - 新增 PolymarketActivityWsService:通过 Activity WebSocket 实时监听 Leader 交易(延迟 < 100ms) - 实现双重监听机制:Activity WS(低延迟)+ On-Chain WS(高可靠性) - 支持动态添加/移除 Leader 监听 - 地址筛选忽略大小写 修复和改进: - 修复订阅消息格式:使用 'action' 而不是 'type'(与 @polymarket/real-time-data-client 保持一致) - 修复 PING 消息格式:使用小写 'ping'(与官方客户端保持一致) - 添加 outcomeIndex 字段支持:优先使用消息中的 outcomeIndex,fallback 到从 outcome 解析 - 添加 connection_id 字段到 DTO - 改进日志记录:增加 INFO 级别日志以便调试 代码重构: - JsonUtils 重构:将 fromJson 和 toJson 改为扩展方法 - 更新后端开发规范:JSON 解析和 Data Class 规范 前端修复: - 修复 InputNumber parser 类型错误:返回 number 而不是 string 文档: - 添加双重监听方案文档 - 添加 Polymarket Activity WebSocket API 格式文档
234 lines
6.2 KiB
Plaintext
234 lines
6.2 KiB
Plaintext
---
|
||
alwaysApply: true
|
||
path: backend/**
|
||
---
|
||
|
||
# 后端开发规范
|
||
|
||
## 核心原则
|
||
- **禁止**在代码中添加 TODO/FIXME/XXX 注释
|
||
- **禁止**返回 mock 数据或硬编码的假数据
|
||
- **禁止**在 API 调用失败时返回默认值作为 fallback
|
||
- 所有功能必须完整实现,确保代码可以正常运行
|
||
|
||
## 项目范围
|
||
- **平台**: 仅支持 Polymarket
|
||
- **包名**: `com.wrbug.polymarketbot`
|
||
|
||
## 实体类规范
|
||
```kotlin
|
||
@Entity
|
||
@Table(name = "example_table")
|
||
data class ExampleEntity(
|
||
@Id
|
||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||
val id: Long? = null, // ✅ 正确:使用可空类型
|
||
|
||
@Column(name = "created_at", nullable = false)
|
||
val createdAt: Long = System.currentTimeMillis(), // ✅ 使用 Long 时间戳
|
||
|
||
@Column(name = "amount", nullable = false, precision = 20, scale = 8)
|
||
val amount: BigDecimal = BigDecimal.ZERO // ✅ 使用 BigDecimal
|
||
)
|
||
```
|
||
|
||
**规则**:
|
||
- ID 字段必须使用 `Long? = null`
|
||
- 时间字段使用 `Long` 时间戳(毫秒)
|
||
- 数值字段使用 `BigDecimal`
|
||
- **禁止**使用 `LocalDateTime` 或 `Double`
|
||
|
||
## 配置文件规范
|
||
- **必须**使用 `application.properties` 格式(禁止 `application.yml`)
|
||
- 使用 `${ENV_VAR:default}` 引用环境变量
|
||
|
||
## 代码规范
|
||
|
||
### Controller
|
||
- **禁止**使用 `suspend`
|
||
- 使用 `runBlocking` 调用 suspend 方法
|
||
- 统一使用 `@PostMapping`
|
||
|
||
### Service
|
||
- 可以使用 `suspend` 方法
|
||
- 使用 `@Transactional` 管理事务
|
||
- 使用构造函数注入依赖
|
||
|
||
### Repository
|
||
- 继承 `JpaRepository<Entity, Long>`
|
||
- 使用 Spring Data JPA 方法命名规范
|
||
|
||
## API 接口规范
|
||
|
||
### 统一响应格式
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {},
|
||
"msg": ""
|
||
}
|
||
```
|
||
|
||
### 错误码范围
|
||
- `0`: 成功
|
||
- `1001-1999`: 参数错误
|
||
- `2001-2999`: 认证/权限错误
|
||
- `3001-3999`: 资源不存在
|
||
- `4001-4999`: 业务逻辑错误
|
||
- `5001-5999`: 服务器内部错误
|
||
|
||
### Controller 示例
|
||
```kotlin
|
||
@RestController
|
||
@RequestMapping("/api/example")
|
||
class ExampleController(
|
||
private val exampleService: ExampleService,
|
||
private val messageSource: MessageSource
|
||
) {
|
||
@PostMapping("/list")
|
||
fun getList(@RequestBody request: ExampleListRequest): ResponseEntity<ApiResponse<ExampleListResponse>> {
|
||
return try {
|
||
val data = runBlocking { exampleService.getList(request) }
|
||
ResponseEntity.ok(ApiResponse.success(data))
|
||
} catch (e: Exception) {
|
||
logger.error("Failed to get list", e)
|
||
ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, messageSource))
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 数值计算
|
||
```kotlin
|
||
val amount = "0.5".toSafeBigDecimal()
|
||
val total = amount.add("0.4".toSafeBigDecimal())
|
||
|
||
if (total.lt(BigDecimal.ONE)) {
|
||
// 业务逻辑
|
||
}
|
||
```
|
||
|
||
## JSON 解析规范
|
||
|
||
### 使用扩展函数(推荐)
|
||
- **优先**使用扩展函数 `fromJson<T>()` 和 `toJson()`,禁止直接使用 `Gson`
|
||
- 扩展函数提供了类型安全的解析,使用方便
|
||
|
||
```kotlin
|
||
// ✅ 正确:使用扩展函数(推荐)
|
||
val json = "{\"name\":\"test\"}"
|
||
val obj = json.fromJson<MyDataClass>()
|
||
val jsonStr = obj.toJson()
|
||
|
||
// ✅ 也可以:使用 JsonUtils 类(兼容旧代码)
|
||
val obj = jsonUtils.fromJson<MyDataClass>(json)
|
||
|
||
// ❌ 错误:直接使用 Gson
|
||
val obj = gson.fromJson(json, MyDataClass::class.java)
|
||
```
|
||
|
||
### 扩展函数列表
|
||
- `String?.fromJson<T>()` - 解析 JSON 字符串为对象(支持泛型)
|
||
- `JsonElement?.fromJson<T>()` - 解析 JsonElement 为对象(支持泛型)
|
||
- `Any?.toJson()` - 将对象转换为 JSON 字符串
|
||
- `String?.parseStringArray()` - 解析 JSON 字符串数组
|
||
|
||
### JsonUtils 类
|
||
- `JsonUtils` 主要用于初始化全局 Gson 实例,供扩展函数使用
|
||
- 保留 `parseStringArray()` 方法用于兼容旧代码
|
||
- 不推荐直接使用 `JsonUtils` 的方法,优先使用扩展函数
|
||
|
||
### Data Class 规范
|
||
- **所有 data class 字段必须提供默认值**
|
||
- 可空字段使用 `? = null`
|
||
- 非空字段提供合适的默认值(空字符串、空集合、默认数值等)
|
||
|
||
```kotlin
|
||
// ✅ 正确:所有字段都有默认值
|
||
data class MyDto(
|
||
val name: String = "",
|
||
val age: Int = 0,
|
||
val tags: List<String> = emptyList(),
|
||
val optional: String? = null
|
||
)
|
||
|
||
// ❌ 错误:缺少默认值
|
||
data class MyDto(
|
||
val name: String, // 缺少默认值
|
||
val age: Int = 0
|
||
)
|
||
```
|
||
|
||
## Side 判断规范
|
||
**禁止**使用 "YES"/"NO" 字符串判断 side
|
||
|
||
```kotlin
|
||
// ❌ 错误
|
||
if (side != null && side.uppercase() == "NO") { }
|
||
|
||
// ✅ 正确
|
||
if (outcomeIndex != null && outcomeIndex == 1) { }
|
||
```
|
||
|
||
## 多语言规范
|
||
- **禁止**硬编码中文或英文错误消息
|
||
- **必须**使用 `ErrorCode` 枚举和 `MessageSource`
|
||
|
||
```kotlin
|
||
// ❌ 错误
|
||
return ResponseEntity.ok(ApiResponse.paramError("配置ID不能为空"))
|
||
|
||
// ✅ 正确
|
||
return ResponseEntity.ok(ApiResponse.error(ErrorCode.PARAM_EMPTY, messageSource))
|
||
```
|
||
|
||
### 多语言支持范围
|
||
1. **API 响应消息**: 使用 `ErrorCode` + `MessageSource`
|
||
2. **日志消息**: 可使用中文或英文
|
||
3. **代码注释**: 建议使用中文
|
||
4. **数据库字段**: 使用英文(snake_case)
|
||
|
||
## HTTP 客户端
|
||
- 使用 Retrofit + OkHttp
|
||
- 使用拦截器处理认证
|
||
|
||
## 跟单系统需求
|
||
参考文档: `docs/copy-trading-requirements.md`
|
||
|
||
核心功能:
|
||
- 账户管理(私钥导入,多账户)
|
||
- Leader 管理
|
||
- 订单同步与执行
|
||
- 跟单配置管理
|
||
- 风险控制
|
||
- 跟单记录与统计
|
||
|
||
## 禁止事项
|
||
|
||
### 代码质量
|
||
- ❌ 禁止使用 `!!` 除非有明确原因
|
||
- ❌ 禁止忽略异常
|
||
- ❌ 禁止硬编码配置值
|
||
- ❌ 禁止提交敏感信息到 Git
|
||
|
||
### 类型
|
||
- ❌ 禁止使用 `Double` 进行数值计算
|
||
- ❌ 禁止使用 `LocalDateTime` 存储时间
|
||
- ❌ 禁止实体类 ID 使用非空默认值
|
||
- ❌ 禁止直接使用 `Gson`(必须使用 `JsonUtils`)
|
||
- ❌ 禁止 data class 字段缺少默认值
|
||
|
||
### API 接口
|
||
- ❌ 禁止使用 GET/PUT/DELETE(统一使用 POST)
|
||
- ❌ 禁止返回不符合统一格式的响应
|
||
- ❌ 禁止在响应中直接返回 Map 类型
|
||
|
||
### Side 判断
|
||
- ❌ 禁止使用 "YES"/"NO" 字符串判断 side
|
||
- ✅ 必须使用 `outcomeIndex` 判断
|
||
|
||
### 数据源
|
||
- ❌ 禁止直接返回 mock 数据
|
||
- ❌ 禁止在 API 调用失败时返回 mock 数据
|
||
- ✅ 所有数据必须来自真实 API 或数据库查询
|