電子詞典階段性小項目

電子詞典階段性項目

項目需求介紹

登陸註冊

用戶能夠登陸和註冊python

用戶名 密碼mysql

用戶名不能重複linux

用戶信息能夠長期保存sql

網絡鏈接

程序分爲服務端,客戶端數據庫

服務端負責數據處理django

啓動服務端後應知足多個客戶端同時操做編程

圖形二級界面

經過基本的圖形界面 print 提示客戶端輸入後端

啓動後進入一級界面bash

一級界面功能服務器

  登陸 註冊 退出

    登錄: 成功進入二級界面

    註冊: 成功返回一級界面,或者直接使用註冊用戶進入二級界面

    退出: 退出軟件

二級界面功能

  查單詞 歷史記錄 註銷

  查單詞: 循環輸入單詞.獲得單詞解釋,輸入特殊符號退出查詢狀態 

    直接使用單詞本查詢.(文本操做)

    現將單詞本存入數據庫查詢.經過數據庫查詢

  歷史記錄:  查詢當前用戶的查詞記錄 要求記錄包含 name word time能夠查看全部記錄,或者只顯示前10條

  註銷: 返回上級頁面

單詞庫

特色

  文本形式 

  每一個單詞佔一行

  單詞按照從小到大順序排列

  單詞和解釋之間必定有空格

形式以下的一個 e_dict.txt 文件

 

a indef art one abacus n.frame with beads that slide along parallel rods, used for teaching numbers to children, and (in some countries) for counting abandon v. go away from (a person or thing or place) not intending to return; forsake; desert abandonment n. abandoning abase v. ~ oneself/sb lower oneself/sb in dignity; degrade oneself/sb ; abash to destroy the self-possession or self-confidence of:disconcert abashed adj. ~ embarrassed; ashamed abate v. make or become less abattoir n. = slaughterhouse (slaughter) abbess n. woman who is head of a convent or nunnery abbey n. buildingin which monks or nuns live as a community under an abbot or abbess abbot n. man who is head of a monastery or abbey abbreviate v. ~ sth shorten (a word, phrase, etc), esp by omitting letters

項目目的

練如下的技能

socket, 協程, MySQL, pymysql, 

項目結果展現

服務端啓動

 服務端執行啓動後等待客戶端鏈接 

客戶端啓動 

客戶端鏈接後顯示一級界面

 

註冊功能展現

  用戶名輸入,密碼,重複密碼輸入,都沒法展現.註冊成功後返回一級界面

登陸功能展現

  登陸輸入驗證成功後進入二級界面

查詢功能展現

   查詢單詞後返回查詢結果,輸入## 退出查看 返回二級目錄

歷史記錄功能查看

   返回當前用戶最近10條歷史查詢單詞,而後返回二級菜單

註銷功能

  註銷當前用戶返回一級菜單 

 

退出程序展現

項目需求分析整理

大白話思路

首先想好要用哪些技術.

數據庫這邊選擇 mysql. 而後使用 pymysql 鏈接數據庫這定死了.

  而後就是數據庫表結構設計

而後就是網絡部分選擇 tcp_socket 分爲服務端客戶端兩套來作.各自處理各自的邏輯也沒得說

而後關於高併發問題用 多進/線/協程來處理,這裏選擇最簡單的協程最方便

而後就是具體的邏輯代碼問題了.

 

邏輯方面:

  登陸驗證這塊就和數據庫的用戶表打交道了.

  查詞這塊是和字典表來查詢.查詢這塊的選擇方式爲了知識點覆蓋咱們不經過數據庫來查詢.基於文件來查詢

  歷史記錄這塊重點就是sql語句的問題了,要排序要限制取數據條數,這塊爲了技術覆蓋面咱們要基於數據庫來

 

那總體差很少就是這樣的分析了.首先設計表結構把

 

mysql 表結構設計

首先這裏咱們可使用外鍵,這樣設計是能夠的.可是鑑於咱們僅僅是練習仍是特麼這麼簡單的結構設計就懶得這麼折騰了

