【问题标题】:win32file.ReadDirectoryChangesW doesn't find all moved fileswin32file.ReadDirectoryChangesW 找不到所有移动的文件
【发布时间】:2018-04-12 14:24:52
【问题描述】:

早上好,

我在 Python 中创建的程序遇到了一个特殊问题。看来,当我将文件从一个位置拖放到另一个位置时,并非所有文件都被模块注册为事件。

我一直在使用 win32file 和 win32con 尝试获取与将文件从一个位置移动到另一个位置相关的所有事件以进行处理。

这是我的检测代码的片段:

import win32file
import win32con
def main():
    path_to_watch = 'D:\\'
    _file_list_dir = 1
    # Create a watcher handle
    _h_dir = win32file.CreateFile(
        path_to_watch,
        _file_list_dir,
        win32con.FILE_SHARE_READ |
        win32con.FILE_SHARE_WRITE |
        win32con.FILE_SHARE_DELETE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
    )
    while 1:
        results = win32file.ReadDirectoryChangesW(
            _h_dir,
            1024,
            True,
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
            win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
            win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
            win32con.FILE_NOTIFY_CHANGE_SIZE |
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
            win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None
        )
        for _action, _file in results:
            if _action == 1:
                print 'found!'
            if _action == 2:
                print 'deleted!'

我拖放了 7 个文件,它只找到了 4 个。

# found!
# found!
# found!
# found!

如何检测所有已删除的文件?

【问题讨论】:

  • results 的内容是什么?

标签: python python-2.7 winapi pywin32


【解决方案1】:

[ActiveState.Docs]: win32file.ReadDirectoryChangesW(这是我能找到的关于[GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions 的最佳文档)是[MS.Docs]: ReadDirectoryChangesW function 的包装器。这是它所说的(关于缓冲区):

1。一般

当您第一次调用 ReadDirectoryChangesW 时,系统会分配一个缓冲区来存储更改信息。这个缓冲区与目录句柄相关联,直到它被关闭并且它的大小在其生命周期内不会改变。调用此函数之间发生的目录更改将添加到缓冲区中,然后在下一次调用中返回。如果缓冲区溢出,则丢弃缓冲区的全部内容,lpBytesReturned 参数为 0,ReadDirectoryChangesW 函数失败并返回错误代码 ERROR_NOTIFY_ENUM_DIR.

  • 我的理解是,这是一个不同的缓冲区,而不是作为参数传递的缓冲区 (lpBuffer):

    • 前者被传递给每次调用ReadDirectoryChangesW(可能是为每次调用传递不同的缓冲区(具有不同的大小))

    • 后者是由系统分配的,而前者显然是在函数调用之前(由用户)分配的
      那是一个存储数据(可能以某种原始格式) ) 在函数调用之间,当函数被调用时,缓冲区内容被复制(并格式化)到 lpBuffer (如果同时没有溢出(并丢弃))

2。同步

成功同步完成后,lpBuffer 参数是一个格式化的缓冲区,写入缓冲区的字节数在 lpBytesReturned 中可用。如果传输的字节数为零,则缓冲区要么太大而系统无法分配,要么太小而无法提供有关目录或子树中发生的所有更改的详细信息。在这种情况下,您应该通过枚举目录或子树来计算更改。

  • 这在一定程度上证实了我之前的假设

    • 缓冲区对于系统来说太大而无法分配” - 也许在分配前一点的缓冲区时,它会考虑到 nBufferLength

无论如何,我拿走了你的代码并“稍微”改变了它。

code00.py

import sys
import msvcrt
import pywintypes
import win32file
import win32con
import win32api
import win32event


FILE_LIST_DIRECTORY = 0x0001
FILE_ACTION_ADDED = 0x00000001
FILE_ACTION_REMOVED = 0x00000002

ASYNC_TIMEOUT = 5000

BUF_SIZE = 65536


