• Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现


    作业:

    开发一个支持多用户在线的FTP程序

    要求:

    1. 用户加密认证
    2. 允许同时多用户登录
    3. 每个用户有自己的家目录 ,且只能访问自己的家目录
    4. 对用户进行磁盘配额,每个用户的可用空间不同
    5. 允许用户在ftp server上随意切换目录
    6. 允许用户查看当前目录下文件
    7. 允许上传和下载文件,保证文件一致性
    8. 文件传输过程中显示进度条
    9. 附加功能:支持文件的断点续传

    README:

    1.client连接server端需要验证账号密码,密码使用MD5加密传输,三次验证不成功即退出。
    2.用户信息保存在服务器本地文件中,密码MD5加密存储。磁盘配额大小也保存在其中。
    3.用户连接上来后,可以执行命令如下
        目录变更:cd /cd dirname / cd . /cd ..
        文件浏览:ls
        文件删除:rm filename
        目录增删:mkdir dirname /rmdir dirname
        查看当前目录:pwd
        查看当前目录大小: du
        移动和重命名: mv filename/dirname filename/dirname
        上传文件:put filename [True] (True代表覆盖)
        下载文件:get filename [True]
        上传断点续传: newput filename [o/r] (o代表覆盖,r代表断点续传)
        下载断点续传: newget filename [o/r]
    4.涉及到目录的操作,用户登录后,程序会给用户一个“锚位”----以用户名字命名的家目录,使用户无论怎么操作,都只能在这个目录底下。而在发给用户的目录信息时,隐去上层目录信息。
    5.用户在创建时,磁盘配额大小默认是100M,在上传文件时,程序会计算当前目录大小加文件大小是否会超过配额上限。未超过,上传;超过,返回磁盘大小不够的信息。磁盘配额可通过用户管理程序修改。
    6.文件上传和下载后都会进行MD5值比对,验证文件是否一致。
    7.服务端和客户端都有显示进度条功能,启用该功能会降低文件传输速度,这是好看的代价。
    8.文件断点续传,支持文件上传和下载断点续传。断点续传上传功能还会检测用户磁盘空间是否足够。(断点续传命令使用前面new+put/get命名,包含put/get所有功能,由于逻辑增多,代码复杂,特地保留原put/get,以备后用)。

    程序结构:

    完整代码:

    1.客户端 

    #Author:Zheng Na
    
    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  ####获取当前文件的上一级的上一级目录
    sys.path.append(BASE_DIR)
    
    from core.client import FtpClient
    
    if __name__ == '__main__':
        ftp = FtpClient()
        ftp.connect('localhost', 9999)
    
        auth_tag = False
        count = 0
        while auth_tag != True:  ####功能:3次验证不通过即退出
            count += 1
            if count <= 3:
                auth_tag = ftp.auth()
            else:
                exit()
    
        ftp.interactive()
        ftp.close()
    main.py
    ####用户端配置文件####
    [DEFAULT]
    logfile = ../log/client.log
    download_dir= ../temp
    
    ####日志文件位置####
    [log]
    logfile = ../log/client.log
    
    ####下载文件存放位置####
    [download]
    download_dir= ../temp
    client.conf
    #Author:Zheng Na
    
    import  os,configparser,logging
    
    ####读取配置文件####
    base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    config_file = os.path.join(base_dir, 'conf/client.conf')
    cf = configparser.ConfigParser()
    cf.read(config_file, encoding='utf-8')
    
    ####设定日志目录####
    if os.path.exists(cf.get('log', 'logfile')):
        logfile = cf.get('log', 'logfile')
    else:
        logfile = os.path.join(base_dir, 'log/client.log')
    
    ####设定下载/上传目录####
    if os.path.exists(cf.get('download', 'download_dir')):
        download_dir = cf.get('download', 'download_dir')
    else:
        download_dir = os.path.join(base_dir, 'temp')
    
    ####设置日志格式####
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S',
                        filename=logfile,
                        filemode='a+')
    settings.py
    # Author:Zheng Na
    
    import socket,os,json,hashlib,sys,time,getpass,logging
    import core.settings
    
    def hashmd5(*args):  ####MD5加密
        m = hashlib.md5()
        m.update(str(*args).encode())
        ciphertexts = m.hexdigest()  ####密文
        return ciphertexts
    
    def processbar(part, total):  ####进度条,运行会导致程序变慢
        if total != 0:
            done = int(50 * part / total)
            sys.stdout.write("
    [%s%s]" % ('' * done, '  ' * (50 - done)))  ####注意:一个方块对应2个空格
            sys.stdout.write('{:.2%}'.format(part / total) + ' ' * 3 + str(part) + '/' + str(total))
            sys.stdout.flush()
    
    class FtpClient(object):
        def __init__(self):
            self.client = socket.socket()
    
        def connect(self, ip, port):  ####连接
            self.client.connect((ip, port))
    
        def auth(self):  ####用户认证
            username = input("请输入用户名>>>:").strip()
            # password = getpass.getpass("请输入密码>>>:").strip()  ####在linux上输入密码不显示,此模块在pycharm中无法使用
            password = input("请输入密码>>>:").strip()  ####Windows测试用
            password = hashmd5(password)
            msg = {
                'username': username,
                'password': password
            }
            self.client.send(json.dumps(msg).encode('utf-8'))
            server_response = self.client.recv(1024).decode('utf-8')
            logging.info(server_response)
            if server_response == 'ok':
                print("认证通过!")
                return True
            else:
                print(server_response)
                return False
    
        def interactive(self): ####交互
            while True:
                self.pwd('pwd')                       ####打印家目录
                cmd = input(">> ").strip()
                if len(cmd) == 0: continue
                cmd_str = cmd.split()[0]              ####用户输入的第一个值必定是命令
                if hasattr(self, cmd_str):            ####反射:判断一个对象中是否有字符串对应的方法或属性
                    func = getattr(self, cmd_str)     ####利用反射来解耦:根据字符串去获取对象里对应的方法的内存地址或对应属性的值
                    func(cmd)                         ####调用命令对应的方法
                else:
                    self.help()
    
        def help(self):  ####帮助
            msg = '''
             仅支持如下命令:
             ls
             du
             pwd
             cd dirname/cd ./cd ..
             mkdir dirname
             rm  filename
             rmdir dirname
             mv filename/dirname filename/dirname  
             get filename [True] (True代表覆盖)
             put filename [True] (True代表覆盖)
             newget filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传)
             newput filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传)
             '''
            print(msg)
    
        def pwd(self, *args):  ####查看当前目录
            cmd_split = args[0].split()
            if len(cmd_split) == 1:
                msg = {'action': 'pwd'}
                self.exec_linux_cmd(msg)
            else:
                self.help()
    
        def ls(self, *args):  ####文件浏览
            cmd_split = args[0].split()
            if len(cmd_split) == 1:
                msg = {'action': 'ls'}
                self.exec_linux_cmd(msg)
            else:
                self.help()
    
        def du(self, *args):  ####查看当前目录大小
            cmd_split = args[0].split()
            if len(cmd_split) == 1:
                msg = {'action': 'du'}
                self.exec_linux_cmd(msg)
            else:
                self.help()
    
        def cd(self, *args):  ####切换目录
            cmd_split = args[0].split()
            if len(cmd_split) == 1:
                dirname = ''
            elif len(cmd_split) == 2:
                dirname = cmd_split[1]
            else:
                return help()
    
            msg = {
                "action": 'cd',
                "dirname": dirname
            }
            self.exec_linux_cmd(msg)
    
        def mkdir(self, *args):  ####生成目录
            cmd_split = args[0].split()
            if len(cmd_split) == 2:
                dirname = cmd_split[1]
                msg = {
                    "action": 'mkdir',
                    "dirname": dirname
                }
                self.exec_linux_cmd(msg)
            else:
                help()
    
        def rm(self, *args):  ####删除文件
            cmd_split = args[0].split()
            if len(cmd_split) == 2:
                filename = cmd_split[1]
                msg = {
                    "action": 'rm',
                    "filename": filename,
                    "confirm": True  ####确认是否直接删除标志
                }
                self.exec_linux_cmd(msg)
            else:
                help()
    
        def rmdir(self, *args):  ####删除目录
            cmd_split = args[0].split()
            if len(cmd_split) == 2:
                dirname = cmd_split[1]
                msg = {
                    "action": 'rmdir',
                    "dirname": dirname,
                    "confirm": True  ####确认是否直接删除标志
                }
                self.exec_linux_cmd(msg)
            else:
                help()
    
        def mv(self, *args):  ####实现功能:移动文件,移动目录,文件重命名,目录重命名
            cmd_split = args[0].split()
            if len(cmd_split) == 3:
                objname = cmd_split[1]
                dstname = cmd_split[2]
                msg = {
                    "action": 'mv',
                    "objname": objname,
                    "dstname": dstname
                }
                self.exec_linux_cmd(msg)
            else:
                help()
    
        def exec_linux_cmd(self, dict): ####用于后面调用linux命令
            logging.info(dict)  ####将发送给服务端的命令保存到日志中
            self.client.send(json.dumps(dict).encode('utf-8'))
            server_response = json.loads(self.client.recv(4096).decode('utf-8'))
            if isinstance(server_response, list):  ####判断是否为list类型
                for i in server_response:
                    print(i)
            else:
                print(server_response)
    
        def get(self, *args):  ####下载文件
            cmd_split = args[0].split()
            override = cmd_split[-1]  ####override:是否覆盖参数,True表示覆盖,放在最后一位
            # print(override,type(override))
            if override != 'True':
                override = 'False'
            # print(override)
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                filepath = os.path.join(core.settings.download_dir, filename)
                if override != 'True' and os.path.isfile(filepath):  ####判断下载目录是否已存在同名文件
                    override_tag = input('文件已存在,要覆盖文件请输入yes >>>:').strip()
                    if override_tag == 'yes':
                        self.put('put %s True' % filename)
                    else:
                        print('下载取消')
                else:
                    msg = {
                        'action': 'get',
                        'filename': filename,
                        'filesize': 0,
                        'filemd5': '',
                        'override': 'True'
                    }
                    # logging.info(msg)
                    self.client.send(json.dumps(msg).encode('utf-8'))
                    server_response = json.loads(self.client.recv(1024).decode('utf-8'))
                    logging.info(server_response)
                    if server_response == 'Filenotfound':
                        print('File no found!')
                    else:
                        print(server_response)
                        self.client.send(b'client have been ready to receive')  ####发送信号,防止粘包
                        filesize = server_response['filesize']
                        filemd5 = server_response['filemd5']
                        receive_size = 0
                        f = open(filepath, 'wb')
                        while filesize > receive_size:
                            if filesize - receive_size > 1024:
                                size = 1024
                            else:
                                size = filesize - receive_size
                            data = self.client.recv(size)
                            f.write(data)
                            receive_size += len(data)
                            processbar(receive_size, filesize)  ####打印进度条
                        f.close()
                        # receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                        receive_filemd5 = 'a'  ####Windows测试用
                        print('
    ', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                        if receive_filemd5 == filemd5:
                            print('文件接收完成!')
                        else:
                            print('Error,文件接收异常!')
            else:
                help()
    
        def put(self, *args):  ####上传文件
            cmd_split = args[0].split()
            override = cmd_split[-1]  ####override:是否覆盖参数,True表示覆盖,放在最后一位
            if override != 'True':
                override = 'False'
            # print(cmd_split,override)
    
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                filepath = os.path.join(core.settings.download_dir, filename)
                if os.path.isfile(filepath):
                    filesize = os.path.getsize(filepath)  ####法1
                    # filesize = os.stat(filepath).st_size  ####法2
    
                    ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三步,代码量更多,效率也低
                    # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                    filemd5 = 'a'  ####Windows测试
    
                    msg = {
                        "action": 'put',
                        "filename": filename,
                        "filesize": filesize,
                        "filemd5": filemd5,
                        "override": override
                    }
                    # logging.info(msg)
                    self.client.send(json.dumps(msg).encode('utf-8'))
                    ###防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等
                    server_response = self.client.recv(1024)
                    # logging.info(server_response)
                    if server_response == b'file have exits, do nothing!':
                        override_tag = input('文件已存在,要覆盖文件请输入yes >>>:')
                        if override_tag == 'yes':
                            self.put('put %s True' % filename)
                        else:
                            print('文件未上传')
                    else:
                        self.client.send(b'client have ready to send')  ####发送确认信号,防止粘包,代号:P01
                        server_response = self.client.recv(1024).decode('utf-8')
                        print(server_response)  ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消
                        if server_response == 'begin':
                            f = open(filepath, 'rb')
                            send_size = 0
                            for line in f:
                                send_size += len(line)
                                self.client.send(line)
                                processbar(send_size, filesize)
                            else:
                                print('
    ', "file upload success...")
                                f.close()
                                server_response = self.client.recv(1024).decode('utf-8')
                                print(server_response)
                else:
                    print(filename, 'is not exist')
            else:
                self.help()
    
        def newget(self, *args):  ####下载文件,具有断点续传功能
            cmd_split = args[0].split()
            tag = cmd_split[-1]  ####tag:o代表覆盖,r代表续传,放在最后一位
    
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                filepath = os.path.join(core.settings.download_dir, filename)
                if tag not in ('o', 'r'):
                    if os.path.isfile(filepath):  ####判断下载目录是否已存在同名文件
                        tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip()
                    else:
                        tag = 'o'
    
                if tag in ('o', 'r'):
                    if tag == 'r':
                        local_filesize = os.path.getsize(filepath)
                    else:
                        local_filesize = 0  # 本地文件大小
    
                    msg = {
                        'action': 'newget',
                        'filename': filename,
                        'filesize': local_filesize,
                        'filemd5': '',
                    }
                    logging.info(msg)
                    self.client.send(json.dumps(msg).encode('utf-8'))
                    server_response = json.loads(self.client.recv(1024).decode('utf-8'))
                    logging.info(server_response)
                    if server_response == 'Filenotfound':
                        print('File no found!')
                    else:
                        print(server_response)
                        self.client.send(b'client have been ready to receive')  # 发送信号,防止粘包
                        filesize = server_response['filesize']
                        filemd5 = server_response['filemd5']
                        receive_size = local_filesize
                        if tag == 'r':
                            f = open(filepath, 'ab+')   ####用于断点续传
                        else:
                            f = open(filepath, 'wb+')  ####用于覆盖或者新生成文件
                        while filesize > receive_size:
                            if filesize - receive_size > 1024:
                                size = 1024
                            else:
                                size = filesize - receive_size
                            data = self.client.recv(size)
                            f.write(data)
                            receive_size += len(data)
                            # print(receive_size, len(data))  ####打印数据流情况
                            processbar(receive_size, filesize)  ####打印进度条
                        f.close()
                        # receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                        receive_filemd5 = 'a'  ####Windows测试用
                        print('
    ', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                        if receive_filemd5 == filemd5:
                            print('文件接收完成!')
                        else:
                            print('Error,文件接收异常!')
                else:
                    print("文件未下载")
            else:
                help()
    
        def newput(self, *args):  ####上传文件,具有断点续传功能
            cmd_split = args[0].split()
            tag = cmd_split[-1]  ####tag:r代表续传,o代表覆盖,放在最后一位
            if tag not in ('o', 'r'):
                tag = 'unknown'
            # print(cmd_split,tag)
    
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                filepath = os.path.join(core.settings.download_dir, filename)
                if os.path.isfile(filepath):
                    filesize = os.path.getsize(filepath)  ####法1
                    # filesize = os.stat(filepath).st_size  ####法2
    
                    ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
                    # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                    filemd5 = 'a'  # Windows测试
    
                    msg = {
                        "action": 'newput',
                        "filename": filename,
                        "filesize": filesize,
                        "filemd5": filemd5,
                        "tag": tag
                    }
                    # logging.info(msg)
                    self.client.send(json.dumps(msg).encode('utf-8'))  ####发送msg
                    server_response1 = self.client.recv(1024).decode()  ####接收文件存在或者文件不存在
                    # logging.info(server_response)
                    print(server_response1)
    
                    if server_response1 == '文件存在':  ####再确认一遍tag
                        if tag == 'unknown':
                            tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip()
                            if tag not in ('o', 'r'):
                                tag = 'unknown'
                    else:  ####文件不存在时
                        tag = 'o'
    
    
                    self.client.send(tag.encode())
                    server_response2 = json.loads(self.client.recv(1024).decode('utf-8'))
                    # print('server_response2:', server_response2)
                    content = server_response2['content']
    
                    if tag == 'o' or tag == 'r':
                        if content == 'begin':
                            position = server_response2['position']
                            print(position)
                            f = open(filepath, 'rb')
                            f.seek(position, 0)
                            send_size = position
                            for line in f:
                                send_size += len(line)
                                self.client.send(line)
                                processbar(send_size, filesize)
                            else:
                                print('
    ', "file upload success...")
                                f.close()
                                server_response3 = self.client.recv(1024).decode('utf-8')  ####服务端对比md5后发送是否成功接收文件,成功或失败
                                print(server_response3)
                        else:
                            print(content)  ####content:服务器已存在同名文件 或。。。
                    else:
                        print(content)  ####content:文件未上传
                else:
                    print(filename, 'is not exist')
            else:
                self.help()
    
        def newput2(self, *args):  ####上传文件,具有断点续传功能,网友写的,与我写的newput功能差不多
            cmd_split = args[0].split()
            override = cmd_split[-1]  ####override:是否覆盖参数,放在最后一位
            if override != 'True':
                override = 'False'
            # print(cmd_split,override)
    
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                filepath = os.path.join(core.settings.download_dir, filename)
                if os.path.isfile(filepath):
                    filesize = os.path.getsize(filepath)  ####法1
                    # filesize = os.stat(filepath).st_size  ####法2
                    ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
                    filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                    filemd5 = 'a'  ####Windows测试
                    msg = {
                        "action": 'newput2',
                        "filename": filename,
                        "filesize": filesize,
                        "filemd5": filemd5,
                        "override": override
                    }
                    # logging.info(msg)
                    self.client.send(json.dumps(msg).encode('utf-8'))
                    ####防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等
                    server_response = self.client.recv(1024)
                    # logging.info(server_response)
                    print(server_response)
                    if server_response == b'file have exits, and is a directory, do nothing!':
                        print('文件已存在且为目录,请先修改文件或目录名字,然后再上传')
                    elif server_response == b'file have exits, do nothing!':
                        override_tag = input('文件已存在,要覆盖文件请输入yes,要断点续传请输入r >>>:').strip()
                        if override_tag == 'yes':
                            self.client.send(b'no need to do anything')  ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
                            time.sleep(0.5)  ####防止黏贴,功能需改进
                            self.put('put %s True' % filename)
                        elif override_tag == 'r':
                            self.client.send(b'ready to resume from break point')  ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
                            self.client.recv(1024)  ####这边接收服务端发送过来的du信息,不显示,直接丢弃
                            server_response = json.loads(self.client.recv(1024).decode('utf-8'))
                            print(server_response)
                            if server_response['state'] == 'True':
                                exist_file_size = server_response['position']
                                f = open(filepath, 'rb')
                                f.seek(exist_file_size, 0)
                                send_size = exist_file_size
                                for line in f:
                                    send_size += len(line)
                                    self.client.send(line)
                                    processbar(send_size, filesize)
                                else:
                                    print('
    ', '文件传输完毕')
                                    f.close()
                                    server_response = self.client.recv(1024).decode('utf-8')
                                    print(server_response)
                            else:
                                print(server_response['content'])
                        else:
                            self.client.send(b'no need to do anything')  ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
                            print('文件未上传')
                    else:
                        self.client.send(b'client have ready to send')  ####发送确认信号,防止粘包,代号:P01
                        server_response = self.client.recv(1024).decode('utf-8')
                        print(server_response)  ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消
                        if server_response == 'begin':
                            f = open(filepath, 'rb')
                            send_size = 0
                            for line in f:
                                send_size += len(line)
                                self.client.send(line)
                                processbar(send_size, filesize)
                            else:
                                print('
    ', "file upload success...")
                                f.close()
                                server_response = self.client.recv(1024).decode('utf-8')
                                print(server_response)
                else:
                    print(filename, 'is not exist')
            else:
                self.help()
    
        def close(self):
            self.client.close()
    client.py

    2.服务端

    # Author:Zheng Na
    
    ####os.path.abspath(__file__) 获取当前当前文件的绝对路径
    ####os.path.dirname()获取当前文件上一层目录
    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  ####获取当前文件的上一级的上一级目录
    sys.path.append(BASE_DIR)
    
    
    import socketserver
    from core.server import MyTCPHandler
    from core.usermanagement import UserOpr
    
    if __name__ == '__main__':
    
        mainpage = '''
        主页
            1、启动服务器
            2、进入用户管理
            退出请按q
        '''
    
        while True:
            print('33[1;35m{}33[0m'.format(mainpage))
            choice = input('>>>:')
            if choice == 'q':
                exit()
            elif choice == '1':
                HOST, PORT = "localhost", 9999
                server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
                server.serve_forever()
            elif choice == '2':
                useropr = UserOpr()
                # useropr.query_all_user()  ####查询所有用户信息
                useropr.interactive()
            else:
                print("33[1;31m输入错误,请重新输入33[0m")
                continue
    main.py
    ####用户端配置文件####
    [DEFAULT]
    logfile = ../log/server.log
    usermgr_log = ../log/usermgr.log
    upload_dir = ../user_files
    userinfo_dir = ../user_info
    
    ####日志文件位置####
    [log]
    logfile = ../log/server.log
    usermgr_log = ../log/usermgr.log
    
    ####上传文件存放位置####
    [upload]
    upload_dir = ../user_files
    
    ####用户信息存放位置####
    [userinfo]
    userinfo_dir = ../user_info
    server.conf
    #Author:Zheng Na
    
    import os,configparser,logging
    
    ####读取配置文件####
    base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  ####获取当前文件的上一级的上一级目录
    config_file = os.path.join(base_dir, 'conf/server.conf')  #####将2个路径组合后返回
    cf = configparser.ConfigParser()
    cf.read(config_file,encoding='utf-8')  # 不编码会报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 12: illegal multibyte sequence
    
    ####设定日志目录####
    '''先判断日志文件是否存在,如果不存在,则创建'''
    if os.path.exists(cf.get('log', 'usermgr_log')):
        usermgr_log = cf.get('log', 'usermgr_log')
    else:
        usermgr_log = os.path.join(base_dir, 'log/usermgr.log')
    
    ####设定用户上传文件目录,这边用于创建用户家目录使用####
    if os.path.exists(cf.get('upload', 'upload_dir')):
        file_dir = cf.get('upload', 'upload_dir')
    else:
        file_dir = os.path.join(base_dir, 'user_files')
    
    ####设定用户信息存储位置####
    if os.path.exists(cf.get('userinfo', 'userinfo_dir')):
        userinfo_dir = cf.get('userinfo', 'userinfo_dir')
    else:
        userinfo_dir = os.path.join(base_dir, 'user_info')
    
    ####设置日志格式####
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S',
                        filename=usermgr_log,
                        filemode='a+')
    settings.py
    #Author:Zheng Na
    
    import os,json
    import core.settings
    
    def query_user(username):  ####查询用户
        filelist = os.listdir(core.settings.userinfo_dir)  ####列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
        dict = {}
        for filename in filelist:
            with open (os.path.join( core.settings.userinfo_dir,filename),'r',encoding='utf-8') as f:
                content = json.load(f)  ####json反序列化
                if content['username'] == username:
                    dict = {'filename':filename,'content':content}
                    # print("查询结果:",dict)
                    return dict
    common.py
    # Author:Zheng Na
    
    import socketserver,sys,json,os,time,shutil
    import core.common
    
    def processbar(part, total):  ####进度条,运行会导致程序变慢
        if total != 0:
            done = int(50 * part / total)
            sys.stdout.write("
    [%s%s]" % ('' * done, '  ' * (50 - done)))  ####注意:一个方块对应2个空格
            sys.stdout.write('{:.2%}'.format(part / total)+' '*3+str(part)+'/'+str(total))
            sys.stdout.flush()
    
    def timestamp_to_formatstringtime(timestamp):  ####时间戳转化为格式化的字符串
        structtime = time.localtime(timestamp)
        formatstringtime = time.strftime("%Y%m%d %H:%M:%S",structtime)
        return formatstringtime
    
    class MyTCPHandler(socketserver.BaseRequestHandler):
    
        def handle(self):
            auth_tag = False
            while auth_tag != True:
                auth_result = self.auth()  ####用户认证,如果通过,返回用户名,不通过为None
                print("the authentication result is:",auth_result)
                if auth_result != None:
                    self.username = auth_result['content']['username']
                    self.spacesize = auth_result['content']['spacesize']
                    auth_tag = True
                    print(self.username,self.spacesize)
                    user_homedir = os.path.join(core.settings.file_dir,self.username)
                    if os.path.isdir(user_homedir):
                        self.position = user_homedir  ####定锚,用户家目录
                        print(self.position)
    
            while True:
                print("当前连接:",self.client_address)
                self.data = self.request.recv(1024).strip()
                print(self.data.decode())
                # logging.info(self.client_address)
                if not self.data:
                    print(self.client_address, "断开了")
                    break
                cmd_dic = json.loads(self.data.decode('utf-8'))
                # print(cmd_dic)
                action = cmd_dic["action"]
                # logging.info(cmd_dic)
                if hasattr(self, action):
                    func = getattr(self, action)
                    func(cmd_dic)
                else:
                    print("未支持指令:",action)
                    # logging.info('current direcory: %s' % self.positoion)
    
        def auth(self):  ####用户认证
            self.data = json.loads(self.request.recv(1024).decode('utf-8'))
            print(self.data)
            recv_username = self.data['username']
            recv_password = self.data['password']
            query_result = core.common.query_user(recv_username)
            print(query_result)
            if query_result == None:
                self.request.send(b'user does not exist')
            elif query_result['content']['password'] == recv_password:
                self.request.send(b'ok')
                return query_result  ####返回查询结果
            elif query_result['content']['password'] != recv_password:
                self.request.send(b'password error')
            else:
                self.request.send(b'unkonwn error')
    
        def pwd(self,*args):  ####查看当前目录
            current_position = self.position
            result = current_position.replace(core.settings.file_dir,'')  ####截断目录信息,使用户只能看到自己的家目录信息
            print(result)
            self.request.send(json.dumps(result).encode('utf-8'))
    
        def ls(self,*args):  ####列出当前目录下的所有文件信息,类型,字节数,生成时间
            result = ['%-20s%-7s%-10s%-23s' % ('filename', 'type', 'bytes', 'creationtime')]  ####信息标题 #没看懂
            for f in os.listdir(self.position):
                f_abspath = os.path.join(self.position,f)  ####给出文件的绝对路径,不然程序会找不到文件
                if os.path.isdir(f_abspath):
                    type = 'd'
                elif os.path.isfile(f_abspath):
                    type = 'f'
                else:
                    type = 'unknown'
                fsize = os.path.getsize(f_abspath)
                ftime = timestamp_to_formatstringtime(os.path.getctime(f_abspath))
                result.append('%-20s%-7s%-10s%-23s' % (f,type,fsize,ftime))
            self.request.send(json.dumps(result).encode('utf-8'))
    
        def du_calc(self): # 注意不能使用os.path.getsize('D:python-studys14')返回的是所有目录大小的和
            '''统计纯文件和目录占用空间大小,结果小于在OS上使用du -s查询,因为有一些(例如'.','..')隐藏文件未包含在内'''
            totalsize = 0
            if os.path.isdir(self.position):
                dirsize,filesize = 0,0
                for root,dirs,files in os.walk(self.position):
                    for d in dirs:              #计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加
                        dirsize += os.path.getsize(os.path.join(root,d))
                    for f in files:             #计算文件占用空间
                        filesize += os.path.getsize(os.path.join(root,f))
                totalsize = dirsize + filesize
                return totalsize
    
        def du(self,*args):  ####查看当前目录大小
            totalsize = self.du_calc()
            result =  'current directory total sizes: %d' % totalsize
            print(result)
            self.request.send(json.dumps(result).encode('utf-8'))
            return totalsize
    
        def cd(self,*args):  ####切换目录,这个函数实在是没怎么看懂
            print(*args)
            user_homedir = os.path.join(core.settings.file_dir,self.username)
            cmd_dic = args[0]
            error_tag = False
            '''判断目录信息'''
            if cmd_dic['dirname'] == '':
                self.position = user_homedir
            elif cmd_dic['dirname'] in ('.','/') or '//' in cmd_dic['dirname']:  ####'.','/','//','///+'匹配
                pass
            elif cmd_dic['dirname'] == '..':
                if user_homedir != self.position and user_homedir in self.position:  ####当前目录不是家目录,并且当前目录是家目录下的子目录
                    self.position = os.path.dirname(self.position)
            elif '.' not in cmd_dic['dirname'] and os.path.isdir(os.path.join(self.position,cmd_dic['dirname'])):####'.' not in cmd_dict['dir'] 防止../..输入
                self.position = os.path.join(self.position,cmd_dic['dirname'])
            else:
                error_tag = True
    
            if error_tag:
                result = 'Error,%s is not path here,or path does not exist!' % cmd_dic['dirname']
                self.request.send(json.dumps(result).encode('utf-8'))
            else:
                self.pwd()
    
        def mkdir(self,*args):  ####创建目录
            try:
                dirname = args[0]['dirname']
                if dirname.isalnum():  ####判断文件是否只有数字和字母
                    if os.path.exists(os.path.join(self.position,dirname)):
                        result = 's% have existed' % dirname
                    else:
                        os.mkdir(os.path.join(self.position,dirname))
                        result = '%s created success' % dirname
                else:
                    result = 'Illegal character %s, dirname can only by string and num here.' % dirname
            except TypeError:
                result = 'please input dirname'
    
            self.request.send(json.dumps(result).encode('utf-8'))
    
        def rm(self,*args):  ####删除文件
            filename = args[0]['filename']
            confirm = args[0]['confirm']
            file_abspath = os.path.join(self.position,filename)
            if os.path.isfile(file_abspath):
                if confirm == True:
                    os.remove(file_abspath)
                    result = "%s have been deleted." % filename
                else:
                    result = 'Not file deleted'
            elif os.path.isdir(file_abspath):
                result = '%s is a dir,please use rmdir' % filename
            else:
                result = 'File %s not exist!' % filename
            self.request.send(json.dumps(result).encode('utf-8'))
    
        def rmdir(self,*args):
            dirname = args[0]['dirname']
            confirm = args[0]['confirm']
            dir_abspath = os.path.join(self.position,dirname)
            if '.' in dirname or '/' in dirname:  ####不能跨目录删除
                result = 'should not rmdir %s this way' % dirname
            elif os.path.isdir(dir_abspath):
                if confirm == True:
                    shutil.rmtree(dir_abspath)
                    result = '%s have been deleted.' % dirname
                else:
                    result = 'Not dir deleted.'
            elif os.path.isfile(dir_abspath):
                result = '%s is a file,please use rm' % dirname
            else:
                result = 'directory %s not exist!' % dirname
            self.request.send(json.dumps(result).encode('utf-8'))
    
        def mv(self,*args):  ####实现功能:移动文件,移动目录,文件重命名,目录重命名
            try:
                print(args)
                objname = args[0]['objname']
                dstname = args[0]['dstname']
                obj_abspath = os.path.join(self.position,objname)
                dst_abspath = os.path.join(self.position,dstname)
                if os.path.isfile(obj_abspath):
                    if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath):
                        shutil.move(obj_abspath,dst_abspath)
                        result = 'move success'
                    elif os.path.isfile(dst_abspath):
                        result = 'moving cancel,file has been exist.'
                elif os.path.isdir(obj_abspath):
                    if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath):
                        shutil.move(obj_abspath,dst_abspath)
                        result = 'move success'
                    elif os.path.isfile(dst_abspath):
                        result = 'moving cancel,%s is a file.'% dst_abspath
                else:
                    result = 'nothing done'
                self.request.send(json.dumps(result).encode('utf-8'))
            except Exception as e:
                print(e)
                result = 'moving fail,' + e
                self.request.send(json.dumps(result).encode('utf-8'))
    
        def get(self,*args):  ####发送给客户端文件
            cmd_dic = args[0]
            filename = cmd_dic['filename']
            filepath = os.path.join(self.position, filename)
            if os.path.isfile(filepath):
                filesize = os.path.getsize(filepath)
                ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
                # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
                filemd5 = 'a'  ####Windows测试用
                msg = {
                    'action': 'get',
                    'filename': filename,
                    'filesize': filesize,
                    'filemd5': filemd5,
                    'override': 'True'
                }
                # print(msg)
                self.request.send(json.dumps(msg).encode('utf-8'))
                '''接下来发送文件给客户端'''
                self.request.recv(1024)  ####接收ACK信号,下一步发送文件
                f = open(filepath, 'rb')
                send_size = 0
                for line in f:
                    send_size += len(line)
                    self.request.send(line)
                    # processbar(send_size, filesize)     ####服务端进度条,不需要可以注释掉
                else:
                    print('文件传输完毕')
                    f.close()
            else:
                print(filepath, '文件未找到')
                self.request.send(json.dumps('Filenotfound').encode('utf-8'))
    
        def put(self, *args):  ####接收客户端文件
            cmd_dic = args[0]
            filename = os.path.basename(cmd_dic['filename'])  ####传输进来的文件名可能带有路径,将路径去掉
            filesize = cmd_dic['filesize']
            filemd5 = cmd_dic['filemd5']
            override = cmd_dic['override']
            receive_size = 0
            file_path = os.path.join(self.position, filename)
            if override != 'True' and os.path.exists(file_path):  ####检测文件是否已经存在
                self.request.send(b'file have exits, do nothing!')
            else:
                if os.path.isfile(file_path):  ####如果文件已经存在,先删除,再计算磁盘空间大小
                    os.remove(file_path)
                current_size = self.du()  ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
                self.request.recv(1024)  ####接收客户端ack信号,防止粘包,代号:P01
                print(self.spacesize, current_size, filesize)
                if self.spacesize >= current_size + filesize:
                    self.request.send(b'begin')  ####发送开始传输信号
                    f = open(file_path, 'wb')
    
                    while filesize > receive_size:
                        if filesize - receive_size > 1024:
                            size = 1024
                        else:
                            size = filesize - receive_size
                        data = self.request.recv(size)
                        f.write(data)
                        receive_size += len(data)
                        # print(receive_size,len(data))   ####打印每次接收的数据
                        # processbar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
                    else:
                        print("file [%s] has uploaded..." % filename)
                        f.close()
                    # receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
                    receive_filemd5 = 'a'  ####windows 测试用
                    print('
    ', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                    if receive_filemd5 == filemd5:
                        self.request.send(b'file received successfully!')
                    else:
                        self.request.send(b'Error, file received have problems!')
                else:
                    self.request.send(
                        b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
                            self.spacesize,current_size, self.spacesize - current_size, filesize))
    
        def newget(self, *args):  ####发送给客户端文件,具有断点续传功能
            # print('get receive the cmd',args[0])
            cmd_dic = args[0]
            filename = cmd_dic['filename']
            send_size = cmd_dic['filesize']
            print(filename)
            # self.request.send(b'server have been ready to send')  ####发送ACK
            file_path = os.path.join(self.position, filename)
            if os.path.isfile(file_path):
                filesize = os.path.getsize(file_path)
                ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
                # filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
                filemd5 = 'a' #Windows测试用
                msg = {
                    'action': 'newget',
                    'filename': filename,
                    'filesize': filesize,
                    'filemd5': filemd5,
                }
                print(msg)
                self.request.send(json.dumps(msg).encode('utf-8'))
                self.request.recv(1024)  ####接收ACK信号,下一步发送文件
                f = open(file_path, 'rb')
                f.seek(send_size,0)
                for line in f:
                    send_size += len(line)
                    self.request.send(line)
                    # processbar(send_size, filesize)     ####服务端进度条,不需要可以注释掉
                else:
                    print('文件传输完毕')
                    f.close()
    
            else:
                print(file_path, '文件未找到')
                self.request.send(json.dumps('Filenotfound').encode('utf-8'))
    
        def newput(self, *args):  ####接收客户端文件,具有断点续传功能
            cmd_dict = args[0]
            filename = os.path.basename(cmd_dict['filename'])  ####传输进来的文件名可能带有路径,将路径去掉
            filesize = cmd_dict['filesize']
            filemd5 = cmd_dict['filemd5']
            tag = cmd_dict['tag']
            receive_size = 0
            file_path = os.path.join(self.position, filename)
            if os.path.isfile(file_path):  ####检测文件是否已经存在
                self.request.send('文件存在'.encode())
                tag = self.request.recv(1024).decode()  ####接收客户端ack信号
                if tag == 'o':
                    os.remove(file_path)####如果文件已经存在,先删除,再计算磁盘空间大小
                    self.upload(tag,filename, filemd5, filesize, file_path, receive_size)
                elif tag == 'r':
                    exist_file_size = os.path.getsize(file_path)
                    if exist_file_size <= filesize:
                        receive_size = exist_file_size
                        self.upload(tag,filename, filemd5, filesize, file_path, receive_size)
                    else:
                        print('服务器已存在同名文件且比原文件大')
                        msg = {
                            "content": '服务器已存在同名文件且比原文件大'
                        }
                        self.request.send(json.dumps(msg).encode('utf-8'))
                else:
                    msg = {
                        "content": '文件未上传'
                    }
                    self.request.send(json.dumps(msg).encode('utf-8'))
            else:  ####文件不存在:如果文件不存在的话,就不用管tag了,直接计算磁盘空间,然后上传
                self.request.send('文件不存在!'.encode())
                tag = self.request.recv(1024).decode()  ####接收客户端ack信号
                self.upload(tag,filename,filemd5,filesize,file_path,receive_size)
    
        def upload(self,tag,filename,filemd5,filesize,file_path,receive_size):
            current_size = self.du_calc()
            print('用户总空间:',self.spacesize, '目前剩余空间:',current_size,'文件大小:', filesize)
            if tag == 'r':
                needrecv_size = filesize - receive_size
            else:
                needrecv_size = filesize
            if self.spacesize >= current_size + needrecv_size:
                msg = {
                    "position":receive_size,
                    "content":'begin'
                }
                self.request.send(json.dumps(msg).encode('utf-8'))   ####发送开始传输信号
                if tag == 'r':
                    f = open(file_path, 'ab')
                else:
                    f = open(file_path, 'wb')
                while filesize > receive_size:
                    if filesize - receive_size > 1024:
                        size = 1024
                    else:
                        size = filesize - receive_size
                    data = self.request.recv(size)
                    f.write(data)
                    receive_size += len(data)
                    # processbar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
                f.close()
                # receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
                receive_filemd5 = 'a'
                print('
    ', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                if receive_filemd5 == filemd5:
                    self.request.send(b'file received successfully!')
                else:
                    self.request.send(b'Error, file received have problems!')
            else:
                msg = {
                    "content":'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
                        self.spacesize, current_size, self.spacesize - current_size, filesize)
                }
                self.request.send(json.dumps(msg).encode('utf-8'))   ##
    
        def newput2(self, *args):  ####接收客户端文件,具有断点续传功能
            cmd_dict = args[0]
            filename = os.path.basename(cmd_dict['filename'])  ####传输进来的文件名可能带有路径,将路径去掉
            filesize = cmd_dict['filesize']
            filemd5 = cmd_dict['filemd5']
            override = cmd_dict['override']
            receive_size = 0
            file_path = os.path.join(self.position, filename)
            # print(file_path,os.path.isdir(file_path))
            if override != 'True' and os.path.exists(file_path):  ####检测文件是否已经存在
                if os.path.isdir(file_path):
                    self.request.send(b'file have exits, and is a directory, do nothing!')
                elif os.path.isfile(file_path):
                    self.request.send(b'file have exits, do nothing!')
                    resume_signal = self.request.recv(1024)     ####接收客户端发来的是否从文件断点续传的信号
                    if resume_signal == b'ready to resume from break point':           ####执行断点续传功能
                        exist_file_size = os.path.getsize(file_path)
                        current_size = self.du()
                        time.sleep(0.5) ####防止粘包
                        print('用户空间上限:%d, 当前已用空间:%d, 已存在文件大小:%d, 上传文件大小:%d ' % (self.spacesize,current_size,exist_file_size,filesize))
                        if self.spacesize >= (current_size - exist_file_size + filesize):  ####判断剩余空间是否足够
                            if exist_file_size < filesize:
                                receive_size = exist_file_size
                                print('服务器上已存在的文件大小为:',exist_file_size)
                                msg = {
                                    'state': True,
                                    'position': exist_file_size,
                                    'content': 'ready to receive file'
                                }
                                self.request.send(json.dumps(msg).encode('utf-8'))
                                f = open(file_path, 'ab+')
                                while filesize > receive_size:
                                    if filesize - receive_size > 1024:
                                        size = 1024
                                    else:
                                        size = filesize - receive_size
                                    data = self.request.recv(size)
                                    f.write(data)
                                    receive_size += len(data)
                                    # print(receive_size,len(data))   ####打印每次接收的数据
                                    # processbar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
    
                                f.close()
                                receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
                                print('
    ', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                                if receive_filemd5 == filemd5:
                                    self.request.send(b'file received successfully!')
                                else:
                                    self.request.send(b'Error, file received have problems!')
    
                            else:       ####如果上传的文件小于当前服务器上的文件,则为同名但不同文件,不上传。实际还需要增加其他判断条件,判断是否为同一文件。
                                msg = {
                                    'state': False,
                                    'position': '',
                                    'content': 'Error, file mismatch, do nothing!'
                                }
                                self.request.send(json.dumps(msg).encode('utf-8'))
                        else:       ####如果续传后的用户空间大于上限,拒接续传
                            msg = {
                                'state': False,
                                'position':'',
                                'content':'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, need_size:%d' % (self.user_spacesize, current_size, self.user_spacesize - current_size, filesize - exits_file_size)
                            }
                            self.request.send(json.dumps(msg).encode('utf-8'))
                    else:
                        pass
    
            else:
                if os.path.isfile(file_path):  ####如果文件已经存在,先删除,再计算磁盘空间大小
                    os.remove(file_path)
                current_size = self.du()  ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
                self.request.recv(1024)  ####接收客户端ack信号,防止粘包,代号:P01
                print(self.spacesize, current_size, filesize)
                if self.spacesize >= current_size + filesize:
                    self.request.send(b'begin')  ####发送开始传输信号
                    fk = open(file_path, 'wb')
                    while filesize > receive_size:
                        if filesize - receive_size > 1024:
                            size = 1024
                        else:
                            size = filesize - receive_size
                        data = self.request.recv(size)
                        fk.write(data)
                        receive_size += len(data)
                        # print(receive_size,len(data))   ####打印每次接收的数据
                        # processbar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
    
                    fk.close()
                    receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
                    print('
    ', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
                    if receive_filemd5 == filemd5:
                        self.request.send(b'file received successfully!')
                    else:
                        self.request.send(b'Error, file received have problems!')
                else:
                    self.request.send(
                        b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
                        self.spacesize, current_size, self.spacesize - current_size, filesize))
    server.py
    #Author:Zheng Na
    import os,time,json,shutil,hashlib
    import core.common
    
    
    def hashmd5(self, *args):
        m = hashlib.md5()
        m.update(str(*args).encode())
        ciphertexts = m.hexdigest()  # 密文
        return ciphertexts
    
    # 用户操作类
    class UserOpr(object):
        def __init__(self):
            pass
    
        def query_userinfo(self,username):
            query_result = core.common.query_user(username)
            if query_result != None: # 用户存在
                print(query_result)
            else:
                print("用户不存在")
    
        def save_userinfo(self,username):             # 保存用户信息
            query_result = core.common.query_user(username)  # 检查是否已存在同名用户,如果没有查询结果应该为None
            if query_result == None:                 # 用户不存在
                id = time.strftime('%Y%m%d%H%M%S', time.localtime()) # 将结构化时间(即元组)转换成格式化的字符串,比如20181211110148
                password = '123456'
                userinfo = {
                    'username':username,
                    'id':id,
                    'phonenumber':'',
                    'password':hashmd5(password),
                    'spacesize': 104857600, ## 初始分配100MB存储空间
                    'level':1 # 会员等级,初始为1,普通会员
                }
    
                with open(os.path.join(core.settings.userinfo_dir,id),'w',encoding='utf-8') as f:
                    json.dump(userinfo,f)
                    print("用户信息保存完毕")
                    try:                        # 创建用户家目录
                        os.mkdir(os.path.join(core.settings.file_dir,username))
                        print('用户目录创建成功!')
                    except Exception as e:
                        print('用户目录创建失败!',e)
    
            else:
                print("用户名重复,信息未保存")
    
    
        def change_userinfo(self,username): # 修改用户信息
            query_result = core.common.query_user(username)  # 检测用户是否存在,不存在不处理
            if query_result != None:                 # 用户存在
                filename = query_result['filename']
                userinfo = query_result['content']
                print('before update: ', userinfo)
                update_item = input("请输入要修改的项目,例如password,phonenumber,spacesize,level:")
    
                if update_item in ('username','id'):
                    print(update_item, "项不可更改")
                elif update_item in ('password','phonenumber','spacesize','level'):
                    print("update item: %s" % update_item)
                    update_value = input("请输入要修改的项目的新值:")
                    if update_item == 'password':
                        userinfo[update_item] = hashmd5(update_value)
                    else:
                        userinfo[update_item] = update_value
                    with open(os.path.join(core.settings.userinfo_dir, filename), 'w', encoding='utf-8') as f:
                        json.dump(userinfo, f)
                        print(update_item, "项用户信息变更保存完毕")
                        print('after update: ', userinfo)
                else:
                    print('输入信息错误,', update_item, '项不存在')
            else:
                print('用户不存在,无法修改')
    
        def delete_user(self,username):
            query_result = core.common.query_user(username)  # 检测用户是否存在,不存在不处理
            if query_result != None:                 # 用户存在
                filename = query_result['filename']
                userfile_path = os.path.join(core.settings.userinfo_dir,filename)
                os.remove(userfile_path)
                query_result_again = core.common.query_user(username)
                if query_result_again == None:
                    print('用户信息文件删除成功!')
                    try:
                        shutil.rmtree(os.path.join(core.settings.file_dir,username))
                        print('用户家目录删除成功')
                    except Exception as e:
                        print('用户家目录删除失败:',e)
                else:
                    print('用户信息文件删除失败!')
            else:
                print('用户不存在或者已经被删除')
    
        def query_all_user(self):           # 查询所有用户信息,用于调试使用
            filelist = os.listdir(core.settings.userinfo_dir)
            if filelist != []:
                for filename in filelist:
                    with open(os.path.join(core.settings.userinfo_dir,filename),'rb') as f:
                        userinfo = json.load(f)
                        print(filename,userinfo)
            else:
                print("用户信息为空")
    
        def interactive(self):
            userpage = '''
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             '''
    
            userpage_data = {
                '1': 'save_userinfo',
                '2': 'query_userinfo',
                '3': 'change_userinfo',
                '4': 'delete_user'
            }
    
            while True:
                print('33[1;35m{}33[0m'.format(userpage))
                choice = input('请输入你的选择:').strip()
    
                if choice == 'q':
                    exit("退出程序!")
                elif choice == 'r':
                    break
                elif choice in userpage_data:
                    username = input("请输入用户名:").strip()
                    if username == '':
                        print('用户不能为空')
                        continue
                    if hasattr(self,userpage_data[choice]):
                        f = getattr(self, userpage_data[choice])
                        f(username)
                else:
                    print("33[1;31m输入错误,请重新输入33[0m")
                    continue
    usermanagement.py

    运行示例:

    D:softwarePython3.6.5python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py
    
        主页
            1、启动服务器
            2、进入用户管理
            退出请按q
        
    >>>:2
    
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             
    请输入你的选择:1
    请输入用户名:xiaoming
    用户信息保存完毕
    用户目录创建成功!
    
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             
    请输入你的选择:2
    请输入用户名:xiaoming
    {'filename': '20181220164706', 'content': {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}}
    
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             
    请输入你的选择:3
    请输入用户名:xiaoming
    before update:  {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}
    请输入要修改的项目,例如password,phonenumber,spacesize,level:phonenumber
    update item: phonenumber
    请输入要修改的项目的新值:1234567890
    phonenumber 项用户信息变更保存完毕
    after update:  {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '1234567890', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}
    
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             
    请输入你的选择:4
    请输入用户名:xiaoming
    用户信息文件删除成功!
    用户家目录删除成功
    
            用户管理界面
                1、新增用户
                2、查询用户
                3、修改用户
                4、删除用户
                退出请按q
                返回上一界面请按r
             
    请输入你的选择:q
    退出程序!
    
    Process finished with exit code 1
    用户管理程序运行示例
    D:softwarePython3.6.5python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py
    
        主页
            1、启动服务器
            2、进入用户管理
            退出请按q
        
    >>>:1
    
    
    
    
    D:softwarePython3.6.5python.exe D:/python-study/s14/Day08/ftp/ftp_client/bin/main.py
    请输入用户名>>>:zhengna
    请输入密码>>>:123123
    认证通过!
    zhengna
    >> pwd
    zhengna
    zhengna
    >> ls
    filename            type   bytes     creationtime           
    test                d      0         20181214 17:17:05      
    test.txt            f      5028331   20181220 15:43:55      
    vedio2.avi          f      86453774  20181214 17:17:35      
    zhengna
    >> du
    current directory total sizes: 96510422
    zhengna
    >> cd test
    zhengna	est
    zhengna	est
    >> ls
    filename            type   bytes     creationtime           
    test2               d      0         20181217 11:21:07      
    zhengna	est
    >> rmdir test2
    test2 have been deleted.
    zhengna	est
    >> cd ..
    zhengna
    zhengna
    >> rm test
    test is a dir,please use rmdir
    zhengna
    >> rm test.txt
    test.txt have been deleted.
    zhengna
    >> mkdir aa
    aa created success
    zhengna
    >> mv aa bb
    move success
    zhengna
    >> put test.ttx
    test.ttx is not exist
    zhengna
    >> put test.txt
    begin
    [██████████████████████████████████████████████████]100.00%   5028331/5028331
     file upload success...
    file received successfully!
    zhengna
    >> put test.txt 
    文件已存在,要覆盖文件请输入yes >>>:yes
    begin
    [██████████████████████████████████████████████████]100.00%   5028331/5028331
     file upload success...
    file received successfully!
    zhengna
    >> put test.txt True
    begin
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     file upload success...
    file received successfully!
    zhengna
    >> get test.ttx
    File no found!
    zhengna
    >> get test.txt
    {'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'}
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     test.txt md5: a 原文件md5: a
    文件接收完成!
    zhengna
    >> get test.txt
    文件已存在,要覆盖文件请输入yes >>>:yes
    begin
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     file upload success...
    file received successfully!
    zhengna
    >> get test.txt True
    {'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'}
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     test.txt md5: a 原文件md5: a
    文件接收完成!
    zhengna
    >> newput test.txt
    文件存在
    文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o
    0
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     file upload success...
    file received successfully!
    zhengna
    >> newput test.txt
    文件存在
    文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r
    11178154
    
     file upload success...
    file received successfully!
    zhengna
    >> newget test.txt
    文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o
    {'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'}
    [██████████████████████████████████████████████████]100.00%   11178154/11178154
     test.txt md5: a 原文件md5: a
    文件接收完成!
    zhengna
    >> newget test.txt 
    文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r
    {'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'}
    
     test.txt md5: a 原文件md5: a
    文件接收完成!
    zhengna
    >> newput vedio.avi
    文件不存在!
    Error, disk space do not enough! Nothing done! Total: 104857600, current: 97631928, rest:7225672, filesize:86453774
    zhengna
    >> 
    主程序运行示例

    参考:http://blog.51cto.com/tryagaintry/1969589

    总结:这是我第一次写一个这么复杂的程序,虽然大多数的代码都是参考别人写好的。实现它我大概用了2周左右的时间,在这过程中,我一直都在努力思考,尽量让自己弄明白每段代码实现了什么功能?为什么这么写?有没有更好的实现方式?我知道最终的程序并不完美,但是对我来说,重要的不是我在上方贴的大段大段的代码,而是在这2周码代码的过程中,我从中学到了什么。

  • 相关阅读:
    自定义CopyOnWriteHashMap
    NIO中Buffer缓冲区的实现
    TOMCAT原理详解及请求过程
    XSS的原理分析与解剖
    mysql分页查询优化
    java如何正确停止一个线程
    Centos搭建ElasticSearch
    redis集群原理
    Idea-每次修改JS文件都需要重启Idea才能生效解决方法
    java 加密 解密 Illegal key size
  • 原文地址:https://www.cnblogs.com/zhengna/p/10081941.html
Copyright © 2020-2023  润新知