table user ----------------------- id | username |  pwd | 
----------------------- table user_hist -------------------------- id | user_id |  hist_id | 
-------------------------- table user_history ----------------------------- id | use_id | dict_id | time ----------------------------- table dict_hist --------------------------- id | dict_id |  hist_id | 
--------------------------- table e_dict.txt ----------------- id | word | val -----------------

因此咱們使用更簡單的結構設計,直接三張表解決吧

其次還須要作一件事就是要把 e_dict.txt 文件中的 數據添加到數據庫中

這裏存在着優化點.

  文件中有 2w條數據,本覺得不算特別大竟然也用了接近5分鐘.這個效率不敢恭維

  所以可使用 將數據整合在列表中,而後用 excutemany 來實現批量的添加更加高效.

""" 一次性的執行腳本文件 目的 實現將文件中的 數據插入在數據庫中 """
import pymysql f = open("e_dict.txt") db = pymysql.connect("127.0.0.1", "root", "123456", "dict") cursor = db.cursor() for line in f:  # 循環讀取文件內容
    tmp = line.split(" ") word = tmp[0] mean = " ".join(tmp[1:]).strip() sql = 'insert into words (word, mean) values ("%s","%s")' % (word, mean) try: cursor.execute(sql) db.commit() except: db.rollback() f.close()

 執行完畢後,數據庫的相關操做就所有完成了

 業務邏輯

 服務端和客戶端彼此的職能是徹底不一樣的,可是須要數據的交互

業務邏輯也所有都是彼此分割

總框架

服務端

前面使用了 sys.argv, 此方法用與獲取用戶命令行操做的參數.這樣能夠實現用戶不經過修改源碼的方式實現自定義屬性的傳入

此方法在 django ,scarpy 中都有體現

 

服務端這須要建立tcp_socket 以及併發的相關操做.以及一些其餘的善後處理.固然還有與 DB 的交互也要完成

而且定義了 相關的業務邏輯操做碼.以及 基於協程的 併發模式處理 

def main(): # 鏈接數據庫
    db = pymysql.connect("127.0.0.1", "root", "123456", "dict") # 建立套接字
    s = socket() # s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 保證端口複用的
 s.bind(ADDR) s.listen(5) # 殭屍進程的處理
    # signal.signal(signal.SIGCHLD, signal.SIG_IGN)

    # 循環等待鏈接
    while True: try: c, addr = s.accept() print("Connect from", addr) except KeyboardInterrupt: s.close() sys.exit("服務器退出") continue

        # 建立子進程
 gevent.spawn(do_child, c, db) # 若是在 linux 下直接這樣也能夠
        # pid = os.fork()
        # if pid == 0:
        # s.close()
        # do_child(c, db)
        # sys.exit()
        # else:
        # c.close()

def do_child(c, db): while True: data = c.recv(1024).decode() print(c.getpeername(), ":", data) if not data or data[0] == "E": c.close() sys.exit() elif data[0] == "R": do_register(c, db, data) elif data[0] == "L": do_login(c, db, data) elif data[0] == "Q": do_query(c, db, data) elif data[0] == "H": do_history(c, db, data) return

客戶端

客戶端要實現一個圖形界面的展現以及用戶的操做指引.順便須要對用戶的操做指定 操做碼 

好比在框架中能夠提現 退出程序 的操做碼爲 " E "

 

一樣和服務端同樣, 客戶端也須要在命令行進行定製

def main(): if len(sys.argv) < 3: print(""" argv is error""") return HOST = sys.argv[1] PORT = int(sys.argv[2]) s = socket() try: s.connect((HOST, PORT)) except Exception as e: print(e) return

    while True: print(""" =====================Welcome====================== -- 1. 註冊 2.登陸 3 退出 -- ================================================== """) try: cmd = int(input("輸入選項:")) except Exception as e: print("命令錯誤") continue
        if cmd not in [1, 2, 3]: print("請輸入正確選項") continue
        elif cmd == 1: do_register(s) elif cmd == 2: do_login(s) elif cmd == 3: s.send(b'E') sys.exit("謝謝使用")