def get_dir_handle(dir_name, asynch):
    flags_and_attributes = win32con.FILE_FLAG_BACKUP_SEMANTICS
    if asynch:
        flags_and_attributes |= win32con.FILE_FLAG_OVERLAPPED
    dir_handle = win32file.CreateFile(
        dir_name,
        FILE_LIST_DIRECTORY,
        (win32con.FILE_SHARE_READ |
         win32con.FILE_SHARE_WRITE |
         win32con.FILE_SHARE_DELETE),
        None,
        win32con.OPEN_EXISTING,
        flags_and_attributes,
        None
    )
    return dir_handle


def read_dir_changes(dir_handle, size_or_buf, overlapped):
    return win32file.ReadDirectoryChangesW(
        dir_handle,
        size_or_buf,
        True,
        (win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
         win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
         win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
         win32con.FILE_NOTIFY_CHANGE_SIZE |
         win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
         win32con.FILE_NOTIFY_CHANGE_SECURITY),
        overlapped,
        None
    )


def handle_results(results):
    for item in results:
        print("    {} {:d}".format(item, len(item[1])))
        _action, _ = item
        if _action == FILE_ACTION_ADDED:
            print("    found!")
        if _action == FILE_ACTION_REMOVED:
            print("    deleted!")


def esc_pressed():
    return msvcrt.kbhit() and ord(msvcrt.getch()) == 27


def monitor_dir_sync(dir_handle):
    idx = 0
    while True:
        print("Index: {:d}".format(idx))
        idx += 1
        results = read_dir_changes(dir_handle, BUF_SIZE, None)
        handle_results(results)
        if esc_pressed():
            break


def monitor_dir_async(dir_handle):
    idx = 0
    buffer = win32file.AllocateReadBuffer(BUF_SIZE)
    overlapped = pywintypes.OVERLAPPED()
    overlapped.hEvent = win32event.CreateEvent(None, False, 0, None)
    while True:
        print("Index: {:d}".format(idx))
        idx += 1
        read_dir_changes(dir_handle, buffer, overlapped)
        rc = win32event.WaitForSingleObject(overlapped.hEvent, ASYNC_TIMEOUT)
        if rc == win32event.WAIT_OBJECT_0:
            bufer_size = win32file.GetOverlappedResult(dir_handle, overlapped, True)
            results = win32file.FILE_NOTIFY_INFORMATION(buffer, bufer_size)
            handle_results(results)
        elif rc == win32event.WAIT_TIMEOUT:
            #print("    timeout...")
            pass
        else:
            print("Received {:d}. Exiting".format(rc))
            break
        if esc_pressed():
            break
    win32api.CloseHandle(overlapped.hEvent)


def monitor_dir(dir_name, asynch=False):
    dir_handle = get_dir_handle(dir_name, asynch)
    if asynch:
        monitor_dir_async(dir_handle)
    else:
        monitor_dir_sync(dir_handle)
    win32api.CloseHandle(dir_handle)


def main():
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    asynch = True
    print("Attempting {}ynchronous mode using a buffer {:d} bytes long...".format("As" if async else "S", BUF_SIZE))
    monitor_dir(".\\test", asynch=asynch)


if __name__ == "__main__":
    main()

注意事项

  • 尽可能使用常量
  • 将代码拆分为函数,使其模块化(同时避免重复)
  • 添加了 print 语句以增加输出
  • 添加了 异步 功能(因此如果 dir 中没有活动,脚本不会永远挂起)
  • 添加了一种在用户按下 ESC 时退出的方式(当然在同步模式下,dir 中的事件也必须发生)
  • 为不同的结果使用不同的值

输出

e:\Work\Dev\StackOverflow\q049799109>dir /b test
0123456789.txt
01234567890123456789.txt
012345678901234567890123456789.txt
0123456789012345678901234567890123456789.txt
01234567890123456789012345678901234567890123456789.txt
012345678901234567890123456789012345678901234567890123456789.txt
0123456789012345678901234567890123456789012345678901234567890123456789.txt
01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt
012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt

