本文由Markdown语法编辑器编辑完成。
1.背景:
最近花了两三天的时间,解决了一个医院客户,困扰了我比较两三星期的,关于ftp下载医学影像的问题。
故障大概是这样的: 某一天,线上的同事找到我说,这家医院有两个院区,医生反馈来自分院的数据,在我们的系统里面无法展示。主院区的影像则正常展示。我通过VPN远程到医院的服务器,看到了很多的报错,涉及到分院的数据时,报错都是: “Resource not found.”
这家医院是FTP的对接方式。他们的数据库里面会存放每个检查的FTP的地址。我们的服务器,会定时轮询,从数据库中读取到检查的FTP路径,然后再从FTP Server把图像下载下来,进行后续处理。
由于他们是两个院区,因此分别有两个FTP Server,一个存主院区的数据,一个存分院区的数据。出问题的是分院区的数据。
2.问题定位:
前期花了一定的时间进行摸索。
我们是作为FTP客户端,从医院的FTP服务端拉取数据。
程序主要使用了两种方式,来进行FTP的对接,对应的是两个不同的python库函数。
ftp也可以利用浏览器直接访问服务端。当我在客户的浏览器,登录ftp的服务器后,并且根据数据库中定义的,文件夹在ftp服务器的路径,是可以一层一层定位到图像的。

于是我也利用python自带的ftplip进行测试。
测试代码如下:
2.1 python内置的ftplip
关于python内置的ftplip的相关介绍,可以参考链接:
https://docs.python.org/zh-cn/3/library/ftplib.html
https://dataxujing.github.io/ftplib/
# 定义FTP服务器的连接常量:
_FTP_SERVER_IP = "192.168.0.11"
_FTP_SERVER_PORT = 21
_FTP_SERVER_ROOT = "/pacs"
_FTP_SERVER_USERNAME = "dicomreader"
_FTP_SERVER_PASSWORD = "abc123"
from ftplib import FTP
if __name__ == "__main__":
# 连接 FTP
ftp_jn = FTP()
ftp_jn.connect(_FTP_SERVER_IP, _FTP_SERVER_PORT) # 主机和端口,默认端口是 21
ftp_jn.login(_FTP_SERVER_USERNAME, _FTP_SERVER_PASSWORD) # 登录
# 列出当前目录下的文件
ftp_jn.cwd("./CT/2025/10/11")
file_list = ftp_jn.nlst()
print(f"file count is: {len(file_list)}")
# ftp_jn.cwd("CT/2025/10/11/C5F33DB3571A36D43372E429D14F6628")
# 或者使用 dir 查看详细信息
# dir_list = ftp_jn.dir()
# if "C5F33DB3571A36D43372E429D14F6628" in dir_list:
# print("11111")
# print(ftp_jn.dir())
# 退出
ftp_jn.quit()
运行上述的测试代码后,当运行: ftp_jn.cwd(“./CT/2025/10/11”)后,即说明,将当前目录cd到指定的目录。
然后再运行: ftp_jn.nlst(), 即输出该目录下面的所有文件夹和文件的名称。
运行这些测试脚本时,都可以正常地运行,而且看到的目录,和在浏览器上面看到的,也是完全相同的。
但为什么真正下载的时候,却还是失败呢?
2.2 python第三方库fs
后来,我又用python的一个第三方库fs. 它将python与ftp, mount等服务器的常见的操作,均进行了封装,并提供了接口来进行交互。属于比python自带的ftplip, 更高级的一个库。
该库的github的链接如下: https://github.com/PyFilesystem/pyfilesystem2

