Such Music

最佳实践

插件开发中的错误处理、状态管理、路径兼容等实践指南

最佳实践

错误处理

所有异步操作都应使用 try/catch 包裹,并通过 window.notify 向用户反馈:

async riskyOperation() {
  try {
    this.setProps({ statusText: '处理中...' })
    const result = await suchmusic.execProgram('tool', ['--input', 'file.ncm'])
    this.setProps({ statusText: '完成' })
    window.notify('处理成功', 'success')
    return { code: true, data: result }
  } catch (err) {
    this.setProps({ statusText: '失败' })
    window.notify('处理失败: ' + (err.message || '未知错误'), 'error')
    return { code: false, message: err.message }
  }
}

⚠️ 不要在插件方法中让未捕获的错误传播到宿主层。宿主会显示通用错误提示,用户体验差。

异步操作中的状态管理

长时间操作应使用 setProps 实时更新 UI 状态:

async processBatch(files: string[]) {
  const total = files.length
  this.setProps({ statusText: '处理中 0/' + total })

  for (let i = 0; i < files.length; i++) {
    await processFile(files[i])
    this.setProps({ statusText: '处理中 ' + (i + 1) + '/' + total })
  }

  this.setProps({ statusText: '完成: ' + total + ' 个文件' })
}

日志记录

养成良好的日志习惯,方便用户和开发者排查问题:

// ✅ 好的日志 — 有关键信息和步骤
console.log('开始获取歌单详情, playlistId=' + playlistId)
console.log('歌单名称: ' + playlistInfo.name + ', 共 ' + trackCount + ' 首')

// ❌ 差的日志 — 信息不足
console.log('ok')
console.log('done')

对于需要展示给用户的日志,使用 log 类型的 UIField + setProps 追加内容:

_log(msg: string) {
  this._logLines.push('[' + new Date().toLocaleTimeString() + '] ' + msg)
  if (this._logLines.length > 200) {
    this._logLines = this._logLines.slice(-100)
  }
  this.setProps({ importLog: this._logLines.join('\n') })
}

ℹ️ 建议限制日志行数(如最多保留 200 行),避免内存无限增长。

文件路径处理的跨平台兼容

Such 运行在 Windows / macOS / Linux 上,处理文件路径时注意:

// ✅ 使用 suchmusic.getSetting('musicFolder') 获取音乐目录
//    不要硬编码路径
const musicFolder = suchmusic.getSetting('musicFolder')

// ✅ 路径拼接使用宿主提供的目录分隔符
//    在 Windows 上 getSetting 返回的路径已使用反斜杠
const destPath = musicFolder + '\\myfile.mp3'

// ⚠️ 如果从网络获取路径,注意转换分隔符
const normalizedPath = remotePath.replace(/\//g, '\\')

权限最小化声明

只声明插件实际需要的权限,避免引起用户安全顾虑:

// ✅ 歌单导入插件只需要 fetch 和 playlist
permissions: ['fetch', 'playlist']

// ✅ 文件处理插件只需要 file_system
permissions: ['file_system']

状态初始化

initialization() 中初始化所有 UI 状态:

initialization() {
  this.setProps({
    // 输入框默认值
    inputPath: '',
    // 状态标签
    statusText: '就绪',
    // 日志
    outputLog: '',
    // 从宿主读取的配置
    musicDir: suchmusic.getSetting('musicFolder')
  })
}

方法返回值

所有供 UI 按钮调用的方法(uiSchema.actions[].method)建议返回统一格式:

{
  code: boolean,     // 是否成功
  message?: string,  // 错误信息(失败时)
  // ... 其他业务数据
}

这便于 UI 层根据返回值做出不同展示。

版本管理

实现 checkUpdate 方法时,建议:

  • 通过 GitHub Releases API 或其他稳定接口获取最新版本
  • 使用语义化版本比较(不要简单字符串比较)
  • 网络异常时返回 null 而非抛出错误
async checkUpdate() {
  try {
    const resp = await suchmusic.fetch(
      'https://api.github.com/repos/yourname/your-plugin/releases/latest'
    )
    const release = await resp.json()
    const latestVer = release.tag_name.replace(/^v/, '')

    if (compareVersions(latestVer, this.version) > 0) {
      return {
        hasNew: true,
        version: latestVer,
        url: release.html_url,
        changelog: release.body || ''
      }
    }
    return { hasNew: false, version: '', url: '', changelog: '' }
  } catch {
    return null // 检查失败
  }
}

On this page