开发常见坑
1. New 返回 no adapter registered for platform
现象
text
yatori: MYPLATFORM: new: no adapter registered for platform原因
- 忘记在 adapter 里写
init() { core.Register(...) }; - 忘记在
internal/driver/driver.go空导入; - 平台常量不在
platformDescriptions里。
修复
go
func init() {
core.Register(core.MyPlatform, New)
}go
import _ "github.com/yatori-dev/yatori-go-core/internal/driver/myplatform"2. New 返回 unsupported platform
原因
core/platform.go 的 platformDescriptions 没有加新平台。
修复
go
var platformDescriptions = map[Platform]string{
MyPlatform: "我的平台",
}3. Courses 返回 ErrUnsupported
原因
Capabilities()没有设置CapCourses;- 方法没有实现,直接返回
core.Unsupported。
修复
go
func (c *client) Capabilities() core.Capability {
return core.CapLogin | core.CapCourses
}并实现 Courses。
4. Courses 返回 ErrNotLoggedIn
原因
- 没有调用
Login; Login没有设置loggedIn = true;Login失败但被忽略。
修复
go
if err := client.Login(ctx); err != nil {
return err
}并在 adapter 的 Login 成功后设置:
go
c.loggedIn = true5. 死锁
现象
调用 Courses 一直卡住。
原因
公开方法里又调用了另一个公开方法:
go
func (c *client) Courses(ctx) {
unlock := c.Lock()
defer unlock()
c.Tree(ctx, ...) // Tree 也会 Lock
}修复
拆成 xxxLocked:
go
func (c *client) Courses(ctx) {
unlock := c.Lock()
defer unlock()
return c.coursesLocked(ctx)
}6. Cookie 被覆盖 / 数据竞争
原因
没有加锁,多个 goroutine 同时修改 cache.cookies。
修复
所有公开方法加锁:
go
unlock := c.Lock()
defer unlock()多账号时,每个账号一个 Client,不要共享。
7. Tree 返回空 children
可能原因
- 章节未开放;
- 章节下确实没有任务点;
- 单个章节请求失败,被
continue跳过; - 平台只返回扁平节点。
修复
- 记录
c.Logger.Debugf; - 确认平台接口返回;
- 如果是锁定章节,属于正常情况;
- 如果是失败,修错误处理。
8. Study 报 node raw data is missing
原因
Study 只拿到 nodeId,但 Tree 没有被调用,或者没有把节点存入 c.nodes。
修复
- 先调用
Tree; - 在
Tree里存:
go
c.nodes[node.ID] = nodeStudy里回查:
go
if stored, ok := c.nodes[req.Node.ID]; ok {
raw = stored.Raw
}9. DoWork 报 work raw data is missing
原因
- 没有先调用
Works; Works没有把作业存入c.works;Work.Raw类型和DoWork断言类型不一致。
修复
go
// Works
c.works[item.ID] = item
out = append(out, core.Work{..., Raw: item})
// DoWork
work, ok := req.Work.Raw.(*agg.Work)
if !ok || work == nil {
work = c.works[req.Work.ID]
}统一使用值或指针。
10. Progress 显示成 0.5%
原因
平台是 0–1,适配器没有乘 100。
修复
go
Progress: item.Progress * 100统一 0–100。
11. Duration 显示成几毫秒
原因
把秒当成 time.Duration 的纳秒。
修复
go
Duration: driverutil.Seconds(item.Duration)或:
go
Duration: time.Duration(item.DurationMillis) * time.Millisecond12. 节点 ID 冲突
原因
- 用课程名/节点名做 ID;
- 用切片索引做 ID;
- 组合 ID 缺少关键字段。
修复
go
ID: fmt.Sprintf("video:%d:%d:%s", knowledgeID, cardIndex, objectID)保证稳定、唯一、不透明。
13. Raw 存数据库后无法使用
原因
Raw 是平台对象,不是可序列化数据。
修复
持久化只保存 Extra:
go
type SavedCourse struct {
ID string `json:"id"`
Name string `json:"name"`
Extra map[string]string `json:"extra"`
}恢复时:
go
core.Course{ID: saved.ID, Name: saved.Name, Extra: saved.Extra}14. 进程被 log.Fatal 杀掉
原因
旧代码或适配器里调用了 log.Fatal / os.Exit。
修复
改成返回错误:
go
if err != nil {
return nil, core.Wrap(core.MyPlatform, "courses", err)
}新代码禁止 log.Fatal / os.Exit / panic。
15. New 很慢 / 启动就写 assets
原因
在 New 里调用了 driverutil.EnsureOCR() 或 utils.YatoriCoreInit()。
修复
移到 Login:
go
func (c *client) Login(ctx context.Context) error {
unlock := c.Lock()
defer unlock()
driverutil.EnsureOCR()
// ...
}16. 平台常量加了,但用户写 yatori.MyPlatform 编译失败
原因
只加了 core.MyPlatform,没有加根包别名。
修复
types.go:
go
const MyPlatform = core.MyPlatform17. Call 参数解析失败
原因
- 参数名写错;
position不是合法时长;courseId/nodeId没有传;- 只传 ID,但缓存里没有(没有先调用
Courses/Tree)。
修复
go
client.Call(ctx, yatori.ActionStudy, map[string]any{
"courseId": course.ID,
"nodeId": node.ID,
"position": "60s",
"mode": "normal",
})先 Courses → Tree,或直接传完整 Course/Node。
18. 能力位设置了,但方法返回 ErrUnsupported
原因
能力位和方法实现不一致。
修复
- 要么实现方法;
- 要么去掉能力位;
- 部分支持时,文档写清楚。
19. 测试需要真实账号
原因
测试直接调用 Login,没有跳过开关。
修复
go
if os.Getenv("YATORI_LIVE") != "1" {
t.Skip("set YATORI_LIVE=1 to run")
}CI 默认不跑真实账号测试。
20. 文档没更新
现象
代码合了,但用户不知道新平台怎么填账号。
修复
更新:
README.md能力表;yatori-go-core/docs.md平台表;v3/platforms.md账号参数;v3/capabilities.md能力矩阵。
21. go vet 报错
常见
- 复制了包含
sync.Mutex的结构体; - 格式化字符串参数不匹配;
- 未使用的变量。
修复
bash
go vet ./...按提示修,不要忽略。
22. 平台接口变了
步骤
- 先复现;
- 抓请求/响应;
- 改
internal/api; - 改
internal/aggregation; - 改 adapter 映射;
- 补测试;
- 更新文档。
不要直接改 adapter 绕过旧层,否则会越来越乱。