可参考
https://www.cnblogs.com/Dominic-Ji/p/10897142.html
1.项目需求分析:
- 管理员
1 注册
2 登录
3 上传视频
4 删除视频
5 发布公告
- 用户
1 注册
2 登录
3 冲会员
4 查看视频
5 下载免费视频
6 下载收费视频
7 查看下载记录
8 查看公告
2.程序的架构设计
架构图:
- 三层架构 - 用户视图层 与用户交互的 - 接口层 处理核心业务逻辑 - 数据层 对数据进行存取 - ATM: - 存: 序列化 dict ----> json ----> data.json - 取: 反序列化 data.json ----> json ----> dict - 优点: 可跨平台 - 缺点: 不能存对象 - 选课系统 - 存: 序列化 object ----> pickle(bytes) ----> data.pickle - 取: 反序列化 data.pickle ----> pickle(bytes) ----> object - 优点: 可以存对象 - 缺点: 不可跨平台 - 仿优酷系统 - ORM: - 存: object ----> MySQL - 取: MySQL ----> [{}, {}] ----> [object, object] object.属性 object.方法 - pymysql - cursor.execute('sql语句') # select() 查询数据 - object.select() # ----> select * from ... # save() 插入数据 - object.save() # ----> insert into .....动态 sql # update() 更新数据 - object.update() # ----> update tab set .... - MySQL: - json ----> dict - dict.属性 - dict.get('key') - dict['key']
3.亮代码
客户端
conf/settings
import os BASE_PATH = os.path.dirname(os.path.dirname(__file__)) UPLOAD_MOVIE_DIR = os.path.join( BASE_PATH, 'upload_movie_dir' ) DOWNLOAD_MOVIE_DIR = os.path.join( BASE_PATH, 'download_movie_dir' )
core/admin
''' 管理员视图 ''' from lib import common from conf import settings import os user_info = { 'cookies': None } # 管理员注册视图 def register(client): while True: username = input('请输入用户名: ').strip() password = input('请输入密码: ').strip() re_password = input('请确认密码: ').strip() if password == re_password: # 一 组织客户端的注册数据 send_dic = { 'username': username, 'password': password, 'user_type': 'admin', 'func_type': 'register' } back_dic = common.send_and_back(send_dic, client) # 判断服务端返回来的字典是否注册成功,并打印消息 if back_dic.get('flag'): print(back_dic.get('msg')) break else: print(back_dic.get('msg')) # 管理员登录 def login(client): while True: username = input('请输入用户名: ').strip() password = input('请输入密码: ').strip() # 1.组织数据发送给服务端 send_dic = { 'username': username, 'password': password, 'func_type': 'login' } # 调用commin中的send_and_back与服务端交互 back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('msg')) user_info['cookies'] = back_dic.get('session') break else: print(back_dic.get('msg')) # 管理员上传电影 def upload_movie(client): while True: upload_movie_dir = settings.UPLOAD_MOVIE_DIR # 1.打印上传电影目录所有的电影文件,让用户选择 movie_list = os.listdir(upload_movie_dir) if not movie_list: print('没有可上传的电影!!!') break for index, movie_name in enumerate(movie_list): print(index, movie_name) choice = input('请输入上传电影编号: ').strip() if choice == 'q': break if not choice.isdigit(): continue choice = int(choice) if choice not in range(len(movie_list)): continue movie_name = movie_list[choice] # 2.获取真实电影数据 movie_path = os.path.join(upload_movie_dir, movie_name) # 3.组织需要上传的电影信息发送给服务端 movie_size = os.path.getsize(movie_path) # 4.获取电影md5值,发送给服务端,校验电影是否存在 movie_md5 = common.get_movie_md5(movie_path) # 5.先发送检测电影是否存在数据给服务端 send_dic = { 'func_type': 'check_movie', 'movie_md5': movie_md5, 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) # 6.校验back_dic中返回的信息,若电影不存在则,发送上传电影功能字典 if back_dic.get('flag'): # 7.管理员设置上传的电影是否免费 choice2 = input('是否免费(y or n)...').strip() is_free = 0 # 判断用户如果输入y 给is_free设置为1 if choice2 == 'y': is_free = 1 send_dic = { 'func_type': 'upload_movie', 'movie_name': movie_name, 'movie_size': movie_size, 'movie_md5': movie_md5, 'cookies': user_info.get('cookies'), 'is_free': is_free } # 7.发送电影信息字典数据给服务端 back_dic2 = common.send_and_back( send_dic, client, file=movie_path) if back_dic2.get('flag'): print(back_dic2.get('msg')) break else: print(back_dic.get('msg')) # 管理员删除电影 def delete_movie(client): while True: # 1.先去服务端获取可删除的电影 send_dic = { 'func_type': 'get_movie_list', 'cookies': user_info.get('cookies'), 'movie_type': 'all' } back_dic = common.send_and_back(send_dic, client) if not back_dic.get('flag'): print(back_dic.get('msg')) break # 2.打印并选择可以删除的电影 movie_list = back_dic.get('movie_list') for index, movie_name_id in enumerate(movie_list): print(index, movie_name_id) choice = input('请需要删除的电影编号: ').strip() if choice == 'q': break if not choice.isdigit(): continue choice = int(choice) if choice not in range(len(movie_list)): continue # 3.发送删除的请求给服务端,让服务端修改电影的is_delete字段即可 # movie_name_id ---> [电影名字, 电影id] movie_name_id = movie_list[choice] send_dic = { 'func_type': 'delete_movie', 'cookies': user_info.get('cookies'), 'movie_id': movie_name_id[1] # 传入唯一的电影id } back_dic2 = common.send_and_back(send_dic, client) if back_dic2.get('flag'): print(back_dic2.get('msg')) break # 管理员发布公告 def send_notice(client): # 1.让管理员输入 公告标题与内容 title = input('请输入公告标题:').strip() # 2.发送数据给服务端保存公告即可 content = input('请输入公告内容:').strip() send_dic = { 'cookies': user_info.get('cookies'), 'title': title, 'content': content, 'func_type': 'send_notice' } back_dic = common.send_and_back(send_dic, client) print(back_dic.get('msg')) func_dic = { '1': register, '2': login, '3': upload_movie, '4': delete_movie, '5': send_notice } # 管理员视图 def admin_view(client): while True: print(''' 1 注册 2 登录 3 上传视频 4 删除视频 5 发布公告 q.退出 ''') choice = input('请输入功能编号:').strip() if choice == 'q': break if choice not in func_dic: continue func_dic.get(choice)(client)
core/src
from core import admin from core import user from tcp_client import socket_client # 功能字典 func_dic = { '1': admin.admin_view, '2': user.user_view } # 总视图run函数 def run(): print('Client is running...') client = socket_client.get_client() while True: print(''' 1.管理员视图 2.普通用户视图 q.退出 ''') choice = input('请输入功能编号: ').strip() if choice == 'q': break if choice not in func_dic: continue func_dic.get(choice)(client) client.close()
core/user
from lib import common from conf import settings import time import os ''' 用户视图 ''' user_info = { 'cookies': None, 'is_vip': 0 # 默认登录前不是VIP } # 普通用户注册视图 def register(client): while True: username = input('请输入用户名: ').strip() password = input('请输入密码: ').strip() re_password = input('请确认密码: ').strip() if password == re_password: # 一 组织客户端的注册数据 send_dic = { 'username': username, 'password': password, 'user_type': 'user', 'func_type': 'register' } back_dic = common.send_and_back(send_dic, client) # 判断服务端返回来的字典是否注册成功,并打印消息 if back_dic.get('flag'): print(back_dic.get('msg')) break else: print(back_dic.get('msg')) # 普通用户登录 def login(client): while True: username = input('请输入用户名: ').strip() password = input('请输入密码: ').strip() # 1.组织数据发送给服务端 send_dic = { 'username': username, 'password': password, 'func_type': 'login' } # 调用commin中的send_and_back与服务端交互 back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('msg')) user_info['cookies'] = back_dic.get('session') is_vip = back_dic.get('is_vip') if is_vip: user_info['is_vip'] = is_vip break else: print(back_dic.get('msg')) # 普通用户充会员 def buy_vip(client): # 1.判断当前用户是否是会员 if not user_info.get('is_vip'): choice = input('请输入 y or n 确认是否购买 (购买会员5000元): ').strip() if choice == 'y': # 2.若不是会员则,发送请求让服务端修改is_vip字段 send_dic = { 'func_type': 'buy_vip', 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('msg')) login(client) else: print(back_dic.get('msg')) else: print('穷比,枪(gun)吧~') else: print('已经是会员了, 该用户已经是黑金VIP!!') # 普通用户查看电影 def check_movies(client): # 1.先去服务端获取可删除的电影 send_dic = { 'func_type': 'get_movie_list', 'movie_type': 'all', 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('movie_list')) # 普通用户下载免费电影 def download_free_movie(client): while True: # 1.先往服务端发送请求,获取所有免费电影 send_dic = { 'func_type': 'get_movie_list', 'movie_type': 'free', 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) if not back_dic.get('flag'): print(back_dic.get('msg')) break movie_list = back_dic.get('movie_list') if not movie_list: print('没有可下载的免费电影') break # 2.打印所有免费电影,并打印,让用户选择 for index, name_id_type in enumerate(movie_list): print(index, name_id_type) choice = input('请输入需要下载电影的编号: ').strip() if not choice.isdigit(): continue choice = int(choice) if choice not in range(len(movie_list)): continue movie_name, movie_id, movie_type = movie_list[choice] # print(movie_name, movie_id, movie_type) # 3.选择成功后,发送下载免费电影请求给服务端 send_dic2 = { 'func_type': 'download_movie', 'movie_name': movie_name, 'movie_id': movie_id, 'cookies': user_info.get('cookies') } back_dic2 = common.send_and_back(send_dic2, client) # 获取广告时间,并模拟广告等待 wait_time = back_dic2.get('wait_time') if wait_time: print('广告时间: {~真情求缘,重金求子~【联系热线: 18574251860】}') time.sleep(wait_time) # 0, 5 # 4.接收服务端返回的下载电影字典 movie_size = back_dic2.get('movie_size') # 5.接收电影真实数据,并保存到download_movie_dir中 movie_path = os.path.join( settings.DOWNLOAD_MOVIE_DIR, movie_name ) # 接收电影 recv_data = 0 with open(movie_path, 'wb') as f: while recv_data < movie_size: data = client.recv(1024) f.write(data) recv_data += len(data) break # 普通用户下载收费电影 def download_buy_movie(client): while True: # 1.先往服务端发送请求,获取所有免费电影 send_dic = { 'func_type': 'get_movie_list', 'movie_type': 'pay', 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) if not back_dic.get('flag'): print(back_dic.get('msg')) break movie_list = back_dic.get('movie_list') if not movie_list: print('没有可下载的收费电影') break # 2.打印所有免费电影,并打印,让用户选择 for index, name_id_type in enumerate(movie_list): print(index, name_id_type) # 交钱并选择电影, 不给钱不让选择,直接退出 choice_buy = input('请缴费---》 [普通用户购买电影(500元一部), VIP用户打骨折(50元一部)], 确认购买输入y,否则退出!: ').strip() if choice_buy == 'y': if user_info.get('is_vip'): print('会员购买成功!') else: print('普通用户购买成功!') print('准备开始选择电影啦~~~~~') else: print('qiong人走吧~~~~~') break choice = input('请输入需要下载电影的编号: ').strip() if not choice.isdigit(): continue choice = int(choice) if choice not in range(len(movie_list)): continue # 收费电影的信息 movie_name, movie_id, movie_type = movie_list[choice] # print(movie_name, movie_id, movie_type) # 3.选择成功后,发送下载免费电影请求给服务端 send_dic2 = { 'func_type': 'download_movie', 'movie_name': movie_name, 'movie_id': movie_id, 'cookies': user_info.get('cookies') } back_dic2 = common.send_and_back(send_dic2, client) # 收费电影没有广告时间 # 4.接收服务端返回的下载电影字典 movie_size = back_dic2.get('movie_size') # 5.接收电影真实数据,并保存到download_movie_dir中 movie_path = os.path.join( settings.DOWNLOAD_MOVIE_DIR, movie_name ) # 接收电影 recv_data = 0 with open(movie_path, 'wb') as f: while recv_data < movie_size: data = client.recv(1024) f.write(data) recv_data += len(data) break # 普通用户查看下载记录 def check_movie_record(client): send_dic = { 'func_type': 'check_movie_record', 'cookies': user_info.get('cookies') } back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('download_movie_list')) else: print(back_dic.get('msg')) # 普通用户查看公告 def check_notice(client): # 1.发送查看公告请求给服务端 send_dic = { 'func_type': 'check_notice', 'cookies': user_info.get('cookies') } # 2.服务端返回公告信息,并打印 back_dic = common.send_and_back(send_dic, client) if back_dic.get('flag'): print(back_dic.get('notice_list')) else: print(back_dic.get('msg')) func_dic = { '1': register, '2': login, '3': buy_vip, '4': check_movies, '5': download_free_movie, '6': download_buy_movie, '7': check_movie_record, '8': check_notice } # 管理员视图 def user_view(client): while True: print(''' 1 注册 2 登录 3 冲会员 4 查看视频 5 下载免费视频 6 下载收费视频 7 查看下载记录 8 查看公告 q.退出 ''') choice = input('请输入功能编号:').strip() if choice == 'q': break if choice not in func_dic: continue func_dic.get(choice)(client)
lib/common
import struct import hashlib import json import os # 发送与接收 def send_and_back(send_dic, client, file=None): # 1.先序列化,得到json数据,并转成bytes数据 json_bytes = json.dumps(send_dic).encode('utf-8') # 2.给bytes数据制作报头 headers = struct.pack('i', len(json_bytes)) # 二 将打包好的数据发送给服务端 # 3.先发送报头 client.send(headers) # 4.再发送真实数据 bytes数据 client.send(json_bytes) # 注意: 此处上传电影 if file: with open(file, 'rb') as f: for line in f: client.send(line) # 三 接收服务端返回的数据 # 1.先接收报头 headers = client.recv(4) print(headers, '检测报头是否有数据') # 2.解压报头,获取真实数据的长度 # struct.unpack得到一个元组 bytes_len = struct.unpack('i', headers)[0] # 3.再接收真实数据 json_bytes_data = client.recv(bytes_len).decode('utf-8') # 4.反序列化得到真实数据字典 back_dic = json.loads(json_bytes_data) return back_dic # 获取电影md5值 def get_movie_md5(movie_path): movie_size = os.path.getsize(movie_path) # 1490 ---> first: 0 2:三分之一 3: 三分之二 last:movie_size - 10 # 找到指定位置,每个位置截取10个 # 开始根据电影的长度 截取指定位置的值,并生成md5值 # 注意: 此处是截取电影数据的 4个位置 index_list = [0, movie_size // 3, (movie_size // 3) * 2, movie_size - 10] # 想要在电影的4个位置分别截取10个bytes数据 然后做md5加密 md5_obj = hashlib.md5() # 先打开电影的真实数据 with open(movie_path, 'rb') as f: for line in index_list: # 光标移动到指定位置 f.seek(line) # 每个位置获取10个bytes数据 data = f.read(10) # 每10个bytes加一次密 md5_obj.update(data) return md5_obj.hexdigest()
tcp_client
import socket # socket客户端 函数 def get_client(): client = socket.socket() client.connect( ('127.0.0.1', 9527) ) return client
start
import sys import os sys.path.append( os.path.dirname(__file__) ) # from tcp_client import socket_client from core import src if __name__ == '__main__': src.run()
服务端
conf/settings
import os BASE_PATH = os.path.dirname( os.path.dirname(__file__) ) MOVIE_DIR = os.path.join(BASE_PATH, 'movie_dir')
db/models
''' models.py 专门用于存放 数据表类 ''' from orm_control.orm import Models, IntegerField, StringField # 用户表类: id、用户名、密码、用户类型、是否为VIP、注册时间 字段 class User(Models): u_id = IntegerField(name='u_id', primary_key=True) # u_id = integerField_obj username = StringField(name='username') password = StringField(name='password') user_type = StringField(name='user_type') # admin, user # 0: 默认不是vip, 1: 是vip is_vip = IntegerField(name='is_vip') register_time = StringField(name='register_time') # 电影表类: id、电影名字、电影大小、电影md5值、电影是否免费、电影是否删除、上传时间、上传用户的id class Movie(Models): m_id = IntegerField(name='m_id', primary_key=True) movie_name = StringField(name='movie_name') movie_size = StringField(name='movie_size') movie_md5 = StringField(name='movie_md5') # 0: 收费 1:免费 is_free = IntegerField(name='is_free') # 0:未删除 1: 已删除 is_delete = IntegerField(name='is_delete') upload_time = StringField(name='upload_time') user_id = IntegerField(name='user_id') # 公告表类: id、公告标题, 公告内容、发布时间、发布用户id class Notice(Models): n_id = IntegerField(name='n_id', primary_key=True) title = StringField(name='title') content = StringField(name='content') create_time = StringField(name='create_time') user_id = IntegerField(name='user_id') # 下载记录表: id、下载电影的id、下载用户的id、下载时间 class DownloadRecord(Models): d_id = IntegerField(name='d_id', primary_key=True) movie_id = IntegerField(name='movie_id') user_id = IntegerField(name='user_id') download_time = StringField(name='download_time')
db/user_data
user_online = { # # '用户凭证1': 'session1', # '用户凭证1': 'session2', # '用户凭证1': 'session3', # '用户凭证1': 'session4', # # '用户凭证2': 'session2', # '(127.0.0.1: 9527)': 'session1', # '(127.0.0.1: 9528)': 'session2', # '(127.0.0.1: 9528)': ['session2', '用户u_id'], }
interface/admin_interface
from db.models import User from db.models import Movie from db.models import Notice import datetime from lib import common from db import user_data from threading import Lock from conf import settings import os mutex = Lock() # 检测电影接口 @common.login_auth def check_movie_interface(back_dic, conn): movie_md5 = back_dic.get('movie_md5') # 1.通过电影表查询电影的md5值是否存在 movie_obj_list = Movie.select_data(movie_md5=movie_md5) if movie_obj_list: send_dic = { 'flag': False, 'msg': '电影已存在!' } else: send_dic = { 'flag': True, 'msg': '可以继续上传!' } # 2.将校验后的send_dic返回给客户端 common.send_data(send_dic, conn) # 上传电影功能 @common.login_auth def upload_movie_interface(back_dic, conn): # 1.组织电影上传的目录 # 1251982h5219-yhuibwquiog0argwwuiqgr + 电影名字.mp4 # 保证电影名字唯一 movie_name = common.get_session() + back_dic.get('movie_name') movie_path = os.path.join( settings.MOVIE_DIR, movie_name ) # 2.开始接收客户端上传的电影 movie_size = back_dic.get('movie_size') recv_data = 0 with open(movie_path, 'wb') as f: while recv_data < movie_size: data = conn.recv(1024) f.write(data) recv_data += len(data) # 3.数据保存电影上传数据 movie_obj = Movie( movie_name=movie_name, movie_size=movie_size, movie_md5=back_dic.get('movie_md5'), is_free=back_dic.get('is_free'), is_delete=0, upload_time=str(datetime.datetime.now()), # 传当前登录用户的id user_id=back_dic.get('user_id') # 在装饰器里面获取的user_id ) movie_obj.insert_data() send_dic = { 'flag': True, 'msg': '电影上传成功!' } common.send_data(send_dic, conn) # 删除电影接口 @common.login_auth def delete_movie_interface(back_dic, conn): movie_id = back_dic.get('movie_id') # 1.获取数据库中电影的记录 movie_obj = Movie.select_data(m_id=movie_id)[0] # 2.修改电影的 is_delete字段为1 movie_obj.is_delete = 1 # 3.调用update_data更新delete字段 movie_obj.update_data() send_dic = { 'flag': True, 'msg': '电影删除成功!' } common.send_data(send_dic, conn) # 发布公告接口 @common.login_auth def send_notice_interface(back_dic, conn): notice_obj = Notice( title=back_dic.get('title'), content=back_dic.get('content'), create_time=str(datetime.datetime.now()), user_id=back_dic.get('user_id') ) notice_obj.insert_data() send_dic = { 'msg': '公告发布成功!' } common.send_data(send_dic, conn)
interface/common_interface
from lib import common from db.models import Movie, User import datetime from db.user_data import user_online from threading import Lock from conf import settings import os from db.models import DownloadRecord mutex = Lock() # 注册接口 def register_interface(back_dic, conn): # 业务逻辑的处理 # 1.判断用户是否存在,去数据库中查找,ORM,User username = back_dic.get('username') user_obj_list = User.select_data(username=username) # [obj, obj, obj] if user_obj_list: # 将用户已存在的数据,打包发送回给客户端 send_dic = { 'flag': False, 'msg': '用户已存在!' } else: # 开始插入数据 user_obj = User( username=username, password=common.get_md5(back_dic.get('password')), user_type=back_dic.get('user_type'), register_time=str(datetime.datetime.now()) ) user_obj.insert_data() send_dic = { 'flag': True, 'msg': f'用户: {username} 注册成功!' } common.send_data(send_dic, conn) # 登录接口 def login_interface(back_dic, conn): # 1.判断用户是否存在 user_obj_list = User.select_data(username=back_dic.get('username')) if not user_obj_list: send_dic = { 'flag': False, 'msg': '用户不存在!' } else: # 校验密码是否正确 user_obj = user_obj_list[0] if user_obj.password == common.get_md5(back_dic.get('password')): addr = back_dic.get('addr') # 1.产生一个session随机字符串 session = common.get_session() # (127.0.0.1: 9527) # 加锁 mutex.acquire() user_online[addr] = [session, user_obj.u_id] # [seesion, u_id] mutex.release() send_dic = { 'flag': True, 'msg': '登录成功!', 'session': session } if user_obj.is_vip: send_dic['is_vip'] = user_obj.is_vip else: send_dic = { 'flag': False, 'msg': '密码错误!' } common.send_data(send_dic, conn) # 未删除电影接口 @common.login_auth def get_movie_list_interface(back_dic, conn): # 1.查看数据库中所有的未删除电影 movie_obj_list = Movie.select_data() if movie_obj_list: # 2.过滤已删除的电影 # 可删除电影list movie_list = [] # 遍历所有电影 for movie_obj in movie_obj_list: # 过滤掉所有已删除的电影 if not movie_obj.is_delete: if back_dic.get('movie_type') == 'all': # 将所有未删除的电影名字 添加到movie_list中 movie_list.append( # [电影名字, 电影的id, 电影是否 免费\收费 [movie_obj.movie_name, movie_obj.m_id, '免费' if movie_obj.is_free else '收费'] ) elif back_dic.get('movie_type') == 'free': # 判断当前电影对象如果is_free中有值,证明都是免费电影 if movie_obj.is_free: movie_list.append( # [电影名字, 电影的id, 电影是否 免费\收费 [movie_obj.movie_name, movie_obj.m_id, '免费'] ) else: # pay # 获取所有收费电影 if not movie_obj.is_free: movie_list.append( # [电影名字, 电影的id, 电影是否 免费\收费 [movie_obj.movie_name, movie_obj.m_id, '收费'] ) send_dic = { 'flag': True, 'movie_list': movie_list } else: send_dic = { 'flag': False, 'msg': '服务端没有电影' } common.send_data(send_dic, conn) # 下载电影接口 @common.login_auth def download_free_movie_interface(back_dic, conn): user_id = back_dic.get('user_id') user_obj = User.select_data(u_id=user_id)[0] # 1.先获取当前电影的电影名字 movie_name = back_dic.get('movie_name') # 2.获取电影的存放路径 movie_path = os.path.join( settings.MOVIE_DIR, movie_name ) # 3.获取电影的大小,与电影的信息,发送给客户端 send_dic = { 'movie_size': os.path.getsize(movie_path), 'wait_time': 0 } # 4.判断当前用户是否是VIP,若不是则添加广告时间 5s if not user_obj.is_vip: send_dic['wait_time'] = 5 # 5.发送真实电影数据 common.send_data(send_dic, conn, file=movie_path) # 6.将下载记录插入到 记录表中 download_obj = DownloadRecord( movie_id=back_dic.get('movie_id'), user_id=user_id, download_time=str(datetime.datetime.now()) ) download_obj.insert_data()
interface/user_interface
from lib import common from db.models import User, DownloadRecord, Movie, Notice # 购买会员接口 @common.login_auth def buy_vip_interface(back_dic, conn): user_id = back_dic.get('user_id') user_obj = User.select_data(u_id=user_id)[0] user_obj.is_vip = 1 user_obj.update_data() send_dic = { 'flag': True, 'msg': '充值购买成功!' } common.send_data(send_dic, conn) # 下载记录接口 @common.login_auth def check_movie_record_interface(back_dic, conn): # 1.先获取当前用户id user_id = back_dic.get('user_id') # 然后根据id获取当前用户所有的下载记录 download_obj_list = DownloadRecord.select_data(user_id=user_id) if not download_obj_list: send_dic = { 'flag': False, 'msg': '没有下载记录' } else: download_movie_list = [] # 方式一: # 2.循环遍历所有的下载记录,获取每一步电影的id # for download_obj in download_obj_list: # # movie_id = download_obj.movie_id # # 循环几次,就查询几次数据,操作几次数据库; # # ---》 可以改成,先查询所有的电影,在根据电影的movie_id做比对,减少对数据库的操作; # movie_obj = Movie.select_data(m_id=movie_id)[0] # # # 将电影的名字与下载记录的时间,添加到download_movie_list中,用户返回给客户端展示给用户看的 # download_movie_list.append( # [movie_obj.movie_name, download_obj.download_time] # ) # 方式二: # 只查一次数据, 操作一次数据库 # 将所有电影查出来 movie_obj_list = Movie.select_data() # 遍历所有 “下载电影记录对象” download_obj for download_obj in download_obj_list: # 获取所有下载记录的电影id download_movie_id = download_obj.movie_id # 循环遍历获取所有 “电影的对象” movie_obj for movie_obj in movie_obj_list: # 判断 电影的对象的id == 下载电影记录对象的movie_id if movie_obj.m_id == download_movie_id: download_movie_list.append( [movie_obj.movie_name, download_obj.download_time] ) send_dic = { 'flag': True, 'download_movie_list': download_movie_list } print(send_dic) common.send_data(send_dic, conn) # 查看公告接口 @common.login_auth def check_notice_interface(back_dic, conn): # 1.查看数据库中所有的公告 notice_obj_list = Notice.select_data() # 2.判断是否有公告 if not notice_obj_list: send_dic = { 'flag': False, 'msg': '没有公告!' } else: # 公告的标题与内容 notice_list = [] # 循环遍历所有的公告对象 for notice_obj in notice_obj_list: notice_list.append( [notice_obj.title, notice_obj.content, notice_obj.create_time] ) send_dic = { 'flag': True, 'notice_list': notice_list } common.send_data(send_dic, conn)
lib/common
import struct import json import hashlib import uuid from db.user_data import user_online from threading import Lock mutex = Lock() # 服务端发送消息给客户端的公共方法 def send_data(back_dic, conn, file=None): # 1序列化得到json的bytes数据 bytes_data = json.dumps(back_dic).encode('utf-8') # 2.制作报头 headers = struct.pack('i', len(bytes_data)) # 3.先发送报头 conn.send(headers) # 4.再发送真实数据 conn.send(bytes_data) if file: with open(file, 'rb') as f: for line in f: conn.send(line) # 密码加密 def get_md5(pwd): md5_obj = hashlib.md5() md5_obj.update(pwd.encode('utf-8')) # 加盐 salt = '啊!!!tank好帅啊,每天被自己帅醒啊!!!' md5_obj.update(salt.encode('utf-8')) return md5_obj.hexdigest() # 产生随机session字符串 def get_session(): uuid_str = str(uuid.uuid4()) session_val = get_md5(uuid_str) return session_val # 登录认证装饰器: 绕过来即可无忧!!!!,绕不过抄10次 def login_auth(func): def inner(*args, **kwargs): # args = (back_dic, conn) # 1.校验当前用户是否登录: 校验session是否一致 # back_dic.get('cookies') client_session = args[0].get('cookies') addr = args[0].get('addr') # ['session2', '用户u_id'] or None mutex.acquire() session_id_list = user_online.get(addr) mutex.release() if session_id_list: if client_session == session_id_list[0]: # 2.若已登录,则将用户id添加到客户端发送过来的字典中 # back_dic['user_id'] = session_id_list[1] # 携带user_id去执行接口功能并使用它 args[0]['user_id'] = session_id_list[1] res = func(*args, **kwargs) return res else: send_dic = { 'flag': False, 'msg': '携带的session值错误,没有执行权限' } # send_data(send_dic, conn) send_data(send_dic, args[1]) else: send_dic = { 'flag': False, 'msg': '用户未登录,没有执行权限' } # send_data(send_dic, conn) send_data(send_dic, args[1]) return inner
orm_control/mysql_control
import pymysql from orm_control.mysql_pool import POOL # MySQL连接类 class MySQL: __instance = None # 单例模式 @classmethod def singleton(cls): if not cls.__instance: cls.__instance = cls() return cls.__instance # 实例化MySQL类时,获取数据库链接对象,获取游标对象 def __init__(self): # self.mysql_client = pymysql.connect() self.mysql_client = POOL.connection() self.cursor = self.mysql_client.cursor( pymysql.cursors.DictCursor ) # 自定义查询方法 def select(self, sql, args=None): # 1、先提交查询sql语句 # select * from table; # select * from table where id=%s; self.cursor.execute(sql, args) # 2、获取返回的查询结果 # res ---> [{}, {}] res = self.cursor.fetchall() return res # 自定义提交sql语句方法,比如: insert、update def execute(self, sql, args): # 1、提交sql语句 # insert into table(字段) values(%s); try: self.cursor.execute(sql, args) except Exception as e: print(e) def close(self): # 先关闭游标 self.cursor.close() # 再关闭数据库连接 self.mysql_client.close()
orm_control/mysql_pool
# pip3 install DBUtils from DBUtils.PooledDB import PooledDB import pymysql ''' 数据库连接池 ''' POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。 # PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123456', database='orm_demo', charset='utf8', autocommit=True )
orm_control/orm
''' ORM: 对象关系映射 将对象 映射成 数据表中的一条条记录 类 ----> 表名 对象 ----> 记录 对象.属性 ----> 字段 ''' # 演示映射关系 ''' User table: 名字、年龄、性别 - 名字、年龄、性别 - jason 80 female class User: user_obj = User() user_obj.name属性 user_obj.age属性 = 80 ''' from orm_control.mysql_control import MySQL class Field: def __init__(self, name, column_type, primary_key, default): self.name = name self.column_type = column_type self.primary_key = primary_key self.default = default # varchar class StringField(Field): def __init__(self, name, column_type='varchar(64)', primary_key=False, default=None): super().__init__(name, column_type, primary_key, default) # int class IntegerField(Field): def __init__(self, name, column_type='int', primary_key=False, default=0): super().__init__(name, column_type, primary_key, default) # 自定义元类: # 解决三件事情 ---》 # 1、保证一张表必须要有表名 2、保证一张表中只能有一个主键 # 3、将所有 “字段名” 与 “字段对象” 添加到一个独立的字典中(mappings), # 以key(字段名):value(字段对象) , 添加到类的名称空间中,方便后期使用 class OrmMetaClass(type): # __call__ ---> __new__ ----> type.__new__() ---> obj ---> Models # 只要定义类就会触发__new__, 因为类也是对象,OrmMetaClass() ---> Models类对象 、User类对象 def __new__(cls, class_name, class_bases, class_attr): # class_name, class_bases, class_attr = args # print(f'类名: {class_name}') # print(f'基类: {class_bases}') # print(f'类的名称空间: {class_attr}') # 1、过滤Models类 if class_name == 'Models': # 将models类的类名、基类、名称空间原路返回 return type.__new__(cls, class_name, class_bases, class_attr) # 2、获取 table 表名,若自定义则获取,没有则默认使用类名 # dict.get(key) ---> key若有则返回对应的值,若没有则返回默认值 class_name就是默认值 # 将类名当做表名 table_name = class_attr.get('table_name', class_name) # print(table_name) # 打印修改类名称空间前的 名称空间 # print(class_attr) # 主键值: 主键名为 字段名, 比如 主键是 id字段 ---》 id就是主键的名字 primary_key = None # 存放字段名与字段对象的字典 mappings = {} # 3、保证一张表只能有一个唯一的主键 # 循环遍历类的名称空间 for k, v in class_attr.items(): # print(k, v) # 将字段以外的属性过滤掉 # 判断当前的v是否是字段对象 if isinstance(v, Field): # print(k, v) # print(v.__dict__) # 4、将所有 “字段名” 与 “字段对象” 添加到一个独立的字典中(mappings) mappings[k] = v # 坑 # class_attr.pop(k) # 纠正,这里当字典被迭代时,不能修改其属性 # 判断字段对象如果有 主键primary_key, 则为primary_key 变量赋值 if v.primary_key: # 若第二次进来,primary有值,证明有主键,抛出异常 if primary_key: raise TypeError('一张表只能有一个主键') # primary_key = k # print(k == v.name, 111111) # 给primary_key变量做一个赋值操作 primary_key = v.name # 5、过滤掉类名称空间中重复的字段属性 for key in mappings.keys(): class_attr.pop(key) # print(table_name) # print(primary_key) # print(mappings) # print(class_attr) if not primary_key: raise TypeError('必须要有一个主键!!!') # 6、给类的名称空间,添加table_name, primary_key,mappings属性; class_attr['table_name'] = table_name # cls.table_name class_attr['primary_key'] = primary_key class_attr['mappings'] = mappings # print('*' * 100) # print(class_attr) return type.__new__(cls, class_name, class_bases, class_attr) class Models(dict, metaclass=OrmMetaClass): # 对象.属性, 属性没有时触发 def __getattr__(self, item): # print(self) return self.get(item) # {'name': 'tank', 'pwd': '123'}.get('name') ----> tank # 对象.属性 = 属性值 时触发 def __setattr__(self, key, value): # print(key, value) # print(self) # {'name': 'tank', 'pwd': '123'} # 给字典添加键值对的方式 self[key] = value # 查询数据 @classmethod def select_data(cls, **kwargs): # name=tank ---> {'name': "tank"} mysql_obj = MySQL() filed_value = None # 若kwargs为False代表没有查询条件 if not kwargs: # 1、查所有 # sql = 'select * from 表名' sql = f'select * from {cls.table_name}' else: # 獲取字段名 filed_name = list(kwargs.keys())[0] # 获取字段值 filed_value = kwargs.get(filed_name) # 2、根据条件查询 # select * from 表名 where 字段名=字段值; sql = f'select * from {cls.table_name} where {filed_name}=?' sql = sql.replace('?', '%s') res = mysql_obj.select(sql, filed_value) # print(res) # [{}, {}] # [cls(**r) for r in res] # [{key: values}, {}] ---> [User(key=value)] return [cls(**r) for r in res] # ----> [{}, {}, {}] ----> [obj, obj, obj] # 插入数据 def insert_data(self): # self ---> user_obj mysql_obj = MySQL() # sql: insert into 表名(字段名1, 字段名2) values(字段值1, 字段值2); # 1.表名 ---》 self.table_name # 2.字段名与字段值---》 mappings # 存放字段名的列表 filed_names = [] # [字段名, 字段名, 字段名, ] # 存放字段值的列表 filed_values = [] # [字段值, 字段值,字段值,] # 设置一个替换值的列表 replace_list = [] # [?, ?] for k, v in self.mappings.items(): # 获取字段名,追加到列表中 # filed_names.append(k) filed_names.append(v.name) # User(username='tank', password='123') # 获取字段值,追加到列表中 # getattr反射返回的值是__getattr__返回的结果 # res = getattr(self, v.name, v.default) # v.default ---> 0 res = self.get(v.name, v.default) # res = getattr(self, v.name) if getattr(self, v.name) else v.default print(f'{v.name}', res) filed_values.append( # 反射: 根据字符串操作对象中的属性或方法 res ) replace_list.append('?') # sql = f'insert into {self.table_name}({",".join(filed_names)}) values({",".join(replace_list)})' sql = 'insert into %s(%s) values(%s)' % ( self.table_name, ",".join(filed_names), ",".join(replace_list) ) sql = sql.replace('?', '%s') mysql_obj.execute(sql, filed_values) # 更新数据 def update_data(self): mysql_obj = MySQL() # sql: update 表名 set 字段名=字段值 where pk=主键值; # 主键值 pk = None # 存放字段名的列表 filed_names = [] # [字段名, 字段名, 字段名, ] # 存放字段值的列表 filed_values = [] # [字段值, 字段值,字段值,] for k, v in self.mappings.items(): # 判断mappings中哪一个字段是主键 if v.primary_key: # 获取主键的值 pk = self.get(v.name) else: # 添加字段名 filed_names.append(v.name + '=?') # 添加字段值 filed_values.append( self.get(v.name, v.default) ) # sql: update User set 字段名=字段值, 字段名=字段值 # sql: update User set username=?, password=? where pk=1; sql = 'update %s set %s where %s=%s' %( self.table_name, ','.join(filed_names), self.primary_key, pk ) # sql: update User set username=%s, password=%s where pk=1; sql = sql.replace('?', '%s') mysql_obj.execute(sql, filed_values)
tcp_server/socket_server
import socket import struct import json from interface import admin_interface from interface import common_interface from interface import user_interface from concurrent.futures import ThreadPoolExecutor from db import user_data from threading import Lock pool = ThreadPoolExecutor(50) mutex = Lock() # 接口字典 func_dic = { 'register': common_interface.register_interface, 'login': common_interface.login_interface, # 检测电影接口 'check_movie': admin_interface.check_movie_interface, # 上传电影功能接口 'upload_movie': admin_interface.upload_movie_interface, # 获取未删除电影接口 'get_movie_list': common_interface.get_movie_list_interface, # 删除电影接口 'delete_movie': admin_interface.delete_movie_interface, # 发布公告接口 'send_notice': admin_interface.send_notice_interface, # 购买会员接口 'buy_vip': user_interface.buy_vip_interface, # 下载电影接口 'download_movie': common_interface.download_free_movie_interface, # 查看下载记录接口 'check_movie_record': user_interface.check_movie_record_interface, # 查看公告接口 'check_notice': user_interface.check_notice_interface, } # 任务函数 def working(conn, addr): while True: try: # 1.先接收报头 headers = conn.recv(4) # 2.解压报头,获取真实数据的长度 # struct.unpack得到一个元组 bytes_len = struct.unpack('i', headers)[0] # 3.再接收真实数据 json_bytes_data = conn.recv(bytes_len).decode('utf-8') # 4.反序列化得到真实数据字典 back_dic = json.loads(json_bytes_data) # 保证每个客户端过来都是唯一,将addr添加到客户端发送过来的字典中 back_dic['addr'] = str(addr) func_type = back_dic.get('func_type') if func_type in func_dic: func_dic.get(func_type)(back_dic, conn) except Exception as e: print(e) mutex.acquire() # 清除当前客户端存放在服务端的session值 user_data.user_online.pop(str(addr)) mutex.release() break conn.close() # 实现并发 def run(): server = socket.socket() server.bind( ('127.0.0.1', 9527) ) server.listen(50) while True: conn, addr = server.accept() print(addr) # 异步提交working任务 pool.submit(working, conn, addr)
start
import sys import os sys.path.append( os.path.dirname(__file__) ) from tcp_server import socket_server if __name__ == '__main__': print('Server is running...') socket_server.run()