登陸業務邏輯

服務端

服務端對業務邏輯 爲登陸的時候定義 操做碼爲 " L ", 而後定義經過 do_login函數具體操做

 

do_login 函數具體取到 客戶端的發送數據而後格式化處理後在數據庫查詢用戶驗證

判斷基於狀態返回

def do_login(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] sql = "select * from user where name = '%s' and pwd = '%s'" % (name, passwd) # 查找用戶
    cursor = db.cursor() cursor.execute(sql) r = cursor.fetchone() if r == None: c.send(b'FAIL') else: c.send(b"OK") return

 客戶端

客戶端的操做主要是獲取用戶數據而後發送 與服務端約定的格式和操做碼.而後等待服務端的回傳消息

 

在基於回傳消息進行對用戶的信息展現.

  若是 登陸成功會進一步的 經過 login 函數進入二級界面.

def do_login(s): name = input("User:") passwd = getpass.getpass() msg = "L %s %s" % (name, passwd) s.send(msg.encode()) data = s.recv(128).decode() print(data) if data == "OK": print("登錄成功") login(s, name) else: print("登陸失敗") return

註冊業務邏輯 

服務端

依舊和登陸相似,數據結構處理後對數據庫的操做

中間有一步對用戶名存在的判斷

def do_register(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] cursor = db.cursor() sql = "select * from user where name = '%s' " % name cursor.execute(sql) r = cursor.fetchone() if r != None: c.send(b"EXISTS") return

    # 插入用戶
    sql = "insert into user (name, pwd) value ('%s','%s')" % (name, passwd) try: cursor.execute(sql) db.commit() c.send(b"OK") except: db.rollback() c.send(b"FAIL") return

客戶端

客戶端依舊是獲取用戶數據以及狀態消息回傳. 密碼部分的顯示用了 getpass 來隱藏輸入顯示

def do_register(s): while True: name = input("User:") passwd = getpass.getpass() passwd1 = getpass.getpass("Again:") if (" " in name) or (" " in passwd): print("用戶名密碼不能有空格") continue
        if passwd != passwd1: print("兩次密碼不一致") continue msg = "R %s %s" % (name, passwd) # 發送請求
 s.send(msg.encode()) # 等待迴應
        data = s.recv(128).decode() if data == "OK": print("註冊成功") # login(s,name) # 註冊成功進入二級界面
            return
        elif data == "EXISYS": print("用戶已存在") else: print("註冊失敗") return

查詢業務邏輯

服務端

服務端的查詢須要拿到單詞的解釋以及分區是否能夠查詢到,

未查詢到的時候要返回具體的錯誤消息(由於客戶端很差判斷,這裏直接返回具體消息更方便)

由於這裏使用過的是基於文件的查詢,在判斷對比 查詢字段的時候 基於 字符串的排序,由於文件內的字段都是排序過的.直接這樣對比能夠更加快速

固然也須要考慮 "zzzz" 這樣的單詞到死也查詢不到的可能性,所以須要注意判斷的嵌套,最外層要又一次表示

def do_query(c, db, data): l = data.split(" ") name = l[1] word = l[2] # 插入歷史記錄
    cursor = db.cursor() tm = time.ctime() sql = "insert into hist (name, word, time) value ('%s','%s','%s')" % (name, word, tm) try: cursor.execute(sql) db.commit() except: db.rollback() f = open(DICT_TEXT) for line in f: tmp = line.split(" ")[0]  # 獲取單詞
        if tmp > word: break
        elif tmp == word: c.send(line.encode()) f.close() return c.send("沒有該單詞".encode()) f.close()

客戶端

 客戶端的查詢邏輯,獲取用戶輸入.基於結束碼結束

這裏須要注意的是不肯定是否拿到的是單詞解釋仍是由於找不到單詞而返回的結果.

可是不管是什麼這裏很差區分咱們由後端來區分,這裏直接所有展現便可

def do_query(s, name): while True: word = input("請輸出查詢單詞:") if word == "##": break msg = "Q %s %s" % (name, word) s.send(msg.encode()) # 單詞解釋 / 找不到
        data = s.recv(2048).decode() print(data)

