更新记录
1.0.0(2026-08-01)
下载此版本
平台兼容性
uni-app(5.0)
| Vue2 |
Vue2插件版本 |
Vue3 |
Vue3插件版本 |
Chrome |
Safari |
app-vue |
app-vue插件版本 |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
1.0.0 |
√ |
1.0.0 |
× |
× |
√ |
1.0.0 |
- |
√ |
× |
× |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| × |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
uni-app x(5.0)
| Chrome |
Safari |
Android |
Android插件版本 |
iOS |
鸿蒙 |
微信小程序 |
| × |
× |
√ |
1.0.0 |
× |
× |
× |
⬇️ Demo 应用下载
sn-ftp 插件 API 文档
sn-ftp 是 uni-app x 的 FTP/SFTP/FTPS 客户端 UTS 插件,支持连接管理、文件上传下载(带实时进度)、文件夹递归传输、目录操作、文件列表、多编码、日志管理、保存到相册、下载目录等。
兼容性 ?
| Android |
iOS |
HarmonyOS |
Web |
小程序 |
| √ |
x |
x |
x |
x |
错误规范:遵循 uni 错误规范,所有异步 API 通过 UniError 返回结果,errCode = 0 表示成功,非 0 表示失败(7 位规范码)。详见 错误码。
目录
connect
连接 FTP / SFTP / FTPS 服务器。异步执行,在新线程完成连接后通过 callback 返回结果。若当前已存在连接,会先自动断开再建立新连接。
SFTP 连接默认启用 keepalive(每 5 秒心跳),避免用户选择文件夹等长时间无通信场景下被服务器 idle 超时断开;session 意外断开后,传输任务会自动使用保存的连接参数重连。
参数
类型
FtpConnectOptions
| 名称 |
类型 |
必备 |
默认值 |
描述 |
| host |
string |
是 |
|
服务器主机地址(IP 或域名),不可为空 |
| port |
number |
是 |
|
服务器端口,FTP 默认 21,SFTP 默认 22 |
| username |
string |
是 |
|
登录用户名,匿名登录填 "anonymous" |
| password |
string |
否 |
"" |
登录密码;匿名登录填任意字符串(如 "anonymous");SFTP key 模式可填空字符串 |
| type |
FtpType |
是 |
|
连接类型:"ftp" / "sftp" / "ftps" |
| mode |
FtpMode |
是 |
|
传输模式:"active" 或 "passive"(仅 FTP/FTPS 有效,SFTP 忽略) |
| encoding |
FtpEncoding |
是 |
|
编码格式:"UTF-8" / "GBK" / "GB2312" / "ISO-8859-1" |
| timeout |
number |
是 |
30000 |
连接超时时间(毫秒) |
| implicit |
boolean | null |
条件必填 |
null |
是否使用 implicit 模式。type == "ftps" 时必填:false=explicit(端口 21 + AUTH TLS 升级),true=implicit(端口 990 隐式加密);type 为 "ftp" / "sftp" 时忽略(填 null) |
| authMethod |
FtpAuthMethod | null |
条件必填 |
null |
SFTP 认证方式。type == "sftp" 时必填:"password" 密码登录 / "key" 秘钥登录;type 为 "ftp" / "ftps" 时忽略(填 null) |
| privateKeyPath |
string | null |
条件必填 |
null |
秘钥文件路径或 content:// URI。type == "sftp" 且 authMethod == "key" 时必填;其它情况填 null |
| passphrase |
string |
否 |
"" |
秘钥密码。仅当私钥本身设置了密码时填写;私钥未加密时填空字符串 "" |
connect 错误码
| 合法值 |
描述 |
| 9001701 |
参数无效(host 为空、ftps 未填 implicit、sftp 未填 authMethod 等) |
| 9001702 |
网络错误(连接失败、超时、SSL 握手失败) |
| 9001703 |
认证失败(密码错误、公钥被服务器拒绝) |
| 9001707 |
协议错误(FTP 服务器拒绝连接) |
| 9001712 |
秘钥错误(Ed25519 不支持、加密秘钥未提供 passphrase、文件格式错误) |
示例
FTP 匿名登录:
import { connect } from '@/uni_modules/sn-ftp'
connect({
host: '192.168.1.100',
port: 21,
username: 'anonymous',
password: 'anonymous',
type: 'ftp',
mode: 'passive',
encoding: 'UTF-8',
timeout: 15000,
implicit: null,
authMethod: null,
privateKeyPath: null,
passphrase: ''
}, (err) => {
if (err.errCode == 0) {
console.log('连接成功')
} else {
console.error('连接失败:' + err.errMsg)
}
})
SFTP 秘钥登录:
import { connect } from '@/uni_modules/sn-ftp'
connect({
host: '192.168.1.100',
port: 22,
username: 'root',
password: '',
type: 'sftp',
mode: 'passive',
encoding: 'UTF-8',
timeout: 15000,
implicit: null,
authMethod: 'key',
privateKeyPath: '/storage/emulated/0/id_rsa',
passphrase: ''
}, (err) => {
console.log(err.errCode == 0 ? '连接成功' : '连接失败:' + err.errMsg)
})
disconnect
断开当前连接。异步执行,释放 FTP/SFTP 会话与通道资源。断开前会取消所有运行中的传输任务。
参数
disconnect 错误码
示例
import { disconnect } from '@/uni_modules/sn-ftp'
disconnect((err) => {
console.log(err.errCode == 0 ? '已断开连接' : '断开失败:' + err.errMsg)
})
upload
上传本地文件或文件夹到远端服务器。异步执行,传输过程在后台线程进行,按指定间隔触发进度回调。传输完成后触发一次最终进度回调(100%)。
每次上传会创建独立任务(UUID),可通过 pauseTask / resumeTask / cancelTask 控制。
插件自动检测 localPath 类型并分发处理:
- 单文件上传(支持断点续传):
localPath 为文件系统路径或 content:// 文件 URI
- 文件夹上传(递归,仅支持暂停/取消,不支持断点续传):
localPath 为 content:// 树 URI(来自 chooseFolder)
文件夹上传时,remotePath 为远端目标目录路径(插件自动递归创建);进度为整体进度(所有文件累计字节数 / 总字节数)。
参数
返回值
类型
FtpTransferOptions
上传 / 下载通用的传输参数。
| 名称 |
类型 |
必备 |
默认值 |
描述 |
| localPath |
string |
是 |
|
本地源/目标路径。upload:本地文件路径、content:// 文件 URI 或 content:// 树 URI(文件夹);download:content:// 树 URI(目标目录)或空字符串(默认公共下载)或文件系统路径 |
| remotePath |
string |
是 |
|
远端路径(文件或目录的绝对路径) |
| progressInterval |
number | null |
否 |
1000 |
进度回调间隔时间(毫秒),null 时使用默认值 1000,最小 100 |
FtpProgress
传输进度信息(upload / download 的 progressCallback 参数类型)。
| 名称 |
类型 |
必备 |
描述 |
| bytesTransferred |
number |
是 |
已传输字节数 |
| totalBytes |
number |
是 |
总字节数 |
| percent |
number |
是 |
进度百分比(0-100,整数) |
| speed |
number |
是 |
当前速度(字节/秒) |
upload 错误码
| 合法值 |
描述 |
| 9003701 |
参数无效(localPath 或 remotePath 为空) |
| 9003704 |
本地文件/文件夹不存在 |
| 9003706 |
IO 错误(读取/写入失败、连接中断) |
| 9003708 |
状态错误(未连接) |
| 9003709 |
传输被取消(cancelTask 调用) |
示例
单文件上传:
import { upload } from '@/uni_modules/sn-ftp'
const taskId = upload({
localPath: '/sdcard/test.txt',
remotePath: '/var/ftp/upload/test.txt',
progressInterval: 500
}, (err) => {
if (err.errCode == 0) {
console.log('上传完成')
} else {
console.error('上传失败:' + err.errMsg)
}
}, (p) => {
console.log(`进度: ${p.percent}% 速度: ${p.speed} B/s`)
})
// taskId 可用于 pauseTask(taskId, cb) / cancelTask(taskId, cb)
文件夹上传(需配合 chooseFolder):
import { upload, chooseFolder } from '@/uni_modules/sn-ftp'
import type { ChooseFolderResult } from '@/uni_modules/sn-ftp'
chooseFolder((err) => {
if (err.errCode != 0) {
console.error('选择文件夹失败:' + err.errMsg)
return
}
const result = err.data as ChooseFolderResult // { uri, name }
upload({
localPath: result.uri, // content:// 树 URI,插件自动识别为文件夹并递归上传
remotePath: '/var/ftp/upload/' + result.name,
progressInterval: 500
}, (upErr) => {
console.log(upErr.errCode == 0 ? '文件夹上传完成' : '文件夹上传失败:' + upErr.errMsg)
}, (p) => {
console.log(`整体进度: ${p.percent}%`)
})
})
download
从远端服务器下载文件或文件夹到本地。异步执行,传输过程在后台线程进行,按指定间隔触发进度回调。传输完成后触发一次最终进度回调(100%)。
每次下载会创建独立任务(UUID),可通过 pauseTask / resumeTask / cancelTask 控制。
插件自动检测 remotePath 类型并分发处理:
- 单文件下载(
localPath 为文件系统路径时支持断点续传):remotePath 为远端文件路径
- 文件夹下载(递归,仅支持暂停/取消,不支持断点续传):
remotePath 为远端目录路径
localPath 目标模式(自动检测):
| localPath 传入 |
行为 |
content:// 树 URI(来自 chooseFolder) |
目标目录:源目录名/文件名作为子项保存到其下(文件夹 B → A/B/...) |
空字符串 "" |
默认保存到公共"下载"目录 Download/sn_ftp/<源名>(MediaStore 实现,无需任何权限,文件管理器可见) |
| 文件系统路径 |
现有行为(应用沙盒内,支持断点续传) |
文件夹下载时,进度为整体进度(所有文件累计字节数 / 总字节数)。成功后 callback 的 data 为实际保存位置字符串。
参数
返回值
download 错误码
| 合法值 |
描述 |
| 9004701 |
参数无效(localPath 或 remotePath 为空) |
| 9004704 |
远端文件/文件夹不存在 |
| 9004706 |
IO 错误(读取/写入失败、连接中断) |
| 9004708 |
状态错误(未连接) |
| 9004709 |
传输被取消(cancelTask 调用) |
示例
单文件下载:
import { download } from '@/uni_modules/sn-ftp'
const taskId = download({
localPath: '/sdcard/download/test.txt',
remotePath: '/var/ftp/pub/test.txt',
progressInterval: 500
}, (err) => {
if (err.errCode == 0) {
console.log('下载完成')
} else {
console.error('下载失败:' + err.errMsg)
}
}, (p) => {
console.log(`已下载 ${p.bytesTransferred}/${p.totalBytes} (${p.percent}%)`)
})
文件夹下载到用户选择的目录(文件夹 B 保存到所选目录 A 下,即 A/B/...):
import { download, chooseFolder } from '@/uni_modules/sn-ftp'
import type { ChooseFolderResult } from '@/uni_modules/sn-ftp'
chooseFolder((err) => {
if (err.errCode != 0) {
console.error('选择保存位置失败:' + err.errMsg)
return
}
const result = err.data as ChooseFolderResult // { uri, name }
download({
localPath: result.uri, // content:// 树 URI:目标目录,文件夹保存到其下
remotePath: '/var/ftp/pub/photos', // 远端源目录
progressInterval: 500
}, (dlErr) => {
if (dlErr.errCode == 0) {
console.log('文件夹下载完成,保存位置: ' + dlErr.data)
} else {
console.error('文件夹下载失败:' + dlErr.errMsg)
}
}, (p) => {
console.log(`整体进度: ${p.percent}%`)
})
})
文件夹下载到默认公共目录(localPath 传空字符串,保存到 Download/sn_ftp/<源目录名>):
import { download } from '@/uni_modules/sn-ftp'
const taskId = download({
localPath: '', // 空字符串 = 默认公共下载
remotePath: '/var/ftp/pub/photos', // 远端源目录
progressInterval: 500
}, (err) => {
if (err.errCode == 0) {
console.log('文件夹下载完成,保存位置: ' + err.data)
} else {
console.error('文件夹下载失败:' + err.errMsg)
}
}, (p) => {
console.log(`整体进度: ${p.percent}%`)
})
chooseFolder
调用 Android 系统文件夹选择器(ACTION_OPEN_DOCUMENT_TREE),让用户选择一个本地文件夹,返回 content:// 树 URI 及文件夹名。
返回的 uri 可直接作为 upload / download 的 localPath 参数:
- upload:插件自动识别为文件夹并递归上传
- download:插件自动识别为目标目录,源目录名/文件名作为子项保存到其下
权限通过 takePersistableUriPermission 持久化,确保后续访问有效(即使应用重启)。无需在 AndroidManifest.xml 声明额外权限,系统文件夹选择器走 Storage Access Framework,不需要运行时权限申请。
参数
类型
ChooseFolderResult
| 名称 |
类型 |
必备 |
描述 |
| uri |
string |
是 |
文件夹的 content:// 树 URI 字符串,可作为 upload / download 的 localPath |
| name |
string |
是 |
文件夹名称(用于构造远端目标路径) |
chooseFolder 错误码
| 合法值 |
描述 |
| 9022704 |
URI 为空(系统选择器异常) |
| 9022705 |
权限获取失败(takePersistableUriPermission 异常) |
| 9022708 |
状态错误(Activity 为空) |
| 9022709 |
用户取消选择 |
示例
import { chooseFolder } from '@/uni_modules/sn-ftp'
import type { ChooseFolderResult } from '@/uni_modules/sn-ftp'
chooseFolder((err) => {
if (err.errCode == 0) {
const result = err.data as ChooseFolderResult
console.log('选中文件夹: ' + result.name)
console.log('URI: ' + result.uri)
// 将 result.uri 作为 upload / download 的 localPath
} else if (err.errCode != 9022709) {
// 9022709 是用户取消,不提示
console.error('选择文件夹失败:' + err.errMsg)
}
})
mkdir
在远端服务器创建目录。若目录已存在或父目录不存在,操作会失败。
参数
| 名称 |
类型 |
必填 |
描述 |
| path |
string |
是 |
目录绝对路径 |
| callback |
FtpCallback |
是 |
结果回调 |
mkdir 错误码
| 合法值 |
描述 |
| 9005701 |
参数无效(path 为空) |
| 9005706 |
IO 错误(创建失败) |
| 9005708 |
状态错误(未连接) |
示例
import { mkdir } from '@/uni_modules/sn-ftp'
mkdir('/var/ftp/newdir', (err) => {
console.log(err.errCode == 0 ? '目录已创建' : err.errMsg)
})
rmdir
删除远端目录。仅能删除空目录,若目录非空会失败。
参数
| 名称 |
类型 |
必填 |
描述 |
| path |
string |
是 |
目录绝对路径(仅能删除空目录) |
| callback |
FtpCallback |
是 |
结果回调 |
rmdir 错误码
| 合法值 |
描述 |
| 9006701 |
参数无效(path 为空) |
| 9006704 |
目录不存在 |
| 9006706 |
IO 错误(删除失败) |
| 9006708 |
状态错误(未连接) |
示例
import { rmdir } from '@/uni_modules/sn-ftp'
rmdir('/var/ftp/newdir', (err) => {
console.log(err.errCode == 0 ? '目录已删除' : err.errMsg)
})
deleteFile
删除远端文件。
参数
| 名称 |
类型 |
必填 |
描述 |
| path |
string |
是 |
文件绝对路径 |
| callback |
FtpCallback |
是 |
结果回调 |
deleteFile 错误码
| 合法值 |
描述 |
| 9007701 |
参数无效(path 为空) |
| 9007704 |
文件不存在 |
| 9007706 |
IO 错误(删除失败) |
| 9007708 |
状态错误(未连接) |
示例
import { deleteFile } from '@/uni_modules/sn-ftp'
deleteFile('/var/ftp/pub/old.txt', (err) => {
console.log(err.errCode == 0 ? '文件已删除' : err.errMsg)
})
cd
切换远端工作目录。
参数
| 名称 |
类型 |
必填 |
描述 |
| path |
string |
是 |
目标目录绝对路径 |
| callback |
FtpCallback |
是 |
结果回调 |
cd 错误码
| 合法值 |
描述 |
| 9008701 |
参数无效(path 为空) |
| 9008704 |
目录不存在 |
| 9008706 |
IO 错误(切换失败) |
| 9008708 |
状态错误(未连接) |
示例
import { cd } from '@/uni_modules/sn-ftp'
cd('/var/ftp/pub', (err) => {
console.log(err.errCode == 0 ? '已切换目录' : err.errMsg)
})
list
获取远端目录下的文件列表(全量返回)。成功时 data 为 FtpFileInfo[]。
参数
类型
FtpFileInfo
远端文件信息条目(list 返回的数组元素,页面展示时也使用)。
| 名称 |
类型 |
必备 |
描述 |
| name |
string |
是 |
文件/目录名 |
| size |
number |
是 |
字节数,目录固定为 0 |
| isDirectory |
boolean |
是 |
是否为目录 |
| modifiedTime |
string |
是 |
最后修改时间,格式 yyyy-MM-dd HH:mm:ss |
| permissions |
string |
是 |
权限字符串,例如 rw-r--r-- |
| path |
string |
是 |
所在目录路径 |
list 错误码
| 合法值 |
描述 |
| 9009701 |
参数无效(path 为空) |
| 9009706 |
IO 错误(列目录失败) |
| 9009708 |
状态错误(未连接) |
示例
import { list } from '@/uni_modules/sn-ftp'
import type { FtpFileInfo } from '@/uni_modules/sn-ftp'
list('/var/ftp/pub', (err) => {
if (err.errCode == 0) {
const files = err.data as FtpFileInfo[]
files.forEach((f) => {
console.log(`${f.isDirectory ? '[D]' : '[F]'} ${f.name} ${f.size}`)
})
} else {
console.error(err.errMsg)
}
})
注:插件仅提供全量列表接口。如需分页(大目录优化),请在业务层基于全量列表做本地切片(参考项目内 hooks/useFtpFileList.uts)。
logText
插件运行日志的只读响应式变量,通过 usePluginLog 组合式函数获取。数据源为 sn-ftp 插件内部日志缓冲区,每 300ms 自动同步,前端绑定到 <text> 组件即可实时显示,无需手动刷新。
特性
- 只读:返回
readonly ref,外部代码不可修改,仅由插件内部日志驱动
- 响应式:
setInterval 定时从插件同步,自动触发 UI 更新
- 纯插件日志:仅包含插件层日志(连接、传输、错误等),不含应用层业务消息
- 格式:按行拼接的纯文本,每行格式为
[yyyy-MM-dd HH:mm:ss.SSS] 消息内容
用法
import { usePluginLog } from '@/hooks/usePluginLog.uts'
const logText = usePluginLog(300) // 300ms 刷新间隔
// 绑定到模板:<text>{{ logText }}</text>
示例
<template>
<scroll-view direction="vertical" :scroll-top="scrollTop">
<text>{{ logText }}</text>
</scroll-view>
</template>
<script lang="uts" setup>
import { ref, watch } from 'vue'
import { usePluginLog } from '@/hooks/usePluginLog.uts'
const scrollTop = ref(0)
const logText = usePluginLog(300)
// 日志变化时自动滚动到底部
watch(logText, () => {
setTimeout(() => { scrollTop.value += 10000 }, 50)
})
</script>
注:日志内容由插件内部维护(连接、传输、错误等),应用层不应向日志写入业务消息。如需清空日志,调用 clearLogs。
getLogTextSync
同步获取当前日志文本(按行拼接)。供 usePluginLog 组合式函数定时轮询使用,通常不需要直接调用。
返回值
| 类型 |
描述 |
| string |
日志文本(按行拼接,无条数限制) |
示例
import { getLogTextSync } from '@/uni_modules/sn-ftp'
const text = getLogTextSync()
console.log(text)
getLogs
获取插件运行日志。日志由插件内部记录,包含连接、传输、错误等关键事件。
参数
| 名称 |
类型 |
必填 |
描述 |
| callback |
FtpCallback |
是 |
结果回调,成功时 data 为按行拼接的 string |
示例
import { getLogs } from '@/uni_modules/sn-ftp'
getLogs((err) => {
if (err.errCode == 0) {
console.log(err.data as string)
} else {
console.error(err.errMsg)
}
})
clearLogs
清空插件内部记录的运行日志。
参数
示例
import { clearLogs } from '@/uni_modules/sn-ftp'
clearLogs((err) => {
console.log(err.errCode == 0 ? '日志已清空' : err.errMsg)
})
clearTempDir
清空临时目录(<cacheDir>/sn_ftp_temp/)下所有文件,释放存储空间。通常用于下载后立即打开的临时文件清理。
参数
clearTempDir 错误码
| 合法值 |
描述 |
| 9013706 |
IO 错误(删除失败) |
示例
import { clearTempDir } from '@/uni_modules/sn-ftp'
clearTempDir((err) => {
console.log(err.errCode == 0 ? '临时目录已清空' : err.errMsg)
})
saveMediaToGallery
保存图片或视频文件到系统相册。兼容 Android 10+ 使用 MediaStore(无需运行时权限),Android 9 及以下使用传统文件路径(需 WRITE_EXTERNAL_STORAGE 运行时权限)。
参数
| 名称 |
类型 |
必填 |
描述 |
| srcPath |
string |
是 |
源文件路径(应用沙盒内的临时文件) |
| isImage |
boolean |
是 |
true = 图片,false = 视频 |
| callback |
FtpCallback |
是 |
结果回调,成功时 data 为目标 Uri 字符串 |
saveMediaToGallery 错误码
| 合法值 |
描述 |
| 9014701 |
参数无效(srcPath 为空) |
| 9014704 |
源文件不存在 |
| 9014705 |
权限不足(Android 9 及以下) |
| 9014706 |
IO 错误(写入失败) |
示例
import { saveMediaToGallery } from '@/uni_modules/sn-ftp'
saveMediaToGallery('/cache/sn_ftp_temp/photo.jpg', true, (err) => {
if (err.errCode == 0) {
console.log('已保存到相册:' + err.data)
} else {
console.error('保存失败:' + err.errMsg)
}
})
saveFileToDownloads
保存任意类型文件到系统 Download 目录。兼容 Android 10+ 使用 MediaStore.Downloads,Android 9 及以下使用传统文件路径写入公共 Download 目录。
参数
| 名称 |
类型 |
必填 |
描述 |
| srcPath |
string |
是 |
源文件路径 |
| callback |
FtpCallback |
是 |
结果回调,成功时 data 为目标路径字符串 |
saveFileToDownloads 错误码
| 合法值 |
描述 |
| 9015701 |
参数无效(srcPath 为空) |
| 9015704 |
源文件不存在 |
| 9015705 |
权限不足(Android 9 及以下) |
| 9015706 |
IO 错误(写入失败) |
示例
import { saveFileToDownloads } from '@/uni_modules/sn-ftp'
saveFileToDownloads('/cache/sn_ftp_temp/doc.pdf', (err) => {
if (err.errCode == 0) {
console.log('已保存到:' + err.data)
} else {
console.error('保存失败:' + err.errMsg)
}
})
open
使用系统应用打开本地文件。基于 Android 原生 Intent + FileProvider 实现(不再使用 uni.openDocument),能正确处理应用私有目录(cacheDir)中的文件。
文件类型处理策略:
- 视频(mp4/m4v/mkv 等)→ 系统播放器
- 音频(mp3/aac/flac 等)→ 系统音乐播放器
- 图片(jpg/png/gif/webp 等)→ 系统相册
- 文档(pdf/doc/xls/ppt 等)→ 系统文档处理器
- 其他/未知类型 → 系统弹出应用选择器,由用户选择
若精确 MIME 无对应应用,自动降级为 */* 兜底;仍无应用则返回错误。
参数
| 名称 |
类型 |
必填 |
描述 |
| filePath |
string |
是 |
本地文件绝对路径(应用沙盒内或已授权路径) |
| callback |
FtpCallback |
是 |
结果回调,成功时 errCode = 0,data = null |
open 错误码
| 合法值 |
描述 |
| 9016701 |
参数无效(filePath 为空) |
| 9016704 |
文件不存在 |
| 9016706 |
打开失败(IO 异常、FileProvider 配置错误) |
| 9016710 |
无应用可打开此文件类型 |
示例
import { open } from '@/uni_modules/sn-ftp'
open('/cache/sn_ftp_temp/doc.pdf', (err) => {
if (err.errCode == 0) {
console.log('已打开文件')
} else {
console.error('打开失败:' + err.errMsg)
}
})
配合下载使用的典型场景:
import { download, getTempFilePath, open } from '@/uni_modules/sn-ftp'
const localPath = getTempFilePath('report.pdf')
download({
localPath: localPath,
remotePath: '/var/ftp/pub/report.pdf',
progressInterval: null
}, (err) => {
if (err.errCode == 0) {
// 下载完成后直接打开
open(localPath, (openErr) => {
console.log(openErr.errCode == 0 ? '已打开' : openErr.errMsg)
})
}
}, (p) => {
console.log(`下载进度: ${p.percent}%`)
})
pauseTask
暂停传输任务。中断当前传输,记录已传输字节数,任务状态变为 "paused"。仅 "running" 状态的任务可暂停。暂停后可通过 resumeTask 从断点继续。
参数
pauseTask 错误码
| 合法值 |
描述 |
| 9017713 |
任务不存在 |
| 9017714 |
任务不可暂停(非 running 状态) |
示例
import { pauseTask } from '@/uni_modules/sn-ftp'
pauseTask(taskId, (err) => {
if (err.errCode == 0) {
console.log('已暂停')
} else {
console.error('暂停失败:' + err.errMsg)
}
})
resumeTask
继续已暂停的传输任务,从断点续传。仅 "paused" 状态的任务可继续。
- FTP/FTPS:使用
REST 命令设置偏移量后 RETR
- SFTP:使用
channel.get 的 offset 参数
- 本地文件以 append 模式打开,新数据追加到已下载部分之后
续传的最终结果通过原 upload / download 的 callback 返回。
注:文件夹任务(treeUri / mediaStore 目标模式)不支持断点续传,resume 时从头开始整个文件夹传输。
参数
| 名称 |
类型 |
必填 |
描述 |
| taskId |
string |
是 |
任务 ID |
| callback |
FtpCallback |
是 |
结果回调(仅表示续传是否成功启动) |
resumeTask 错误码
| 合法值 |
描述 |
| 9018713 |
任务不存在 |
| 9018715 |
任务不可继续(非 paused 状态) |
| 9018708 |
状态错误(未连接) |
示例
import { resumeTask } from '@/uni_modules/sn-ftp'
resumeTask(taskId, (err) => {
if (err.errCode == 0) {
console.log('已继续传输')
} else {
console.error('继续失败:' + err.errMsg)
}
})
cancelTask
取消传输任务。中断传输并删除本地临时文件(下载时),任务状态变为 "canceled"。可对任意状态的任务执行取消。
参数
| 名称 |
类型 |
必填 |
描述 |
| taskId |
string |
是 |
任务 ID |
| callback |
FtpCallback |
是 |
结果回调 |
cancelTask 错误码
示例
import { cancelTask } from '@/uni_modules/sn-ftp'
cancelTask(taskId, (err) => {
if (err.errCode == 0) {
console.log('已取消任务')
} else {
console.error('取消失败:' + err.errMsg)
}
})
getTaskStatus
查询任务状态(同步)。
参数
| 名称 |
类型 |
必填 |
描述 |
| taskId |
string |
是 |
任务 ID |
返回值
| 类型 |
描述 |
| TaskInfo | null |
任务信息,任务不存在时返回 null |
类型
TaskInfo
任务信息(getTaskStatus 返回值,任务列表也使用)。
| 名称 |
类型 |
必备 |
描述 |
| taskId |
string |
是 |
任务 ID(UUID) |
| kind |
string |
是 |
任务类型:"download" 或 "upload" |
| status |
TaskStatus |
是 |
当前任务状态 |
| bytesTransferred |
number |
是 |
已传输字节数 |
| totalBytes |
number |
是 |
总字节数(未知时为 0) |
| percent |
number |
是 |
进度百分比(0-100) |
| localPath |
string |
是 |
本地文件路径 |
| remotePath |
string |
是 |
远端文件路径 |
| errMsg |
string |
是 |
失败时的错误信息(status == "failed" 时有值) |
示例
import { getTaskStatus } from '@/uni_modules/sn-ftp'
const info = getTaskStatus(taskId)
if (info != null) {
console.log(`状态: ${info.status} 进度: ${info.percent}%`)
} else {
console.log('任务不存在')
}
listTasks
列出所有任务 ID(同步)。
返回值
示例
import { listTasks } from '@/uni_modules/sn-ftp'
const ids = listTasks()
console.log('当前任务数: ' + ids.length)
getTempDir
获取应用临时目录路径(同步)。用于下载后立即打开的临时文件存放,路径为 <cacheDir>/sn_ftp_temp/。
返回值
| 类型 |
描述 |
| string |
临时目录绝对路径 <cacheDir>/sn_ftp_temp/ |
示例
import { getTempDir } from '@/uni_modules/sn-ftp'
const tempDir = getTempDir()
console.log('临时目录:' + tempDir)
getDownloadDir
获取应用下载目录路径(同步)。用于持久保存的下载文件,路径为 <externalFilesDir>/Downloads/sn_ftp/,应用卸载时清除。
注:该目录位于应用沙盒内,用户无法直接通过文件管理器便捷访问。如需保存到公共"下载"目录,将 download 的 localPath 传空字符串(默认公共下载)或使用 chooseFolder 选择目标目录。
返回值
| 类型 |
描述 |
| string |
下载目录绝对路径 <externalFilesDir>/Downloads/sn_ftp/ |
示例
import { getDownloadDir } from '@/uni_modules/sn-ftp'
const downloadDir = getDownloadDir()
console.log('下载目录:' + downloadDir)
isImageFile
判断文件名是否为图片类型(同步,按扩展名判断)。支持扩展名:jpg/jpeg/png/gif/bmp/webp/heic/heif。
参数
| 名称 |
类型 |
必填 |
描述 |
| filename |
string |
是 |
文件名(含扩展名) |
返回值
示例
import { isImageFile } from '@/uni_modules/sn-ftp'
console.log(isImageFile('photo.jpg')) // true
console.log(isImageFile('doc.pdf')) // false
isVideoFile
判断文件名是否为视频类型(同步,按扩展名判断)。支持扩展名:mp4/avi/mov/wmv/flv/mkv/webm/3gp/m4v。
参数
| 名称 |
类型 |
必填 |
描述 |
| filename |
string |
是 |
文件名(含扩展名) |
返回值
示例
import { isVideoFile } from '@/uni_modules/sn-ftp'
console.log(isVideoFile('movie.mp4')) // true
console.log(isVideoFile('photo.jpg')) // false
isTempFileExists
检查临时目录中是否存在指定文件(同步)。
参数
| 名称 |
类型 |
必填 |
描述 |
| filename |
string |
是 |
文件名 |
返回值
示例
import { isTempFileExists } from '@/uni_modules/sn-ftp'
if (isTempFileExists('cache.txt')) {
console.log('临时文件已存在')
}
getTempFilePath
获取临时目录中指定文件的完整路径(同步)。会自动创建临时目录(若不存在)。
参数
| 名称 |
类型 |
必填 |
描述 |
| filename |
string |
是 |
文件名 |
返回值
示例
import { getTempFilePath } from '@/uni_modules/sn-ftp'
const path = getTempFilePath('cache.txt')
console.log('完整路径:' + path)
通用类型
通用类型用于放置那些并非某函数专用、很多地方都可以用或者可以暴露给用户使用的类型。
FtpType
连接类型枚举。
| 合法值 |
描述 |
"ftp" |
标准 FTP 协议(基于 Apache Commons Net) |
"sftp" |
SFTP 协议(基于 JSch,走 SSH 通道) |
"ftps" |
FTP over SSL/TLS(基于 FTPSClient,支持 explicit/implicit 双模式) |
FtpMode
传输模式枚举。仅 FTP/FTPS 有效,SFTP 忽略。
| 合法值 |
描述 |
"active" |
主动模式,服务器主动连接客户端数据端口 |
"passive" |
被动模式,客户端连接服务器数据端口(默认,穿越 NAT 友好) |
FtpEncoding
编码格式枚举。
| 合法值 |
描述 |
"UTF-8" |
默认推荐 |
"GBK" |
中文服务器常见 |
"GB2312" |
简体中文 |
"ISO-8859-1" |
西欧编码(部分老服务器) |
FtpAuthMethod
SFTP 认证方式枚举。仅 type == "sftp" 时有效。
| 合法值 |
描述 |
"password" |
密码登录(默认) |
"key" |
秘钥登录(需提供 privateKeyPath) |
FtpCallback
通用结果回调类型。遵循 uni 错误规范,所有异步 API 通过 UniError 返回结果。
type FtpCallback = (err: UniError) => void
- 成功:
errCode = 0,data 携带结果数据
- 失败:
errCode != 0,errMsg 为错误描述,cause 为源错误
data 类型说明:
| API |
data 类型 |
| connect / disconnect / mkdir / rmdir / deleteFile / cd / clearLogs / clearTempDir / open |
null |
| upload |
null(进度通过 progressCallback 返回) |
| download |
string(实际保存位置,进度通过 progressCallback 返回) |
| list |
FtpFileInfo[] |
| getLogs |
string(日志文本) |
| saveMediaToGallery / saveFileToDownloads |
string(目标路径) |
| chooseFolder |
ChooseFolderResult |
FtpProgressCallback
传输进度回调类型(upload / download 的 progressCallback 参数)。
type FtpProgressCallback = (progress: FtpProgress) => void
progress 为 FtpProgress 类型。
TaskStatus
任务状态枚举。
| 合法值 |
描述 |
"running" |
传输中 |
"paused" |
已暂停(可通过 resumeTask 继续) |
"completed" |
已完成 |
"failed" |
失败 |
"canceled" |
已取消 |
错误码
7 位错误码格式
格式:90XX7XX = ERR_BASE + ERR_API_XX + ERR_TYPE_XX
- 1-2 位(90):sn-ftp 插件标识
- 3-4 位(XX):API 编号
- 5-7 位(7XX):具体错误(Android 用 7xx)
API 编号
| API 编号 |
API 名称 |
错误码范围 |
| 01 |
connect |
9001700-9001712 |
| 02 |
disconnect |
9002700-9002708 |
| 03 |
upload |
9003700-9003709 |
| 04 |
download |
9004700-9004709 |
| 05 |
mkdir |
9005700-9005708 |
| 06 |
rmdir |
9006700-9006708 |
| 07 |
deleteFile |
9007700-9007708 |
| 08 |
cd |
9008700-9008708 |
| 09 |
list |
9009700-9009708 |
| 11 |
getLogs |
9011700-9011708 |
| 12 |
clearLogs |
9012700-9012708 |
| 13 |
clearTempDir |
9013700-9013708 |
| 14 |
saveMediaToGallery |
9014700-9014708 |
| 15 |
saveFileToDownloads |
9015700-9015708 |
| 16 |
open |
9016700-9016708 |
| 17 |
pauseTask |
9017700-9017715 |
| 18 |
resumeTask |
9018700-9018715 |
| 19 |
cancelTask |
9019700-9019715 |
| 20 |
getTaskStatus |
—(同步,不返回错误码) |
| 21 |
listTasks |
—(同步,不返回错误码) |
| 22 |
chooseFolder |
9022700-9022709 |
错误类型后缀
| 后缀 |
含义 |
| 700 |
通用错误 |
| 701 |
参数无效 |
| 702 |
网络错误(连接失败、超时、断开) |
| 703 |
认证失败 |
| 704 |
文件/目录不存在 |
| 705 |
权限不足 |
| 706 |
IO 错误 |
| 707 |
协议错误 |
| 708 |
状态错误(未连接、已连接等) |
| 709 |
取消/中断 |
| 710 |
不支持的功能 |
| 711 |
路径错误 |
| 712 |
秘钥错误 |
| 713 |
任务不存在 |
| 714 |
任务不可暂停(非 running 状态) |
| 715 |
任务不可继续(非 paused 状态) |
完整错误码表
| errCode |
含义 |
触发场景 |
| 9001701 |
connect 参数无效 |
host 为空、ftps 未填 implicit、sftp 未填 authMethod |
| 9001702 |
connect 网络错误 |
连接失败、超时、SSL 握手失败 |
| 9001703 |
connect 认证失败 |
密码错误、公钥被服务器拒绝 |
| 9001707 |
connect 协议错误 |
FTP 服务器拒绝连接 |
| 9001712 |
connect 秘钥错误 |
Ed25519 不支持、加密秘钥未提供 passphrase、文件格式错误 |
| 9002708 |
disconnect 状态错误 |
未连接 |
| 9003/4701 |
upload/download 参数无效 |
localPath 或 remotePath 为空 |
| 9003/4704 |
upload/download 文件不存在 |
本地/远端文件不存在 |
| 9003/4706 |
upload/download IO 错误 |
读取/写入失败、连接中断 |
| 9003/4708 |
upload/download 状态错误 |
未连接 |
| 9003/4709 |
upload/download 被取消 |
cancelTask 调用或断开连接 |
| 9005-9008 701 |
mkdir/rmdir/cd/delete 参数无效 |
path 为空 |
| 9005-9008 706 |
mkdir/rmdir/cd/delete IO 错误 |
操作失败 |
| 9005-9008 708 |
mkdir/rmdir/cd/delete 状态错误 |
未连接 |
| 9009 701 |
list 参数无效 |
path 为空 |
| 9009 706 |
list IO 错误 |
列目录失败 |
| 9009 708 |
list 状态错误 |
未连接 |
| 9014/15 701 |
saveMedia/saveFile 参数无效 |
srcPath 为空 |
| 9014/15 704 |
saveMedia/saveFile 文件不存在 |
源文件不存在 |
| 9014/15 706 |
saveMedia/saveFile IO 错误 |
写入失败 |
| 9016 701 |
open 参数无效 |
filePath 为空 |
| 9016 704 |
open 文件不存在 |
指定路径的文件不存在 |
| 9016 706 |
open 打开失败 |
IO 异常、FileProvider 配置错误 |
| 9016 710 |
open 无应用可用 |
系统无应用可打开此文件类型 |
| 9017713 |
pauseTask 任务不存在 |
taskId 不在任务 Map 中 |
| 9017714 |
pauseTask 不可暂停 |
任务非 running 状态 |
| 9018713 |
resumeTask 任务不存在 |
taskId 不在任务 Map 中 |
| 9018715 |
resumeTask 不可继续 |
任务非 paused 状态 |
| 9018708 |
resumeTask 状态错误 |
未连接 |
| 9019713 |
cancelTask 任务不存在 |
taskId 不在任务 Map 中 |
| 9022704 |
chooseFolder URI 为空 |
系统选择器返回的 URI 为空 |
| 9022705 |
chooseFolder 权限获取失败 |
takePersistableUriPermission 抛 SecurityException |
| 9022708 |
chooseFolder 状态错误 |
Activity 为空 |
| 9022709 |
chooseFolder 用户取消 |
用户在系统选择器中点击返回/取消 |
注意事项
- 同一时刻只能维持一个连接。再次调用
connect 会先断开旧连接。
- 操作串行化。所有操作入队单线程执行,后入队的操作会等待前一个完成。
- 自动重连。SFTP session 意外断开(服务器 idle 超时、网络波动)后,传输任务会自动使用保存的连接参数重连;连接时默认启用 keepalive(每 5 秒心跳)从源头减少断开。
- 本地路径权限。Android 11+ 对
/sdcard 有 scoped storage 限制,建议使用应用沙盒目录、content:// 树 URI(SAF 授权)或公共目录(MediaStore)。
- 匿名 FTP。
username 填 "anonymous",password 填任意字符串即可(多数服务器不校验)。
- SFTP 跳过主机密钥校验。当前实现设置
StrictHostKeyChecking=no,生产环境若需安全校验请自行扩展。
- FTPS 证书校验。当前使用系统默认信任库严格校验。若服务器使用自签名证书会连接失败,需要自行将证书导入 Android 系统信任库,或在
FtpClient.kt 中扩展 FTPSClient.setTrustManager(...) 跳过校验(仅限调试环境)。
- FTPS 数据通道加密。连接后自动执行
PBSZ 0 + PROT P,保证数据通道也加密。如需明文数据通道(仅控制通道加密),修改 FtpClient.kt 中 execPROT("P") 为 execPROT("C")。
- 进度百分比。下载时若服务器不支持
SIZE 命令,totalBytes 可能为 0,percent 也为 0;上传时 totalBytes 始终使用本地文件大小。
- 文件夹传输。
upload / download 自动检测路径类型并递归处理文件夹:上传文件夹需先用 chooseFolder 获取 content:// 树 URI;文件夹传输仅支持暂停/取消,不支持断点续传,暂停后恢复会从头开始。
- 下载目标路径。
download 的 localPath 支持三种模式:content:// 树 URI(保存到所选目录下)、空字符串(默认公共"下载"目录 Download/sn_ftp/<源名>,MediaStore 实现无需权限)、文件系统路径(应用沙盒内,支持断点续传)。
- 进度回调最小间隔 100ms。设置小于 100 的值会被强制提升到 100;填
null 使用默认值 1000ms。
- 日志无条数限制。插件日志缓冲区不设上限,全部保留在内存中;长时间运行日志量大会占用一定内存,可调用 clearLogs 手动清空。
- SFTP 秘钥格式。仅支持 OpenSSH 格式私钥(
-----BEGIN OPENSSH PRIVATE KEY----- 或 PEM 格式)。PuTTY PPK 格式需先用 puttygen 转换。选择私钥文件而非公钥(公钥文件以 .pub 后缀结尾)。
- SFTP Ed25519 秘钥限制。JSch 0.2.20 在 Android 9+ 不支持 Ed25519/Ed448 秘钥签名(缺少 EdDSA 签名提供者)。请改用 RSA (
ssh-keygen -t rsa -b 4096) 或 ECDSA (ssh-keygen -t ecdsa -b 256) 秘钥。
- SFTP 加密秘钥。若私钥生成时设置了 passphrase,必须在
passphrase 字段填写;否则认证会失败(USERAUTH fail)。可用 ssh-keygen -t rsa -b 4096 -f new_key -N "" 生成不带密码的秘钥。
- FTPS require_ssl_reuse。vsftpd 等服务器若启用
require_ssl_reuse=YES,插件已实现 SSL session 复用尝试,但 Android Conscrypt 实现可能不支持反射注入。若仍报 425 错误,需服务器端设置 require_ssl_reuse=NO,或改用 SFTP 协议。