##項目總結三步驟mysql
1、項目生命週期爲基準線、分析要有層次感、不要想到什麼說什麼。 2、這條基準線上,負責的是哪一塊,作了什麼。 三、舉例說明項目中遇到的問題及怎麼解決的。
##一、項目需求分析git
管理員 1 註冊 2 登陸 3 上傳視頻 4 刪除視頻 5 發佈公告 用戶 1 註冊 2 登陸 3 衝會員 4 查看視頻 5 下載免費視頻 6 下載收費視頻 7 查看觀影記錄 8 查看公告
##二、搭建框架sql
層級結構:客戶端 服務端 數據庫
客戶端:
基於tcp鏈接的套接字程序
管理員視圖
註冊、登陸、上傳視頻、刪除視頻、發佈公告
用戶視圖
註冊、登陸、購買vip、查看視頻、下載免費視頻、下載收費視頻、查看下載記錄、查看公告
服務端:
tcpserver:基於多線程實現併發的套接字通訊 解決粘包問題
interface:admin_interface、user_interface、common_interface
models類和ORM框架:models類中的四張表繼承ORM框架中的基類model
數據庫:
建立四張表:user、movie、notice、download_record
##三、ORM框架分析數據庫
可直接去專門將ORM框架的博客
優勢: 讓一個不懂數據庫操做的小白也可以簡單快速操做數據庫實現相應功能 缺點: sql封裝固定,不利於sql查詢優化 對象關係映射 類 >>> 數據庫的表 對象 >>> 表的一條條的記錄 對象獲取屬性或方法 >>> 記錄的字段對應的值 一張表有字段,字段又有字段名,字段類型,字段是不是主鍵,字段的默認值 class Field(object): pass # 爲了在定義的時候更加方便 經過繼承Field定義具體的字段類型 class StringField(Field): pass class IntegerField(Field): pass class Models(dict): pass def __getattr__(self,item): return self.get(item) def __setattr__(self,key,value) self[key] = value # 查詢 def select(self,**kwargs): # select * from userinfo # select * from userinfo where id = 1 # 新增 def save(self): # insert into userinfo(name,password) values('jason','123') # 修改:是基於已經存在了的數據進行修改操做 def update(self): # update userinfo set name='jason',password='234' where id = 1 """ (******) hasattr getattr setattr """ # 元類攔截類的建立過程 使它具有表的特性 class ModelsMetaClass(type): def __new__(cls,class_name,class_bases,class_attrs): # 只攔截模型表的建立表 if class_name == 'Models': return type.__new__(cls,class_name,calss_bases,class_attrs) table_name = class_attrs.get('table_name',class_name) primary_key = None mappings = {} for k,v in class_attrs.items(): if isinstance(v,Field): mappings[k] = v if v.primary: if primary_key: raise TypeError('主鍵重複') primary_key = v.name for k in mappings.keys(): class_attrs.pop(k) if not primary_key: raise TypeError('必需要有一個主鍵') class_attrs['table_name'] = table_name class_attrs['primary_key'] = primary_key class_attrs['mappings'] = mappings return type.__new__(cls,class_name,calss_bases,class_attrs)
##四、數據庫設計json
表結構定義好之後,數據怎麼操做,邏輯代碼怎麼實現就清晰了 用戶表:包含管理員和普通用戶信息 user--->id ,name ,password ,is_locked ,is_vip , user_type,register_time 電影表:movie---->id,name,, path,is_free, is_delete,user_id,create_time,file_md5 公告表:notice--->id,title,content,create_time,user_id 下載記錄表:download_record---->id,user_id,movie_id,create_time create table user( id int auto_increment primary key, name varchar(255), password varchar(255), is_locked int not null default 0, is_vip int not null default 0, user_type varchar(255), register_time varchar(255) )engine=Innodb charset='utf8'; create table movie( id int auto_increment primary key, name varchar(64), path varchar(255), is_free int not null default 1, is_delete int not null default 0, create_time varchar(255), file_md5 varchar(255), user_id int )engine=Innodb charset='utf8'; create table notice( id int auto_increment primary key, title varchar(255), content varchar(255), user_id int, create_time varchar(255) )engine=Innodb charset='utf8'; create table download_record( id int auto_increment primary key, user_id int, movie_id int, create_time varchar(255) )engine=Innodb charset='utf8';
##五、項目中各個功能模塊分析服務器
管理員: 1、註冊功能 客戶端 1-1、選擇每一個功能以前都須要都須要須要鏈接上服務器,即須要一個socket對象,每一個函數傳一個client 1-2、密碼在傳遞過程當中不能是明文吧,須要加密,可選擇hashlib中md5加密,定義一個公共方法咯 1-3、定義一個發送和接收的公共方法、這裏要注意的是在這個方法有一個關鍵字形參、用於傳輸文件,默認爲None 1-4、考慮一個問題,發送的字典中要包含哪些數據、對於註冊這個問題,包含服務器端用於標識的type的功能類型、 用戶名、密碼(要加密)、還有用戶類型"user_type"(admin或者user)這裏是admin類型 1-5、接收穫得的字典back_dic又包含那些數據,常見的就flag和msg,後續的功能中有返回列表類型的 服務端 1-6、首先就是基於多線程實現併發的套接字程序,子線程working函數中會先接收到客戶端發來的字典(用到json、struct模塊) 1-7、有個問題是有什麼便利的方法將接收到的字典recv_dic 和與客戶端創建鏈接的socket對象conn 交給接口層中相應的功能進 行操做數據庫,那就定義一個分發函數dispatch(recv_dic,conn),而後判斷recv_dic["type]類型和全局func_dic字典中進行 比對,去執行與之對應的函數,若是傳過來的類型不存在func_dic字典中,那就自定義一個字典back_dic(包含flag和msg數據) 調用服務端公共發送數據方法返回給客戶端 1-8、我們不知不覺就來到了服務端註冊接口了,意味着能夠操做數據庫啦,就須要用到ORM框架和db目錄中models模塊中與表一一對應 的類、這四個類都是根據事先在數據庫中定義好的字段進行建立的,不要寫錯了,字段和類型。這四個類都繼承了ORM框架的基類 modle,因此但是直接點就能夠調用ORM框架中基類中方法,select方法是類方法,獲得的是一個列表套對象,還有save方法,用於保存 ,還有一個update方法用於更新,那我們回過頭來 1-九、註冊功能拿到的recv_dic中能夠拿到註冊的用戶名,獲得用戶名後使用user_data = models.User.select(name=name )進行判斷要註冊的 用戶是否存在,若果存在老規矩back_dic(flag爲False,msg爲註冊失敗)返回去,不存在那咋整,還能咋整保存到數據庫user表中唄,那 怎麼保存呀,name,password,user_type,is_locked和is_vip都有默認值,register_time註冊時間的話寫個方法 time.strftime("%Y-%m-%d %X") 這樣不就全搞定了,什麼數據都拿到了,那就用models.User()把這些數據搞進去建立獲得一個對象,對象調用save方法進行方法就ojbk了,不急還有 要記得通知客戶端,老規矩back_dic字典,調用公共發送方法,註冊大功告成 2 登陸 客戶端 2-1、在註冊功能該項目的整體框架都已經打通了任督二脈,個人乖乖,那登陸功能須要考慮一個問題,客戶端若是登錄成功,是否是須要標記一下登錄狀態 ,老規矩在全局定義一個字典,把返回的字典中一個session存到全局字典cookie中,解決了ojbk, 2-2、發送字典send_dic中type類型修改成login,密碼的話照樣發送密文,而後over了 服務器 2-3、還記得tcpserver模塊中的全局func_dic字典嗎?強大的地方來了,剛剛只是寫了一個註冊的映射接口,如今來了一個login類型,那咋整,就往裏加一個 login的映射方法,還能夠直接拿到recv_dic和conn,任督二脈打通了就是強,哦還有註冊和登陸都是管理員和普通用戶的公共方法,因此放到common_interface 中,其實放哪都同樣只要能找到就行啦 哈哈 2-4、你要登錄,邏輯點在哪裏,首先我要判斷你這貨存不存在呀,不存在登錄個屁呀,淡定淡定,哈哈,上面說過select方法獲得的是列表,別給老子忘了,列表裏面 放的是一個個對象,models中User類調用select方法根據name=recv_dic["name"]獲得user_list,若是user_list存在,那就取零號位就拿到user_obj用戶對象 2-5、拿到user_obj對象點表中的字段屬性判斷其類型和接收的recv_dic字典中類型和密碼是否一致,一致的話即可以獲得一個back_dic字典了,老規矩包含flag和msg 2-6、重點來了,這裏可能有帶你繞,請無關人員速速離開,要返回的back_dic字典中須要添加一個session添加到字典中,這個session是用戶登錄成功以後生成的一個 隨機字符串,咱這裏也是用hashlib,這裏要保證生成的字符串是惟一的,這裏須要加鹽,加一個當前cpu執行代碼的時間 time.clock() 2-7、,服務端怎麼校驗用戶的登錄問題,考慮兩個問題,第一個問題服務端須要保存session,第二個問題當用戶退出以後將該用戶對應的鍵值刪除? 那咱們如何判斷用戶走了,運行到哪一段代碼就標記用戶走了呢,咱們可不能夠經過addr就能夠定位哪個用戶斷開了,找到當前用對應的數據刪除,數據保存形式 {‘addr’:[session,user_id]} 將這個東西存在哪裏呢,能夠放在全局,但咱們這裏把他存到Tcpsever目錄下user_data模塊中live_user['addr’']=[session,user_id] 那問題又來,怎麼拿到add,第一種思路給每個函數都添加addr參數,可是這個addr參數只是login函數用到,其餘函數都沒用到,這樣第一種思路很不合理,第二種思路 能夠經過working中接收到的recv_dic字典添加recv_dic["addr"] = str(addr) 再傳給每個函數,在login函數中user_data.live_user[recv_dic["addr"]] = [session,user_obj.id] 有考慮一個問題,由於多線程要操做公共數據user_data中的live_user字典,就會出現數據錯亂,因此要加鎖,那這個鎖在那裏產生呢?咱們要在tcpsever全局中產生mutex = Lock() 在這裏產生,可是不能在這裏用,由於會出現循環導入問題,tcpserver導入common_interface,在common_interface中又用到tcpserver中的鎖,相互導入就出現循環導入,解決辦法, 將鎖保存到user_data中 user_data.mutex = mutex,在login中給user_data.live_user[recv_dic["addr"]] = [session,user_obj.id]加鎖,直接導入user_data就可使用到鎖啦 還沒完在tcpserver中 用戶退出(try...except.(下面的執行的代碼就表示其中一個線程斷開)..)就要刪除user_data.live_user.pop(str(addr)) ,這裏也是公共方法須要 加鎖user_data.mutex.acquire()和user_data.mutex.release() 2-8、下面的功能都須要先登陸才能操做,這裏來個裝飾器功能:校驗客戶端發過來的隨機字符串,若是有這個隨機字符串那就正常執行函數,若是沒有返回請先登陸的提示,意味着客戶端 發送的字典要帶着session過來,裝飾器inner(*args,**kwargs)中args=(recv_dic,conn) kwargs={} 拿到客戶端發過來的隨機字符串與服務器的數據進行比對 vlues=[session,user_id] for vlues in user_data.live_user.vlues(): if args[0].get("session") == v[0]:將對應的user_id放入recv_dic中,以便後續使用args[0]["user_id"]=vlues[1] break 以上for循環不必定能找到,for循環只是單單的判斷session,而後將user_id放到接收字典recv_dic中,那被裝飾的函數到底執不執行,if args[0].get("user_id"): func(*args,**kwargs) else: back_dic ={"flag"False,"msg":"請先登陸"} 而後調用返回函數send_back(back_dic,args[1]) 3、上傳視頻 客戶端 3-1、查看有哪些影片須要上傳的,即獲取全部視頻 3-二、判斷影片是否存在才能上傳,那應該怎麼判斷是個問題,咱們能不能對上傳的視頻文件進行hashlib,自定義被hash的數據能夠在文件開頭,1/3,2/3,末尾-10而後獲得md5值 發送字典類型"check_movie",包含"session","file_md5",獲得字典back_dic,若是視頻不存在那要輸入is_free,是否免費,而後在發字典send_dic,該字典類型爲"upload_movie",還包含 "session"、"file_name"、 "file_size"、"file_md5",這裏調用公共收發方法是要給文件file傳參了,把上傳文件路徑傳過去 服務端 3-三、還記得tcpserver模塊中的全局func_dic字典嗎?加上"check_movie"和"upload_movie"映射,映射函數全都加上裝飾器 3-四、"check_movie"比較簡單,只是查看要上傳視頻的file_md5是否在數據庫,注意數據庫中存的只是文件地址而已,不是真實的視頻文件 3-5、這裏爲了不上傳的視頻名字是同樣的可是內容不同,因此文件名應該儘可能取的惟一,因此給傳來的file_name加上一個隨機字符串,就直接調用以前定義的 get_session方法便可 3-6、這裏要拼接文件的存放路徑了,根據file_size循環寫入文件 3-7、生成一個 movie_obj 電影對象,調用save方法保存,而後返回back_dic說明上傳成功 4、刪除視頻 客戶端 4-一、先查詢出全部沒有被刪除的電影列表,即send_dic字典中"type"爲'get_movie_list' 和'movie_type'爲"all",返回的電影列表能夠所有是收費,所有是免費,收費免費都有,這裏須要注意的是獲取全部視頻列表考 慮的不周全,若是單從管理員角度要得到全部視頻不考慮用戶獲取收費或者免費的視頻,會出現一些代碼冗餘,因此在獲取全部視頻這個功能要判斷傳過來的的movie_type是all、free、charge 4-二、拿到全部視頻列表movie_list,該列表的格式[電影名稱,是否免費收費,電影id]發送字典send_dic中"type"爲"delete_movie"和delete_movie_id'爲movie_list[choice-1][2] 服務端 4-三、還記得tcpserver模塊中的全局func_dic字典嗎?加上'get_movie_list'和"delete_movie"映射,映射函數全都加上裝飾器 4-4、刪除電影不是真的刪除,只是找到每個電影對象,而後點is_delete屬性改成1便可,因此get_movie_list方法會先得到全部對象列表,遍歷列表獲得每個對象,對每個對象的is_delete屬性進行判斷,注意還要判斷 ecv_dic['movie_type'],這裏是「all」類型,知足的所有添加到一個返回的列表中back_movie_list,而後返回給客戶端 4-五、delete_movie方法的話 movie_list = models.Movie.select(id=recv_dic.get("delete_movie_id"))而後對列表去索引獲得一個電影對象,而後修改movie_obj.is_delete,而後調用update()方法更新,而後返回back_cic 5、發佈公告 客戶端 5-1 公告包含title和content 發送的字典send_dic包含"type"爲"release_notice"、"session"、"title"、"content" 服務端、 5-2、這裏須要知道接受的字典recv_dic是包含user_id字段的,要寫入表notice時用到 5-3、也是建立表notice對象,而後調用save方法保存 普通用戶 1、註冊 直接調用公共註冊方法 2、登陸 直接調用公共登陸,在全局添加user_dic中保存session和is_vip 3、購買會員 客戶端 3-一、判斷全局user_dic['is_vip']可知道是不是會員 3-2、若是不是的話,讓用戶選擇是否購買會員,購買的話最後要修改全局 服務端 3-三、根據recv_dic["user_id"]判斷是哪個用戶要購買會員,獲得的對象點is_vip屬性修改成1,調用update(0方法保存 4、查看全部視頻 客戶端 4-一、發送字典send_dic裏面的type爲'get_movie_list','movie_type爲'all' 服務器 4-2、直接調用以前寫好的get_movie_list方法便可 這和管理員中刪除視頻就先獲取全部視頻 5、下載免費電影 客戶端 5-一、先列出全部免費電影,和上個功能差很少,只是'movie_type'改成'free' 5-二、再發送字典send_dic中'type'爲'download_movie' 'movie_id'爲movie_list[choice-1][2] 5-3、接受獲得的字典back_dic中有一個wait_time 打印多是0或者30秒 拼接下載的路徑,循環寫入文件 服務端 5-四、id=recv_dic.get('movie_id')來獲得電影列表movie_list,而後索引取值獲得電影對象 5-5 id=recv_dic['user_id']來獲得用戶列表索引取得用戶對象user_obj 5-6、下載電影的話先判斷使用是不是vip,vip的話不須要等待30秒 不是的話須要等待30秒 5-7、更新下載記錄到down_record表中 5-8、循環發送文件 5-9、發送字典back_dic 6、下載收費電影 客戶端 6-1、針對普通用戶和vip用戶下載收費視頻收費標準不同(5元 10元) 6-二、發送字典send_dic 中仍是'get_movie_list'可是電影類型爲收費'movie_type':'charge' 6-3、剩下功能和下載免費電影差很少 服務器 同上 7、查看下載記錄 客戶端 7-一、發送字典send_dic 中的類型'check_download_record' 7-2、接受字典back_dic進行判斷便可 服務端 7-三、還記得tcpserver模塊中的全局func_dic字典嗎?加上'check_download_record'的映射方法 7-4、要查看下載記錄 先根據用戶id獲得一個記錄列表,循環該列表獲得的是每個記錄對象 7-5、根據每個對象點movie_id 和電影id判斷獲得電影列表,索引取值獲得各個對象 7-6、把每個對象的名字添加到一個自定義的列表中,用於返回給客戶端 8、查看公告 客戶端 8-一、發送字典send_dic 中的類型'check_notice' 8-2、接受字典back_dic進行判斷便可 服務端 8-三、還記得tcpserver模塊中的全局func_dic字典嗎?加上'check_notice'的映射方法 8-4、Notice類調用select方法獲得公告列表 8-五、列表存在的話 遍歷該列表獲得每個對象,返回字典中保存對象點title,點content進行返回
##六、項目中遇到的問題及怎麼解決的cookie
1、校驗登錄問題(服務端必須校驗,客戶端無所謂) 2、獲取全部視頻列表考慮的不周全,若是單從管理員角度要得到全部視頻不考慮用戶獲取收費或者免費 的視頻,會出現一些代碼冗餘,因此在獲取全部視頻這個功能要判斷傳過來的的movie_type是all、 free、charge 3、服務端怎樣標識客戶端問題:cookie保存到客戶端、session保存到服務器user_data文件中 四、從客戶端到數據庫一頓操做打通之後遇到最多的問題 有字段打錯了、
##七、客戶端代碼session
#conf多線程
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) UPDATE_MOVIE=os.path.join(BASE_DIR,'update_movie') DOWNLOAD_MOVIE_DIR = os.path.join(BASE_DIR,'download_movie')
#core併發
from core import admin, user func_dic = { "1":admin.admin_view, "2":user.user_view } def run(): while True: print(""" 一、管理員視圖 二、普通用戶視圖 q、退出 """) choice = input("please choice your number>>:").strip() if choice == 'q':break if choice not in func_dic: print("choice err not in range") continue func_dic.get(choice)()
import os from Tcpclient import tcpclient from lib import common from conf import setting user_info = { "session": None } def register(client): while True: name = input("please input your name>>:").strip() password = input("please input your password>>:").strip() re_password = input("please agan input your password>>:").strip() if password != re_password: print("兩次密碼不一致") continue send_dic = {"type": "register", "name": name, "password": common.get_md5(password), "user_type": "admin"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print(back_dic["msg"]) break else: print(back_dic["msg"]) def login(client): while True: name = input("please input your name>>:").strip() password = input("please input your password>>:").strip() send_dic = {"type": "login", "name": name, "password": common.get_md5(password), "user_type": "admin"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print(back_dic["msg"]) user_info["session"] = back_dic["session"] break else: print(back_dic["msg"]) def update_movie(client): """ 思路: 一、是否是要先獲取有哪些能夠上傳的視頻 二、選擇好要上傳的視頻後是否是還要判斷服務器存不存在,存在了就不須要上傳了 三、那就須要校驗視頻文件,可自定義校驗規則 四、循環上傳 :param client: :return: """ while True: movie_list = common.get_movie() if not movie_list: print("暫無影片上傳") return for i, m in enumerate(movie_list, start=1): print("%s:%s" % (i, m)) choice = input("please input your choice>>: ").strip() if choice == 'q': break if choice.isdigit(): choice = int(choice) if choice in range(1, len(movie_list) + 1): file_path = os.path.join(setting.UPDATE_MOVIE, movie_list[choice - 1]) file_md5 = common.get_file_md5(file_path) send_dic = {"type": "check_movie", "session": user_info["session"], "file_md5": file_md5} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: # 若是能夠上傳,那標識上傳免費仍是收費 is_free = input("上傳的影片是否免費(y/n)>>:").strip() is_free = 1 if is_free == 'y' else 0 file_name = movie_list[choice - 1] send_dic = {"type": "update_movie", "session": user_info["session"], "is_free": is_free, "file_name": file_name,"file_md5":file_md5,"file_size":os.path.getsize(file_path)} back_dic = common.send_back(send_dic,client,file_path) if back_dic["flag"]: print(back_dic["msg"]) return else: print(back_dic["msg"]) else: print(back_dic["msg"]) else: print("choice not in range") else: print("input choice must be a number !") def delete_movie(client): """ 思路: 一、先從服務器獲取全部視頻 二、要刪除的發給服務器 :param client: :return: """ send_dic = {"type":"get_movie_list","session":user_info["session"],"movie_type":"all"} back_dic = common.send_back(send_dic,client) if back_dic["flag"]: """ 服務器的get_movie_list會返回一個電影列表,列表裏面爲[電影名,收費或免費,電影id] """ movie_list =back_dic["movie_list"] for i,m in enumerate(movie_list,start=1): print("%s:%s-%s"%(i,m[0],m[1])) choice = input("input your delete movie>>:").strip() if choice.isdigit(): choice = int(choice) if choice in range(1,len(movie_list)+1): send_dic = {"type":"delete_movie","session":user_info["session"],"movie_id":movie_list[choice-1][2]} back_dic = common.send_back(send_dic,client) if back_dic['flag']: print(back_dic['msg']) return else: print(back_dic['msg']) else: print('choice noe in range') else: print(back_dic['msg']) def release_notice(client): while True: title = input("please input title>>:").strip() content = input("please input content>>:").strip() send_dic = {"type":"release_notice","session":user_info["session"],"title":title,"content":content} back_dic = common.send_back(send_dic,client) if back_dic["flag"]: print(back_dic["msg"]) break else: print(back_dic['msg']) break func_dic = { "1": register, "2": login, "3": update_movie, "4": delete_movie, "5": release_notice } def admin_view(): client = tcpclient.get_client() while True: print(""" 一、註冊 二、登陸 三、上傳電影 四、刪除電影 五、發佈公告 """) choice = input("please choice your number>>:").strip() if choice == 'q': break if choice not in func_dic: print("choice err not in range") continue func_dic.get(choice)(client)
import os import time from Tcpclient import tcpclient from conf import setting from lib import common user_info = { "session": None, "is_vip": None } def register(client): while True: name = input("please input your name>>:").strip() password = input("please input your password>>:").strip() re_password = input("please agan input your password>>:").strip() if password != re_password: print("兩次密碼不一致") continue send_dic = {"type": "register", "name": name, "password": common.get_md5(password), "user_type": "user"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print(back_dic["msg"]) break else: print(back_dic["msg"]) def login(client): while True: name = input("please input your name>>:").strip() password = input("please input your password>>:").strip() send_dic = {"type": "login", "name": name, "password": common.get_md5(password), "user_type": "user"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print(back_dic["msg"]) user_info["session"] = back_dic["session"] user_info["is_vip"] = back_dic["is_vip"] break else: print(back_dic["msg"]) def buy_vip(client): while True: buy_vip = input("是否購買會員(y/n)>>:").strip() if buy_vip == 'q': break if buy_vip not in ['y', 'n']: print("輸入有誤") continue if buy_vip == 'y': send_dic = {"type": "buy_vip", "session": user_info["session"]} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print(back_dic["msg"]) break else: print(back_dic["msg"]) else: print("歡迎下次購買") break def check_movie(client): send_dic = {"type": "get_movie_list", "session": user_info["session"], "movie_type": "all"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: movie_list = back_dic["movie_list"] for i, m in enumerate(movie_list, start=1): print("%s:%s-%s" % (i, m[0], m[1])) else: print(back_dic["msg"]) def download_free_movie(client): send_dic = {"type": "get_movie_list", "session": user_info["session"], "movie_type": "free"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: movie_list = back_dic["movie_list"] for i, m in enumerate(movie_list, start=1): print("%s:%s-%s" % (i, m[0], m[1])) while True: choice = input("請選擇要下載的電影編號>>:").strip() if choice == 'q': break if choice.isdigit(): choice = int(choice) if choice in range(1, len(movie_list) + 1): send_dic = {"type": "download_movie", "session": user_info["session"], "movie_id": movie_list[choice - 1][2], "movie_type": "free"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print("請等待》》》") time.sleep(back_dic["wait_time"]) file_path = os.path.join(setting.DOWNLOAD_MOVIE_DIR, back_dic["file_name"]) recv_size = 0 with open(file_path, 'wb') as f: while recv_size < back_dic["file_size"]: data = client.recv(1024) f.write(data) recv_size += len(data) print("下載成功") return else: print(back_dic["msg"]) else: print("choice not in range") else: print("choice must be number") else: print(back_dic["msg"]) def download_charge_movie(client): if user_info["is_vip"]: charge = input("請支付10元(y/n)>>:").strip() else: charge = input('請支付20元(y/n)>>:').strip() if charge != "y": print("慢走 不送") return send_dic = {"type": "get_movie_list", "session": user_info["session"], "movie_type": "charge"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: movie_list = back_dic["movie_list"] for i, m in enumerate(movie_list, start=1): print("%s:%s-%s" % (i, m[0], m[1])) while True: choice = input("請選擇要下載的電影編號>>:").strip() if choice == 'q': break if choice.isdigit(): choice = int(choice) if choice in range(1, len(movie_list) + 1): send_dic = {"type": "download_movie", "session": user_info["session"], "movie_id": movie_list[choice - 1][2], "movie_type": "free"} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: print("請等待》》》") time.sleep(back_dic["wait_time"]) file_path = os.path.join(setting.DOWNLOAD_MOVIE_DIR, back_dic["file_name"]) recv_size = 0 with open(file_path, 'wb') as f: while recv_size < back_dic["file_size"]: data = client.recv(1024) f.write(data) recv_size += len(data) print("下載成功") return else: print(back_dic["msg"]) else: print("choice not in range") else: print("choice must be number") else: print(back_dic["msg"]) def download_movie_record(client): """ 思路:當前登陸的用戶須要查看本身的觀影記錄,須要獲得電影名 :param client: :return: """ send_dic ={"type":"download_movie_record","session":user_info["session"]} back_dic = common.send_back(send_dic,client) if back_dic["flag"]: back_record_list =back_dic['back_record_list'] for m in back_record_list: print(m) else: print(back_dic['msg']) def check_notice(client): """ 查看公告思路: :param client: :return: """ send_dic = {"type": "check_notice", "session": user_info["session"]} back_dic = common.send_back(send_dic, client) if back_dic["flag"]: back_record_list = back_dic['back_notice_list'] for m in back_record_list: print(m) else: print(back_dic['msg']) func_dic = { "1": register, "2": login, "3": buy_vip, "4": check_movie, "5": download_free_movie, "6": download_charge_movie, "7": download_movie_record, "8": check_notice } def user_view(): client = tcpclient.get_client() while True: print(""" 一、註冊 二、登陸 三、購買會員 四、查看全部電影 五、下載免費電影 六、下載收費電影 七、查看觀影記錄 八、查看公告 """) choice = input("please choice your number>>:").strip() if choice == 'q': break if choice not in func_dic: print("choice err not in range") continue func_dic.get(choice)(client)
#lib
import hashlib import json import os import struct from conf import setting def send_back(send_dic,client,file=None): json_bytes = json.dumps(send_dic).encode("utf-8") client.send(struct.pack('i', len(json_bytes))) client.send(json_bytes) if file: with open(file,'rb') as f: for line in f: client.send(line) recv_len = struct.unpack('i', client.recv(4))[0] recv_dic = json.loads(client.recv(recv_len).decode("utf-8")) return recv_dic def get_md5(password): md = hashlib.md5() md.update(password.encode("utf-8")) return md.hexdigest() def get_movie(): movie_list = os.listdir(setting.UPDATE_MOVIE) return movie_list def get_file_md5(path): md = hashlib.md5() file_size = os.path.getsize(path) file_list = [0,file_size//3,(file_size//3)*2,file_size-10] with open(path,"rb") as f: for line in file_list: f.seek(line) md.update(f.read(10)) return md.hexdigest()
#Tcpclient
import socket def get_client(): client = socket.socket() client.connect(("127.0.0.1",1688)) return client
#start
import os ,sys from core import src sys.path.append(os.path.dirname(__file__)) if __name__ == '__main__': src.run()
##八、服務端代碼
#conf
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 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") 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) title = StringField("title") 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
import os from db import models from lib import common from conf import setting @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(back_dic, conn) @common.login_auth def update_movie(recv_dic, conn): file_name = common.get_session(recv_dic["file_name"]) + recv_dic["file_name"] file_path = os.path.join(setting.MOVIE_DIR, file_name) print(recv_dic) recv_size = 0 with open(file_path, 'wb') as f: while recv_size < recv_dic["file_size"]: data = conn.recv(1024) f.write(data) recv_size += len(data) movie_obj = models.Movie(name=file_name, path=file_path, is_free=recv_dic.get("is_free"), is_delete=0, create_time=common.get_time(), 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(back_dic, conn) @common.login_auth def delete_movie(recv_dic,conn): movie_obj = models.Movie.select(id = recv_dic.get('movie_id'))[0] movie_obj.is_delete = 1 movie_obj.update() back_dic = {"flag":True,"msg":"刪除成功"} common.send_back(back_dic,conn) @common.login_auth def release_notice(recv_dic,conn): title =recv_dic["title"] content = recv_dic["content"] user_id = recv_dic["user_id"] create_time = common.get_time() notice_obj = models.Notice(title=title,content=content,user_id=user_id,create_time=create_time) notice_obj.save() back_dic = {"flag":True,"msg":"發佈成功"} common.send_back(back_dic,conn)
from db import models from lib import common from Tcpserver import user_data def register(recv_dic, conn): user_list = models.User.select(name=recv_dic["name"]) if user_list: back_dic = {"flag": False, "msg": "用戶已存在"} common.send_back(back_dic, conn) return user_obj = models.User(name=recv_dic["name"], password=recv_dic["password"], is_locked=0, is_vip=0, user_type=recv_dic["user_type"],register_time=common.get_time()) user_obj.save() back_dic = {"flag":True,"msg":"註冊成功"} common.send_back(back_dic,conn) def login(recv_dic,conn): user_list = models.User.select(name = recv_dic["name"]) if user_list: user_obj = user_list[0] if user_obj.user_type == recv_dic["user_type"]: if user_obj.password ==recv_dic["password"]: back_dic = {"flag":True,"msg":"登錄成功","is_vip":user_obj.is_vip} #獲取每一個用戶的惟一隨機字符串,用於標識每一個用戶 session = common.get_session(user_obj.name) back_dic["session"]=session #服務端要記錄正在登陸的客戶端,將數據user_data文件live_user字典中,放在爲了更好的標識 #每個客戶,字典的key爲recv_dic["addr"] -----他是一個元組包含ip和端口,值的話是一個列表 #保存每個session和用戶id #由於時公共數據,且併發會形成數據錯亂,我們給他來個鎖 user_data.mutex.acquire() user_data.live_user[recv_dic["addr"]]=[session,user_obj.id] user_data.mutex.release() else: back_dic ={"flag":False,"msg":"密碼不正確"} else: back_dic = {"flag":False,"msg":"用戶類型不對"} else: back_dic ={"flag":False,"msg":"用戶不存在"} common.send_back(back_dic,conn) @common.login_auth def get_movie_list(recv_dic, conn): """ 要給調用者返回相應的電影列表:all、free、charge :param recv_dic: :param conn: :return: """ movie_list =models.Movie.select() if movie_list: back_movie_list = [] for movie_obj in movie_list: if not movie_obj.is_delete: if recv_dic["movie_type"] == "all": back_movie_list.append([movie_obj.name,'免費'if movie_obj.is_free else '收費',movie_obj.id]) elif recv_dic["movie_type"] == "free": if movie_obj.is_free: back_movie_list.append([movie_obj.name, '免費' , movie_obj.id]) else: if not movie_obj.is_free: back_movie_list.append([movie_obj.name, '收費', movie_obj.id]) if back_movie_list: back_dic = {"flag":True,"movie_list":back_movie_list} else: back_dic = {"flag":False,"msg":"暫無影片"} else: back_dic = {"flag": False, "msg": "暫無影片"} common.send_back(back_dic,conn)
import os from conf import setting from db import models from lib import common @common.login_auth def buy_vip(recv_dic, conn): user_obj = models.User.select(id=recv_dic['user_id'])[0] if user_obj.is_vip: back_dic = {"flag": False, "msg": "您已是會員啦"} else: user_obj.is_vip = 1 user_obj.save() back_dic = {"flag": True, "msg": "購買成功"} common.send_back(back_dic, conn) @common.login_auth def download_movie(recv_dic, conn): """ 下載電影功能:普通用戶下載須要等待30秒 VIP用下載不須要等待 :param recv_dic: :param conn: :return: """ movie_list = models.Movie.select(id=recv_dic["movie_id"]) if movie_list: movie_obj = movie_list[0] file_path = movie_obj.path user_obj = models.User.select(id=recv_dic["user_id"])[0] 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(file_path), "wait_time": wait_time} download_record = models.DownloadRecord(user_id=user_obj.id, movie_id=movie_obj.id, create_time=common.get_time()) download_record.save() common.send_back(back_dic, conn) with open(file_path, 'rb') as f: for line in f: conn.send(line) else: back_dic = {"flag":False,"msg":"暫無影片"} common.send_back(back_dic,conn) @common.login_auth def download_movie_record(recv_dic,conn): record_list = models.DownloadRecord.select(user_id = recv_dic["user_id"]) back_record_list = [] if record_list: for m in record_list: movie_obj =models.Movie.select(id=m.movie_id)[0] back_record_list.append(movie_obj.name) back_dic = {"flag":True,"back_record_list":back_record_list} else: back_dic ={"flag":False,"msg":"暫無下載記錄"} common.send_back(back_dic,conn) @common.login_auth def check_notice(recv_dic,conn): notice_list =models.Notice.select() back_notice_list = [] if notice_list: for notice_obj in notice_list: back_notice_list.append([notice_obj.title,notice_obj.content]) back_dic = {"flag": True, "back_notice_list": back_notice_list} else: back_dic = {"flag": False, "msg": "暫無下載記錄"} common.send_back(back_dic,conn)
#lib
import hashlib import json import struct import time from functools import wraps from Tcpserver import user_data def send_back(back_dic,conn): json_bytes = json.dumps(back_dic).encode("utf-8") conn.send(struct.pack('i', len(json_bytes))) conn.send(json_bytes) def get_time(): return time.strftime("%Y-%m-%d %X") 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) #登陸了之後服務端user_data文件中live_user就有存在用戶登錄的數據,若是沒有登錄就沒有數據,能夠爲此 #來做爲判斷是否登陸的依據 for values in user_data.live_user.values(): #values = [session,user_id] if args[0]["session"] == values[0]: args[0]["user_id"] =values[1] break if args[0].get("user_id"): func(*args,**kwargs) else: back_dic = {"flag":False,"msg":"請先登陸"} send_back(back_dic,args[1]) return inner
#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='123', database='youku', charset='utf8', autocommit='True' )
import pymysql from orm_pool import db_pool class Mysql(object): def __init__(self): self.conn = db_pool.POOL.connection() self.cursor = self.conn.cursor(pymysql.cursors.DictCursor) def close(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)
from orm_pool.mysql_singleton 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(255)', 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=None): super().__init__(name, column_type, primary_key, default) class ModelMetaClass(type): def __new__(cls, class_name, class_bases, class_attrs): # 我僅僅只想攔截模型表的類的建立過程 if class_name == 'Models': return type.__new__(cls, class_name, class_bases, class_attrs) # 給類放表名,主鍵字段,全部字段 table_name = class_attrs.get('table_name', class_name) # 定義一個存儲主鍵的變量 primary_key = None # 定義一個字典用來存儲用戶自定義的表示表的全部字段信息 mappings = {} # for循環當前類的名稱空間 for k, v in class_attrs.items(): if isinstance(v, Field): mappings[k] = v if v.primary_key: if primary_key: raise TypeError("主鍵只能有一個") primary_key = v.name # 將重複的鍵值對刪除 for k in mappings.keys(): class_attrs.pop(k) if not primary_key: raise TypeError('必需要有一個主鍵') # 將處理好的數據放入class_attrs中 class_attrs['table_name'] = table_name class_attrs['primary_key'] = primary_key class_attrs['mappings'] = mappings return type.__new__(cls, class_name, class_bases, class_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() # select * from userinfo if not kwargs: sql = 'select * from %s' % cls.table_name res = ms.select(sql) else: # select * from userinfo where id = 1 k = list(kwargs.keys())[0] v = kwargs.get(k) sql = 'select * from %s where %s=?' % (cls.table_name, k) # select * from userinfo where id = ? sql = sql.replace('?', '%s') # select * from userinfo where id = %s res = ms.select(sql,v) if res: return [ cls(**r) for r in res] # 將數據庫的一條數據映射成類的對象 # 新增方法 def save(self): ms = Mysql() # insert into userinfo(name,password) values('jason','123') # insert into %s(%s) values(?) fields = [] # [name,password] values = [] args = [] for k,v in self.mappings.items(): if not v.primary_key: # 將id字段去除 由於新增一條數據 id是自動遞增的不須要你傳 fields.append(v.name) args.append('?') values.append(getattr(self,v.name)) # insert into userinfo(name,password) values(?,?) sql = "insert into %s(%s) values(%s)"%(self.table_name,','.join(fields),','.join(args)) # insert into userinfo(name,password) values(?,?) sql = sql.replace('?','%s') ms.execute(sql,values) # 修改方法:基於已經存在了的數據進行一個修改操做 def update(self): ms = Mysql() # update userinfo set name='jason',password='123' where id = 1 fields = [] # [name,password] values = [] pr = None for k,v in self.mappings.items(): if v.primary_key: pr = getattr(self,v.name,v.default) else: fields.append(v.name+'=?') values.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 userinfo set name='?',password='?' where id = 1 sql = sql.replace('?','%s') ms.execute(sql,values) # if __name__ == '__main__': # class Teacher(Models): # table_name = 'teacher' # tid = IntegerField(name='tid',primary_key=True) # tname = StringField(name='tname') # obj = Teacher(tname='jason老師') # obj.save() # res = Teacher.select() # for r in res: # print(r.tname) # print(res) # res = Teacher.select(tid=1) # teacher_obj = res[0] # teacher_obj.tname = 'jason老師' # teacher_obj.update() # res1 = Teacher.select() # print(res1) # class User(Models): # table_name = 'User' # id = IntegerField(name='id', primary_key=True) # name = StringField(name='name') # password = StringField(name='password') # print(User.primary_key) # print(User.mappings) # obj = User(name='jason') # print(obj.table_name) # print(obj.primary_key) # print(obj.mappings)
#Tcpserver
import json import socket import struct import traceback from concurrent.futures import ThreadPoolExecutor from threading import Lock from Tcpserver import user_data from interface import common_interface, admin_interface, user_interface from lib import common pool = ThreadPoolExecutor(20) #全局產生鎖,爲了不循環導入問題,將產生的鎖放到user_data中 mutex = Lock() user_data.mutex=mutex func_dic = { "register":common_interface.register, "login":common_interface.login, "check_movie":admin_interface.check_movie, "update_movie":admin_interface.update_movie, "get_movie_list":common_interface.get_movie_list, "delete_movie":admin_interface.delete_movie, "release_notice":admin_interface.release_notice, "buy_vip":user_interface.buy_vip, "download_movie":user_interface.download_movie, "download_movie_record":user_interface.download_movie_record, "check_notice":user_interface.check_notice } def get_server(): server = socket.socket() server.bind(("127.0.0.1",1688)) 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")) recv_dic["addr"] = str(addr) dispatch(recv_dic, conn) except Exception as e: traceback.print_exc() 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.get("type") in func_dic: func_dic.get(recv_dic["type"])(recv_dic,conn) else: back_dic = {"flag":False,"msg":"類型不合法"} common.send_back(back_dic,conn)
live_user={}
mutex=None
#start
import os ,sys from Tcpserver import tcpserver sys.path.append(os.path.dirname(__file__)) if __name__ == '__main__': tcpserver.get_server()