訪問歷史業務邏輯

服務端

主要仍是 mysql 的使用 sql 語句,要用到排序和限制.

須要注意的是數據庫建立的時候圖方便沒使用 time 相關的字段而是用了字符串

所以這裏排序不能按照時間,要按照自增的 id 倒敘來保證最近時間的數據可靠性

def do_history(c, db, data): name = data.split(" ")[1] cursor = db.cursor() sql = "select * from hist where name='%s' order by id desc limit 10" % name cursor.execute(sql) r = cursor.fetchall() if not r: c.send(b"FAIL") else: c.send(b"OK") time.sleep(0.1) for i in r: msg = "%s %s %s" % (i[1], i[2], i[3]) c.send(msg.encode()) time.sleep(0.1) c.send(b"##")

客戶端

客戶端這塊就沒什麼好說的了,惟一須要注意的是由於你不知道要查詢的字段到底有多少數據,

所以直接選擇用 死循環不停接收便可,也由於死循環,須要定義一個結束標識, 即 "##"

def do_history(s,name): msg = "H %s" %name s.send(msg.encode()) data = s.recv(128).decode() if data == "OK": while True: data = s.recv(1024).decode() if data == "##": break
            print(data) else: print("沒有歷史記錄")

總結

整合了網絡編程,併發編程.mysql,以及前期階段的集成項目.很是適合練手.

項目雖然完成可是中間有大量的優化空間,

  尤爲是大量的冗餘代碼.好比 DB 操做能夠整合爲一個 工具類這樣 服務端中的數據庫操做會更加溫馨

  其次全程使用 函數式編程,整合成 面向對象方式更加清晰.

  還有爲了不沾包問題,這裏用了很蠢的 sleep 來處理也是優化空間

  窩草.通篇忘了使用 pymysql 的防注入了.全程鐵頭本身拼字符串還行...醉了....回過頭來看問題真的多

以上差很少就是這樣吧,,,算了算了...心放寬...寬...

所有代碼

客戶端

from socket import *
import sys import getpass # 建立網絡鏈接
def main(): if len(sys.argv) < 3: print(""" argv is error""") return HOST = sys.argv[1] PORT = int(sys.argv[2]) s = socket() try: s.connect((HOST, PORT)) except Exception as e: print(e) return

    while True: print(""" =====================Welcome====================== -- 1. 註冊 2.登陸 3 退出 -- ================================================== """) try: cmd = int(input("輸入選項:")) except Exception as e: print("命令錯誤") continue
        if cmd not in [1, 2, 3]: print("請輸入正確選項") continue
        elif cmd == 1: do_register(s) elif cmd == 2: do_login(s) elif cmd == 3: s.send(b'E') sys.exit("謝謝使用") def do_register(s): while True: name = input("User:") passwd = getpass.getpass() passwd1 = getpass.getpass("Again:") if (" " in name) or (" " in passwd): print("用戶名密碼不能有空格") continue
        if passwd != passwd1: print("兩次密碼不一致") continue msg = "R %s %s" % (name, passwd) # 發送請求
 s.send(msg.encode()) # 等待迴應
        data = s.recv(128).decode() if data == "OK": print("註冊成功") # login(s,name) # 註冊成功進入二級界面
            return
        elif data == "EXISYS": print("用戶已存在") else: print("註冊失敗") return


def do_login(s): name = input("User:") passwd = getpass.getpass() msg = "L %s %s" % (name, passwd) s.send(msg.encode()) data = s.recv(128).decode() print(data) if data == "OK": print("登錄成功") login(s, name) else: print("登陸失敗") return


def login(s, name): while True: print(""" =====================Welcome====================== -- 1. 查詞 2.歷史記錄 3 註銷 -- ================================================== """) try: cmd = int(input("輸入選項:")) except Exception as e: print("命令錯誤") continue
        if cmd not in [1, 2, 3]: print("請輸入正確選項") continue
        elif cmd == 1: do_query(s, name) elif cmd == 2: do_history(s,name) elif cmd == 3: return


