优酷服务端
服务端项目目录
conf
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_MOVIE_DIR = os.path.join(BASE_DIR,'movie_dir')
db
from orm_pool.orm import Models, StringField, IntegerField class User(Models): table_name = 'user' id = IntegerField('id', primary_key=True) name = StringField('name') password = StringField('password') is_locked = IntegerField('is_locked', default=0) is_vip = IntegerField('is_vip', default=0) user_type = StringField('user_type') register_time = StringField('register_time') class Movie(Models): table_name = 'movie' id = IntegerField('id', primary_key=True) name = StringField('name',column_type='varchar(64)') path = StringField('path') is_free = IntegerField('is_free', default=1) is_delete = IntegerField('is_delete', default=0) create_time = StringField('create_time') user_id = IntegerField('user_id') file_md5 = StringField('file_md5') class Notice(Models): table_name = 'notice' id = IntegerField('id', primary_key=True) name = StringField('name') content = StringField('content') user_id = IntegerField('user_id') create_time = StringField('create_time') class DownloadRecord(Models): table_name = 'download_record' id = IntegerField('id', primary_key=True) user_id = IntegerField('user_id') movie_id = IntegerField('movie_id') create_time = StringField('create_time')
interface
from db import models from datetime import datetime from lib import common from conf import settings import os @common.login_auth def release_notice(recv_dic,conn): title = recv_dic.get('title') content = recv_dic.get('content') user_id = recv_dic.get('user_id') obj = models.Notice(name=title,content=content,user_id=user_id,create_time=str(datetime.now())) obj.save() back_dic = {'flag':True,'msg':'发布公告成功'} common.send_back(conn,back_dic) @common.login_auth def check_movie(recv_dic,conn): movie_data = models.Movie.select(file_md5=recv_dic['file_md5']) if movie_data: back_dic = {'flag':False,'msg':'该电影已存在'} else: back_dic = {'flag':True,'msg':'该电影不存在,可以上传'} common.send_back(conn,back_dic) @common.login_auth def upload_movie(recv_dic,conn): # 这里为了避免上传的视频名字是一样的但是内容不一样,所以文件名应该尽量取的唯一 file_name = common.get_session(recv_dic.get('file_name'))+recv_dic.get('file_name') file_path = os.path.join(settings.BASE_MOVIE_DIR,file_name) recv_size = 0 with open(file_path,'wb') as f: while recv_size < recv_dic['file_size']: recv_data = conn.recv(1024) f.write(recv_data) recv_size += len(recv_data) # 调用orm完成保存操作 movie_obj = models.Movie(name=file_name,path=file_path,is_free=recv_dic.get("is_free"),is_delete=0, create_time = common.get_nowtime(),user_id = recv_dic.get('user_id'), file_md5=recv_dic.get("file_md5") ) movie_obj.save() back_dic = {'flag':True,'msg':'上传成功'} common.send_back(conn,back_dic) @common.login_auth def delete_movie(recv_dic,conn): # 删除电影并不是真的将电影数据删除 而仅仅是将标示该电影是否删除的字段改为1 movie_list = models.Movie.select(id=recv_dic.get("delete_movie_id")) movie_obj = movie_list[0] movie_obj.is_delete = 1 movie_obj.update() back_dic = {'flag':True,'msg':'删除成功'} common.send_back(conn,back_dic)
from lib import common from db import models import os @common.login_auth def get_movie_list(recv_dic,conn): movie_list = models.Movie.select() if movie_list: back_movie_list = [] for movie in movie_list: # 筛选出所有没有被删除的电影 if not movie.is_delete: # 根据用户想要查询的电影类型进行划分 if recv_dic['movie_type'] == 'all': # 只要不是删除的电影都要 数据格式[[电影名,是否免费,数据id],[...],[...]] back_movie_list.append([movie.name, '免费' if movie.is_free else '收费', movie.id]) elif recv_dic['movie_type'] == 'free': if movie.is_free: back_movie_list.append([movie.name, '免费', movie.id]) else: if not movie.is_free: back_movie_list.append([movie.name, '收费', movie.id]) # 校验电影列表是否为空 if not back_movie_list: back_dic = {"flag":False,'msg':"暂无符合要求的电影"} else: back_dic = {'flag':True,'movie_list':back_movie_list} else: back_dic = {"flag":True,'msg':'暂无电影'} common.send_back(conn,back_dic) @common.login_auth def buy_member(recv_dic, conn): user_list = models.User.select(id=recv_dic['user_id']) user_obj = user_list[0] user_obj.is_vip = 1 user_obj.update() back_dic = {'flag': True, 'msg': '购买会员成功'} common.send_back(conn,back_dic) @common.login_auth def download_movie(recv_dic,conn): movie_list = models.Movie.select(id=recv_dic.get('movie_id')) if movie_list: movie_obj = movie_list[0] user_obj = models.User.select(id=recv_dic['user_id'])[0] # 下载免费视频需要判断当前用户是否是vip会员,如果不是客户端需要等待30秒才能继续下载 wait_time = 0 if recv_dic['movie_type'] == 'free': if user_obj.is_vip: wait_time = 0 else: wait_time = 30 back_dic = {'flag':True,"file_name":movie_obj.name,'file_size':os.path.getsize(movie_obj.path),'wait_time':wait_time} # 将下载记录记录到专门存放记录的表中 down_record = models.DownloadRecord(user_id=user_obj.id,movie_id=movie_obj.id,create_time=common.get_nowtime()) down_record.save() common.send_back(conn,back_dic) # 打开文件传输给客户端 with open(movie_obj.path,'rb') as f: for line in f: conn.send(line) else: back_dic = {'flag':False,'msg':'该电影不存在'} common.send_back(conn,back_dic) @common.login_auth def check_download_record(recv_dic,conn): record_list = models.DownloadRecord.select(user_id=recv_dic['user_id']) back_record = [] if record_list: for record in record_list: # 查出电影对应的电影名 movie_obj = models.Movie.select(id=record.movie_id)[0] back_record.append(movie_obj.name) back_dic = {"flag":True,'record':back_record} else: back_dic = {'flag':False,'msg':"暂无观影记录"} common.send_back(conn,back_dic) @common.login_auth def check_notice(recv_dic,conn): notice_list = check_notice_by_count(count=None) if notice_list: back_dic = {'flag': True, 'notice_list': notice_list} else: back_dic = {'flag': False, 'msg': '暂无公告'} common.send_back(conn, back_dic) # 定义专门用来获取公告的方法 def check_notice_by_count(count=None): notice_list = models.Notice.select() back_notice_list = [] if notice_list: # 不为空,继续查询,为空直接返回false if not count: for notice in notice_list: back_notice_list.append({notice.name: notice.content}) else: # 查一条 notice_list = sorted(notice_list, key=lambda notice: notice.create_time,reverse=True) back_notice_list.append({notice_list[0].name: notice_list[0].content}) return back_notice_list else: return False
from lib import common from db import models from datetime import datetime from TcpServer import user_data from interface import user_interface def register(recv_dic, conn): name = recv_dic.get('name') user_data = models.User.select(name=name) if user_data: back_dic = {"flag": False, 'msg': '用户名已存在'} else: password = recv_dic.get('password') user_type = recv_dic.get('user_type') user_obj = models.User(name=name, password=password, user_type=user_type, is_locked=0, is_vip=0, register_time="%s" % datetime.now()) user_obj.save() back_dic = {'flag': True, 'msg': '注册成功'} common.send_back(conn, back_dic) def login(recv_dic, conn): name = recv_dic.get("name") user_list = models.User.select(name=name) if user_list: user_obj = user_list[0] if user_obj.user_type == recv_dic.get("user_type"): if user_obj.password == recv_dic.get('password'): back_dic = {'flag': True, 'msg': '登陆成功','is_vip':user_obj.is_vip} session = common.get_session(user_obj.name) back_dic['session'] = session # 这里需要存储用户的session以及user_id 客户端登陆成功后只会携带session过来 # 我们除了校验他的合法身份之外,也应该存该用户数据库对于的主键值,方便后期获取该用户数据进行相应的修改 user_data.mutex.acquire() user_data.live_user[recv_dic['addr']] = [session, user_obj.id] user_data.mutex.release() # 普通用户在登陆之后强制打印最新一条公告内容 if recv_dic['user_type'] == 'user': last_notice = user_interface.check_notice_by_count(1) back_dic['last_notice'] = last_notice else: back_dic = {'flag': False, 'msg': "密码错误"} else: back_dic = {'flag':False,'msg':'用户类型不正确'} else: back_dic = {'flag': False, 'msg': '用户不存在'} common.send_back(conn, back_dic)
lib
import json import hashlib import time from functools import wraps from TcpServer import user_data import struct def send_back(conn,back_dic): back_bytes = json.dumps(back_dic).encode('utf-8') header = struct.pack('i',len(back_bytes)) conn.send(header) conn.send(back_bytes) def get_session(name): # 为了保证生成的随机字符串是独一无二的这里用cpu执行时间加盐 md = hashlib.md5() md.update(str(time.clock()).encode('utf-8')) md.update(name.encode('utf-8')) return md.hexdigest() def login_auth(func): @wraps(func) def inner(*args,**kwargs): # args=(recv_dic,conn) for values in user_data.live_user.values(): if args[0]['session'] == values[0]: # 如果当前用户存在且登陆 将该用户id放入recv_dic中 args[0]['user_id'] = values[1] break if args[0].get("user_id"): func(*args,**kwargs) else: back_dic = {'flag':False,'msg':'请先登陆'} send_back(args[1],back_dic) return inner def get_nowtime(): now_time = time.strftime('%Y-%m-%d %X') return now_time
orm_pool
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='12345678', database='youku', charset='utf8', autocommit='True' )
import pymysql from orm_pool.db_pool import POOL class Mysql(object): def __init__(self): self.conn = POOL.connection() self.cursor = self.conn.cursor(pymysql.cursors.DictCursor) def close_db(self): self.cursor.close() self.conn.close() def select(self, sql, args=None): self.cursor.execute(sql, args) res = self.cursor.fetchall() return res def execute(self, sql, args): try: self.cursor.execute(sql, args) except BaseException as e: print(e) if __name__ == '__main__': ms = Mysql() res = ms.select('select * from class where cid=%s',1) print(res)
from orm_pool.mysql_pool import Mysql class Field(object): 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 class StringField(Field): def __init__(self, name, column_type='varchar(32)', primary_key=False, default=None): super().__init__(name, column_type, primary_key, default) class IntegerField(Field): def __init__(self, name, column_type='int', primary_key=False, default=0): super().__init__(name, column_type, primary_key, default) class ModelMetaClass(type): def __new__(cls, name, bases, attrs): if name == 'Models': return type.__new__(cls, name, bases, attrs) table_name = attrs.get('table_name', name) # 获取表名,如果用户没有自定义那么就用类名 primary_key = None mappings = {} for k, v in attrs.items(): if isinstance(v, Field): # 判断是否我们自定义的属性table_name,id,name等... mappings[k] = v if v.primary_key: if primary_key: raise TypeError("一张表有且只有一个主键") primary_key = k # 判断是否是主键 # 将单个单个的自定义的字段属性从attrs中 for k in mappings.keys(): attrs.pop(k) if not primary_key: raise TypeError('没有设置主键') # 将表的三个特性表名,主键,字段付给类的属性 attrs['table_name'] = table_name attrs['primary_key'] = primary_key attrs['mappings'] = mappings return type.__new__(cls, name, bases, attrs) class Models(dict, metaclass=ModelMetaClass): def __init__(self, **kwargs): super().__init__(**kwargs) def __getattr__(self, item): # 字典对象获取值 return self.get(item, '没有该键值对!') def __setattr__(self, key, value): # 字段对象设置值 self[key] = value @classmethod def select(cls, **kwargs): ms = Mysql() if not kwargs: # 第一种查询可能:select * from userinfo; sql = "select * from %s" % cls.table_name res = ms.select(sql) # 结果肯定是[{},{},{}]列表套字典 else: # 第二种查询可能:select * from userinfo where id=%s或name=%s或password=%s...; key = list(kwargs.keys())[0] # 这里我们规定过滤条件只能有一个,所以这里只取一个 value = kwargs.get(key) sql = "select * from %s where %s=?" % (cls.table_name, key) sql = sql.replace('?', '%s') res = ms.select(sql, value) # 结果肯定是[{},{},{}]列表套字典 if res: # 列表推导式获取到一个个字典,**打散成name='jason',password='123'形式,传入类中完成实例化 return [cls(**r) for r in res] # 返回结果为[obj1,obj2,obj3...] def update(self): ms = Mysql() # update user set name='jason',password='123' where id=1; 更新操作where后统一就用主键 fields = [] pr = None args = [] for k,v in self.mappings.items(): if v.primary_key: pr = getattr(self,v.name,v.default) else: fields.append(v.name+'=?') args.append(getattr(self,v.name,v.default)) sql = "update %s set %s where %s=%s"%(self.table_name,','.join(fields),self.primary_key,pr) # 上句的拼接结果update user set name=?,password=? where id=1 sql = sql.replace('?','%s') ms.execute(sql,args) def save(self): ms = Mysql() # insert into user(name,password) values('jason','123') fields = [] args = [] values = [] for k,v in self.mappings.items(): if not v.primary_key: fields.append(v.name) args.append('?') values.append(getattr(self,v.name,v.default)) sql = "insert into %s(%s) values(%s)"%(self.table_name,','.join(fields),','.join(args)) # sql = "insert into user(name,password) values(?,?)" sql = sql.replace('?','%s') ms.execute(sql,values)
orm_singleton
TcpServer
import socket import json from db import models from datetime import datetime from lib import common from interface import admin_interface,user_interface,common_interface from concurrent.futures import ThreadPoolExecutor from threading import Lock from TcpServer import user_data import struct # 对于报错消息是Address already in use from socket import SOL_SOCKET,SO_REUSEADDR pool = ThreadPoolExecutor(20) mutex = Lock() # 将生成的锁放到user_data,避免出现交叉导入的现象,这里导了common_interface,common_interface中又要导这里 user_data.mutex = mutex func_dic = { 'register':common_interface.register, 'login':common_interface.login, 'release_notice':admin_interface.release_notice, 'check_movie':admin_interface.check_movie, 'upload_movie':admin_interface.upload_movie, 'get_movie_list':user_interface.get_movie_list, 'delete_movie':admin_interface.delete_movie, 'buy_member':user_interface.buy_member, 'download_movie':user_interface.download_movie, 'check_download_record':user_interface.check_download_record, 'check_notice':user_interface.check_notice } def get_server(): server = socket.socket() server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind之前设置,避免服务重启时避免报Address already in use server.bind(('127.0.0.1',8082)) server.listen(5) while True: conn,addr = server.accept() pool.submit(working,conn,addr) def working(conn,addr): while True: try: recv_header = conn.recv(4) recv_bytes = conn.recv(struct.unpack('i',recv_header)[0]) recv_dic = json.loads(recv_bytes.decode('utf-8')) # 在执行分发方法之前,将当前用户的addr也放入接受的到的字典中 recv_dic['addr'] = str(addr) # 由于addr不是字符串,这里手动转一下,后面需要拿它作为字典的key dispatch(recv_dic,conn) except Exception as e: print(e) conn.close() user_data.mutex.acquire() user_data.live_user.pop(str(addr)) user_data.mutex.release() break def dispatch(recv_dic,conn): if recv_dic['type'] in func_dic: func_dic.get(recv_dic['type'])(recv_dic,conn) else: back_dic = {'flag':False,'msg':'请求不合法!'} common.send_back(conn,back_dic)
live_user = {}
mutex = None
import os import sys BASE_DIR = os.path.dirname(__file__) sys.path.append(BASE_DIR) from TcpServer import tcpserver if __name__ == '__main__': tcpserver.get_server()