使用 async 命令同步文件。實現(xiàn)備份或移動文件。
開發(fā)
dockerfile
安裝 async 命令
FROM node:20.9.0-alpine AS base
# 添加源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
...
RUN apk add --no-cache rsync
explorer-manage
使用 node 的 spawn 模塊執(zhí)行 rsync 命令。
import { spawn } from 'child_process'
import { formatPath } from '../format-path.mjs'
/**
*
* @param {string} source
* @param {string} destination
* @param {string[]} opt
* @param {boolean} test
* @returns {ChildProcessWithoutNullStreams}
*/
export const rsync = (source, destination, opt = [], test = false) => {
if (test) {
opt.push('-n')
}
console.log('rsync', [...opt, source, destination].join(' '))
return spawn('rsync', [...opt, source, destination])
}
/**
*
* @param {string} source
* @param {string} destination
* @param {boolean} has_delete_source
* @param {boolean} test
* @returns {ChildProcessWithoutNullStreams}
*/
export const rsyncMovePath = (source, destination, has_delete_source = false, test = false) => {
const opt = ['-a', '--progress']
has_delete_source && opt.push('--remove-source-files')
return rsync(formatPath(source), formatPath(destination), opt, test)
}
新增兩個方法,rsync 與 rsyncMovePath 方法
- rsync:為簡單的命令行封裝
- rsyncMovePath:添加一個當同步完成后,刪除原目錄的參數(shù) --remove-source-files。
explorer
創(chuàng)建一個流式數(shù)據(jù) route api 接口
import { NextRequest, NextResponse } from 'next/server'
import { rsyncMovePath } from '@/explorer-manager/src/rsync/index.mjs'
export const POST = async (req: NextRequest) => {
const { path, out_path, rsync_delete_source } = await req.json()
try {
const stream = rsyncMovePath(path, out_path, rsync_delete_source)
return new NextResponse(stream.stdout, {
headers: {
'Content-Type': 'application/octet-stream',
},
})
} catch (e: any) {
return NextResponse.json({ err_msg: e.message }, { status: 500 })
}
}
客戶端部分在原”移動“的彈窗上新增一個移動方式的 Select 菜單。提供兩個選項
- 移動
- rsync 同步。當選擇 rsync 同步時,出現(xiàn)一個”刪除源文件“的開關
<Form.Item<FieldType> label="移動方式" name="move_type" rules={[{ required: true, message: '請選擇移動方式' }]}>
<Select
options={[
{ label: '移動', value: 'move' },
{ label: 'rsync 同步', value: 'rsync' },
]}
/>
</Form.Item>
提交表單時,判斷移動方式類型,選擇不同的兩種移動方式。
有時候目錄文件比較大,rsync 同步使用流式回傳當前同步狀態(tài)信息。
...
import { moveAction } from '@/components/move-modal/action'
const MoveModal: React.FC = () => {
const move_path = useMovePathStore()
const changeMovePath = useMovePathDispatch()
const [chunk, changeChunk] = useState<string[]>([])
const { update } = useUpdateReaddirList()
const { message } = App.useApp()
const rsyncMove = useRequest(
async (values: FieldType) => {
const { path, new_path, last, rsync_delete_source } = values
const res = await fetch('/path/api/rsync-move', {
method: 'post',
body: JSON.stringify({
path: path,
out_path: [new_path, last].join('/'),
rsync_delete_source: rsync_delete_source,
}),
})
if (res.body) {
const reader = res.body.getReader()
const decode = new TextDecoder()
while (1) {
const { done, value } = await reader.read()
const decode_value = decode.decode(value)
!isEmpty(decode_value) && changeChunk((chunk) => chunk.concat(decode_value).reverse())
if (done) {
break
}
}
}
return Promise.resolve().then(update)
},
{
manual: true,
},
)
return (
<Modal
title="移動"
open={!isEmpty(move_path)}
width={1000}
onCancel={() => changeMovePath('')}
footer={false}
destroyOnClose={true}
>
<MoveForm
onFinish={(values) => {
const { move_type, path, new_path, last } = values
const thenAction = () => {
changeMovePath('')
update()
message.success('移動成功').then()
}
if (move_type === 'move') {
return moveAction(path, [new_path, last].join('/')).then(thenAction)
} else if (move_type === 'rsync') {
rsyncMove.run(values)
}
}}
/>
{!isEmpty(chunk) && (
<Card>
<pre>{chunk.join('')}</pre>
</Card>
)}
</Modal>
)
}
export default MoveModal
效果

截屏2023-12-22 20.34.11.png