def do_query(s, name): while True: word = input("請輸出查詢單詞:") if word == "##": break msg = "Q %s %s" % (name, word) s.send(msg.encode()) # 單詞解釋 / 找不到
        data = s.recv(2048).decode() print(data) def do_history(s,name): msg = "H %s" %name s.send(msg.encode()) data = s.recv(128).decode() if data == "OK": while True: data = s.recv(1024).decode() if data == "##": break
            print(data) else: print("沒有歷史記錄") if __name__ == '__main__': main()

 服務端

from gevent import monkey monkey.patch_all() from socket import *
import pymysql import sys import time import gevent import os # 制定輸入格式,提供用戶輸入指引
if len(sys.argv) < 3: print(""" Start as: python3 dict_server.py 0.0.0.0 8000 """) sys.exit() # 定義全局變量
HOST = sys.argv[1]  # sys.argv 獲取命令行參數
PORT = int(sys.argv[2]) ADDR = (HOST, PORT) DICT_TEXT = "e_dict.txt"


# 搭建網絡鏈接
def main(): # l鏈接數據庫
    db = pymysql.connect("127.0.0.1", "root", "123456", "dict") # 建立套接字
    s = socket() # s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 保證端口複用的
 s.bind(ADDR) s.listen(5) # 殭屍進程的處理
    # signal.signal(signal.SIGCHLD, signal.SIG_IGN)

    # 循環等待鏈接
    while True: try: c, addr = s.accept() print("Connect from", addr) except KeyboardInterrupt: s.close() sys.exit("服務器退出") continue

        # 建立子進程
 gevent.spawn(do_child, c, db) # 若是在 linux 下直接這樣也能夠
        # pid = os.fork()
        # if pid == 0:
        # s.close()
        # do_child(c, db)
        # sys.exit()
        # else:
        # c.close()


# 處理客戶端請求
def do_child(c, db): while True: data = c.recv(1024).decode() print(c.getpeername(), ":", data) if not data or data[0] == "E": c.close() sys.exit() elif data[0] == "R": do_register(c, db, data) elif data[0] == "L": do_login(c, db, data) elif data[0] == "Q": do_query(c, db, data) elif data[0] == "H": do_history(c, db, data) return


def do_register(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] cursor = db.cursor() sql = "select * from user where name = '%s' " % name cursor.execute(sql) r = cursor.fetchone() if r != None: c.send(b"EXISTS") return

    # 插入用戶
    sql = "insert into user (name, pwd) value ('%s','%s')" % (name, passwd) try: cursor.execute(sql) db.commit() c.send(b"OK") except: db.rollback() c.send(b"FAIL") return


def do_login(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] sql = "select * from user where name = '%s' and pwd = '%s'" % (name, passwd) # 查找用戶
    cursor = db.cursor() cursor.execute(sql) r = cursor.fetchone() if r == None: c.send(b'FAIL') else: c.send(b"OK") return


def do_query(c, db, data): l = data.split(" ") name = l[1] word = l[2] # 插入歷史記錄
    cursor = db.cursor() tm = time.ctime() sql = "insert into hist (name, word, time) value ('%s','%s','%s')" % (name, word, tm) try: cursor.execute(sql) db.commit() except: db.rollback() f = open(DICT_TEXT) for line in f: tmp = line.split(" ")[0]  # 獲取單詞
        if tmp > word: break
        elif tmp == word: c.send(line.encode()) f.close() return c.send("沒有該單詞".encode()) f.close() def do_history(c, db, data): name = data.split(" ")[1] cursor = db.cursor() sql = "select * from hist where name='%s' order by id desc limit 10" % name cursor.execute(sql) r = cursor.fetchall() if not r: c.send(b"FAIL") else: c.send(b"OK") time.sleep(0.1) for i in r: msg = "%s %s %s" % (i[1], i[2], i[3]) c.send(msg.encode()) time.sleep(0.1) c.send(b"##") if __name__ == '__main__': main()
相關文章
相關標籤/搜索