正如它的简介所示, “Python’s Filesystem abstraction layer”, 它是一个对于python文件系统的一个抽象层. 当然ftp, mount等,都属于文件系统的一部分,因此都是支持的。
当我用fs, 来测试该分院服务器的数据时,奇怪的事情发生了.
from pathlib import Path
from fs.ftpfs import FTPFS
_FTP_SERVER_IP = "192.168.0.11"
_FTP_SERVER_PORT = 21
_FTP_SERVER_ROOT = "/pacs"
_FTP_SERVER_USERNAME = "dicomreader"
_FTP_SERVER_PASSWORD = "abc123"
_root_fs_ftp = FTPFS(
host=_FTP_SERVER_IP,
port=_FTP_SERVER_PORT,
user=_FTP_SERVER_USERNAME,
passwd=_FTP_SERVER_PASSWORD,
)
def _recursion_get_file_list(remote_rel_path: str):
"""
递归的返回远端文件列表
:param remote_rel_path: 相对于remote_path_root的相对地址
"""
print(f"root_fs_ftp的对象类型为: {type(_root_fs_ftp)}")
print(f"fsftp pwd: {_root_fs_ftp.ftp.pwd()}, list dir: {_root_fs_ftp.listdir('/')}")
_remote_root_fs = _root_fs_ftp.opendir(_FTP_SERVER_ROOT)
print(f"remote_root_fs的类型为: {type(_remote_root_fs)}")
for entry in _remote_root_fs.filterdir(".", exclude_files=True, exclude_dirs=False):
print(f"路径下面包含的文件夹名称为: {entry.name}")
for x in _remote_root_fs.scandir(remote_rel_path):
path = (Path(remote_rel_path) / x.name).as_posix()
if x.is_dir:
yield from _recursion_get_file_list(path)
elif x.is_file:
yield path
def get_file_list(remote_rel_path: str):
return list(_recursion_get_file_list(remote_rel_path))
if __name__ == "__main__":
remote_rel_path = "/CT/2025/10/11"
print(get_file_list(remote_rel_path))
由于原来的代码是可以正常运行的。我其实比较纳闷,之前定义的:
_FTP_SERVER_ROOT = '/pacs'
到底有啥用? 因为不管是实际下载,还是在浏览器里面查看图像的路径时,我都没有看到这个,带有"pacs"字符串的目录层级。但是之前一段时间,这里的确是定义了它,而且可以成功下载图像。
于是我在用fs内置的: fs.ftpfs里面的FTPFS, 应用初始化参数构建后,就想看看目录的目录结构是什么。
于是采用了: _root_fs_ftp.ftp.pwd(), 来输出当前连接ftp后,所处的服务器的路径, 以及ftp服务器的根路径,都有哪些文件夹。
结果这时输出的ftp的当前路径是:
/pacs2025
而ftp服务器所处的根路径下面,有一个pacs和一个pacs2025的文件夹。

之后我试图,从/pacs2025的当前路径,先cd到上一级目录,再进入pacs路径,想一探究竟,看看为什么pacs路径里面,没有我要下载的影像。
以下是我在调试的过程中,和chatgpt探讨如何解决问题的一些关键截图:






经过和AI的反复探讨和考证,最后发现,之所以我们的服务,突然无法下载医院分院的数据。是因为医院信息科,调整了他们的FTP Server的根路径,也就是使用账号和密码登录后,默认进入的根路径。
调整前,是/pacs; 调整后,是/pacs2025.
因此,医院服务端返回给我们的图像的路径,其实已经是相对于/pacs2025, 这个根路径下面的相对路径了。而我们在下载时,还是先opendir()了/pacs, 然后再从/pacs下面找图, 当然就所有的图像,都会报错: Resource not found了。
3. 结论:
其实不只是FTP Server.
平时我们在用python的http-server服务的时候,也是这样的。我随便切换到某个磁盘目录下面,然后运行:
python3 -m http.server 9001(可以任意选择一个未被占用的端口即可)
那么我们在浏览器上面访问时,输入启动http-server服务器的ip:9001目录,便可以看到这个目录下面的所有文件了。但是我们其实并不知道,服务器上面还有哪些其他目录。我们能够看到的文件,只是服务端允许我们看到的那部分文件罢了。
就像《封神榜第一部, 朝歌风云》中,殷商说的那句话,”马看到什么,是人决定的。“
我也就模仿着来一句,”FTP/HTTP客户端看到什么,是服务端(也是人)决定的 ! “
(完)

1151

被折叠的 条评论
为什么被折叠?