e:\Work\Dev\StackOverflow\q049799109>
e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code00.py
Python 2.7.10 (default, Mar  8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32

Attempting Synchronous mode using a buffer 512 bytes long...
Index: 0
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    deleted!
Index: 1
    (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    deleted!
Index: 2
    (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    deleted!
Index: 3
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    deleted!
    (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    deleted!
Index: 4
    (2, u'01234567890123456789012345678901234567890123456789.txt') 54
    deleted!
Index: 5
    (2, u'0123456789012345678901234567890123456789.txt') 44
    deleted!
    (2, u'012345678901234567890123456789.txt') 34
    deleted!
Index: 6
    (2, u'01234567890123456789.txt') 24
    deleted!
    (2, u'0123456789.txt') 14
    deleted!
Index: 7
    (1, u'0123456789.txt') 14
    found!
Index: 8
    (3, u'0123456789.txt') 14
Index: 9
    (1, u'01234567890123456789.txt') 24
    found!
Index: 10
    (3, u'01234567890123456789.txt') 24
    (1, u'012345678901234567890123456789.txt') 34
    found!
    (3, u'012345678901234567890123456789.txt') 34
    (1, u'0123456789012345678901234567890123456789.txt') 44
    found!
Index: 11
    (3, u'0123456789012345678901234567890123456789.txt') 44
    (1, u'01234567890123456789012345678901234567890123456789.txt') 54
    found!
    (3, u'01234567890123456789012345678901234567890123456789.txt') 54
Index: 12
Index: 13
    (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    found!
Index: 14
Index: 15
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    found!
Index: 16
    (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
Index: 17
    (1, u'a') 1
    found!
Index: 18
    (3, u'a') 1

e:\Work\Dev\StackOverflow\q049799109>
e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code00.py
Python 2.7.10 (default, Mar  8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32

Attempting Synchronous mode using a buffer 65536 bytes long...
Index: 0
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    deleted!
Index: 1
    (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    deleted!
Index: 2
    (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    deleted!
Index: 3
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    deleted!
Index: 4
    (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    deleted!
Index: 5
    (2, u'01234567890123456789012345678901234567890123456789.txt') 54
    deleted!
Index: 6
    (2, u'0123456789012345678901234567890123456789.txt') 44
    deleted!
Index: 7
    (2, u'012345678901234567890123456789.txt') 34
    deleted!
    (2, u'01234567890123456789.txt') 24
    deleted!
    (2, u'0123456789.txt') 14
    deleted!
Index: 8
    (1, u'0123456789.txt') 14
    found!
Index: 9
    (3, u'0123456789.txt') 14
Index: 10
    (1, u'01234567890123456789.txt') 24
    found!
Index: 11
    (3, u'01234567890123456789.txt') 24
Index: 12
    (1, u'012345678901234567890123456789.txt') 34
    found!
Index: 13
    (3, u'012345678901234567890123456789.txt') 34
Index: 14
    (1, u'0123456789012345678901234567890123456789.txt') 44
    found!
Index: 15
    (3, u'0123456789012345678901234567890123456789.txt') 44
Index: 16
    (1, u'01234567890123456789012345678901234567890123456789.txt') 54
    found!
    (3, u'01234567890123456789012345678901234567890123456789.txt') 54
Index: 17
    (1, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    found!
    (3, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    found!
Index: 18
    (3, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    found!
    (3, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    found!
    (3, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    found!
    (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
Index: 20
    (2, u'a') 1
    deleted!

e:\Work\Dev\StackOverflow\q049799109>
e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code00.py
Python 2.7.10 (default, Mar  8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32

Attempting Asynchronous mode using a buffer 512 bytes long...
Index: 0
Index: 1
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    deleted!
Index: 2
    (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    deleted!
Index: 3
    (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    deleted!
Index: 4
    (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    deleted!
Index: 5
    (2, u'01234567890123456789012345678901234567890123456789.txt') 54
    deleted!
Index: 6
    (2, u'0123456789012345678901234567890123456789.txt') 44
    deleted!
Index: 7
    (2, u'012345678901234567890123456789.txt') 34
    deleted!
Index: 8
    (2, u'01234567890123456789.txt') 24
    deleted!
Index: 9
    (2, u'0123456789.txt') 14
    deleted!
Index: 10
Index: 11
Index: 12
    (1, u'0123456789.txt') 14
    found!
Index: 13
    (1, u'01234567890123456789.txt') 24
    found!
Index: 14
    (1, u'012345678901234567890123456789.txt') 34
    found!
Index: 15
    (3, u'012345678901234567890123456789.txt') 34
Index: 16
    (1, u'0123456789012345678901234567890123456789.txt') 44
    found!
    (3, u'0123456789012345678901234567890123456789.txt') 44
Index: 17
Index: 18
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    found!
Index: 19
Index: 20
    (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    found!
Index: 21
Index: 22
Index: 23
Index: 24

e:\Work\Dev\StackOverflow\q049799109>
e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code00.py
Python 2.7.10 (default, Mar  8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32

Attempting Asynchronous mode using a buffer 65536 bytes long...
Index: 0
Index: 1
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    deleted!
Index: 2
    (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    deleted!
Index: 3
    (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    deleted!
Index: 4
    (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    deleted!
Index: 5
    (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    deleted!
Index: 6
    (2, u'01234567890123456789012345678901234567890123456789.txt') 54
    deleted!
Index: 7
    (2, u'0123456789012345678901234567890123456789.txt') 44
    deleted!
Index: 8
    (2, u'012345678901234567890123456789.txt') 34
    deleted!
    (2, u'01234567890123456789.txt') 24
    deleted!
Index: 9
    (2, u'0123456789.txt') 14
    deleted!
Index: 10
Index: 11
Index: 12
    (1, u'0123456789.txt') 14
    found!
Index: 13
    (1, u'01234567890123456789.txt') 24
    found!
Index: 14
    (1, u'012345678901234567890123456789.txt') 34
    found!
Index: 15
    (3, u'012345678901234567890123456789.txt') 34
    (1, u'0123456789012345678901234567890123456789.txt') 44
    found!
    (3, u'0123456789012345678901234567890123456789.txt') 44
Index: 16
    (1, u'01234567890123456789012345678901234567890123456789.txt') 54
    found!
    (3, u'01234567890123456789012345678901234567890123456789.txt') 54
    (1, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    found!
    (3, u'012345678901234567890123456789012345678901234567890123456789.txt') 64
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    found!
Index: 17
    (3, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74
    (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    found!
    (3, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84
    (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    found!
    (3, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94
    (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
    found!
    (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104
Index: 18
Index: 19

备注

  • 使用了一个目录 test,其中包含 10 个不同名称的文件(重复 0123456789
  • 有 4 次运行:
    1. 同步
      • 512B 缓冲区
      • 64K 缓冲区
    2. 异步
      • 512B 缓冲区
      • 64K 缓冲区
  • 对于每个(以上)运行,文件是(使用 Windows Commander 进行操作):
    • 目录移动(涉及删除
    • 移动(返回) 目录(涉及添加
  • 每个组合只运行一次,到目前为止不能作为基准,但我多次运行脚本,模式趋于一致
  • 删除文件在运行过程中变化不大,这意味着事件在(少量)时间内均匀分布
  • 另一方面,添加文件取决于缓冲区大小。另一个值得注意的是,每次添加都有 2 个事件
  • 从性能的角度来看,异步模式并没有带来任何改进(正如我所期望的那样),相反,它往往会减慢速度。但它最大的优点是可以在超时时优雅退出(异常中断可能会保持资源锁定直到程序退出(有时甚至更久!))

最重要的是,没有办法可以避免丢失事件。通过增加生成事件的数量,可以“击败”所采取的每一项措施。

最小化损失:

  • 缓冲区大小。这是您的情况的(主要)问题。不幸的是,文档再清楚不过了,没有关于它应该有多大的指导方针。浏览 C 论坛我注意到 64K 是一个常见的值。然而:

    • 不可能有一个巨大的缓冲区,并且在失败的情况下减少它的大小直到成功,因为这意味着在计算缓冲区大小时丢失所有生成的事件

    • 即使 64k 足以容纳(多次)我在测试中生成的所有事件,但仍有一些事件丢失了。可能是因为我一开始提到的“神奇”缓冲区

  • 尽可能减少事件的数量。在您的情况下,我注意到您只对添加和删除事件感兴趣(FILE_ACTION_ADDEDFILE_ACTION_REMOVED)。只为 ReadDirectoryChangesW 指定适当的 FILE_NOTIFY_CHANGE_* 标志(例如,您不关心 FILE_ACTION_MODIFIED,但在添加文件时会收到它)

  • 尝试将 dir 内容拆分为多个子目录并同时监控它们。例如,如果你只关心一个 dir 和它的一堆子目录中发生的变化,那么递归地监视整个树是没有意义的,因为它很可能会产生很多无用的事件。无论如何,如果是并行处理,不要使用线程,因为 GIL!!! ([Python.Wiki]: GlobalInterpreterLock)。请改用[Python.Docs]: multiprocessing - Process-based “threading” interface

  • 提高在循环中运行的代码的速度,使其在ReadDirectoryChangesW之外花费尽可能少的时间(当生成的事件可能溢出缓冲区时)。当然,下面的一些项目可能影响不大并且(也有一些不好的副作用),但我还是列出了它们:

    • 尽可能减少处理并尝试延迟处理。也许在另一个进程中执行(因为 GIL

    • 去掉所有print之类的语句

    • 而不是例如win32con.FILE_NOTIFY_CHANGE_FILE_NAME 在脚本开头使用 from win32con import FILE_NOTIFY_CHANGE_FILE_NAME,并且只在循环中使用 FILE_NOTIFY_CHANGE_FILE_NAME(避免在模块)

    • 不要使用函数(因为 call / ret 之类的指令)- 不确定

    • 尝试使用 win32file.GetQueuedCompletionStatus 方法获取结果(仅异步

    • 随着时间的推移,事情往往会变得更好(当然也有例外),请尝试切换到更新的 Python 版本。也许它会跑得更快

    • 使用 C - 这可能是不可取的,但它可能有一些好处:

      • PyWin32 执行的 PythonC 之间不会有来回转换 - 但我没有使用分析器检查他们花了多少时间

      • lpCompletionRoutinePyWin32 不提供)也将可用,也许它更快

      • 作为替代方案,可以使用 CTypes 调用 C,但这需要一些工作,我觉得不值得

【讨论】:

  • 抱歉,我还没有时间完整地看这个。请允许我用几天时间来了解这一点,然后我将能够正确回答并提供您所要求的信息。
  • 我很欣赏这是多么详细。阅读它,需要处理很多信息(我对带有所有 win32 组件的 pywin32 模块相当陌生)。非常感谢您彻底解释优化我的代码的所有可能方法以及以有效方式正确搜索这些目录的方法。
  • 不客气 :) 。我不再需要 result 内容,因为我能够重现我这边的问题。
  • 作为免责声明,您似乎对缓冲区大小是正确的。我还没有完全测试它(因为我仍在将新代码合并到我自己的项目中,通读和剖析),但这也可能部分是由于我的代码正在监视服务器以获取更新。
  • 默认情况下,Python 会将它们转换为其内部的int。我这样定义它们是因为MSDN 上就是这样定义它们的(为了清楚起见)。
猜你喜欢
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 2011-08-28
  • 2019-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多