zoukankan      html  css  js  c++  java
  • 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周码代码的过程中,我从中学到了什么。

  • 相关阅读:
    背水一战 Windows 10 (61)
    背水一战 Windows 10 (60)
    背水一战 Windows 10 (59)
    背水一战 Windows 10 (58)
    背水一战 Windows 10 (57)
    背水一战 Windows 10 (56)
    背水一战 Windows 10 (55)
    背水一战 Windows 10 (54)
    背水一战 Windows 10 (53)
    背水一战 Windows 10 (52)
  • 原文地址:https://www.cnblogs.com/zhengna/p/10081941.html
Copyright © 2011-2022 走看看