简介
题目:经理管理上市菜品数据库
经理要进行登录验证,用户名和密码与数据库中匹配才可进入线程写日志
详写
生活水平不断提高,大大小小的餐馆随处可见。每个餐馆都有自己的招牌菜,同时也有一些不太受欢迎的菜品,这就需要对店面的销售菜品种类及价格进行管理,比如将不受喜爱的菜品去除,调整一下价格,增加一些新品等等。对菜品进行实时动态的系统更新,可以方便餐馆运营。
经理对菜品系统的操作包括select(查询),add(增加),delete(删除)。
实现该系统需要4项技术:多线程、文件操作、数据库操作、网络编程。
多线程:编写线程函数,进行写文件操作。
文件操作:包括两个文件,日志文件和查询结果文件。
日志文件记录经理在何时进行了何种操作,如果进行增加操作,记录在何时增加了什么内容,若进行删除,记录在何时删除了哪个菜品。
数据库操作:包含两张表,manager和menu。
manager(name,password)name表示用户名,password表示登录密码
menu(mno,mname,price)mno菜品编号,mname菜品名称,price价格
网络编程:使用TCP传输,模拟client和server,经理在client端操作,菜品管理系统相当于server,反馈给client操作提示。
该系统实现的是经理对数据库中菜品表的查询、修改和删除。查询当前本餐馆销售哪些菜品,包括菜品的编号,名称和价格。如果餐馆增加了一些菜式,在数据库中对这些菜式进行添加。如果某些菜式不再上市,需要从系统中删除,给出要删除的菜品编号,在日志文件中记录被删除的菜品名称。
采用TCP网络连接进行模拟,经理作为client,被管理的上市菜品数据库系统作为server。
client
首先进行登录者判断,用户名和密码和数据库中manager表中的信息一致,则和server建立连接,开始按照server的提示进行通信和操作,输入的指令为bye时,表示不再操作了,断开通信。如果不一致,直接断开连接,不进行进一步的操作。
server
client所有的操作规则全部在server中定义,包括日志文件和查询结果文件的写入。server接收client传过来的消息,首先将它转为str()型,再进行判断要执行的是哪一个操作。对于client发送的每一个操作请求,都会调用线程进行日志文件的写入,在执行完后,也会告诉client操作完毕。
操作1,查询。这个操作比较简单,直接执行sql语句,将查询得到的信息,通过调用线程,逐条写入到查询结果文件中,并在client中显示。
操作2,新增。得到这条指令后,发送给client一条提示信息,“请输入更新内容”,然后再对接收到更新信息进行写入数据库操作。
操作3,删除。基本流程类似操作2,反馈给client一条提示信息,“请输入删除的菜品编号”,接收到编号以后,先查询该编号的菜品名称,进行日志文件的写入,然后再执行删除操作。
若接收到的消息的小写格式为bye,则断开连接,表示操作已完成,同时关闭文件和数据库。
若接收到除以上内容外的信息,则默认为是错误信息,提示client重新输入。
根据系统的功能,画出流程图如图2-1所示。
数据库中包含两个表。
一个是manager表,两列:name和password,分別表示用户名和密码。只有一条记录,即经理的用户名和对应的密码,用户名为jl,密码为python666。用作登录验证。
一个是menu表,三列:mno,mname,price,分别表示菜品编号,菜品名称和价格。初始插入两条记录:1,pork,15和2,chicken,20。
两个表结构如图2-2和2-3所示。
首先创建一个数据库,python.db。在数据库中创建两个表,具体如上一节中的数据库设计所示。
用到三个模块:socket,sys,sqlite3。
对于登录验证,要求输入的格式是以逗号分隔用户名和密码,然后对输入的整个字符串进行拆分,拆分格式就是以逗号为分界符,放到数组中,代码实现为:
c = input('Input thecontent:')
c=c.split(',')
将从数据库manager表中查询到的用户名和密码分别存放到user和pwd中,如果列表第一个元素与user相同,且第二个元素与pwd相同,则登录成功,否则失败,断开连接,代码实现:
if c[0]==user andc[1]==pwd:
print("登录成功!")
……(继续操作)
else:
print("登录失败!")
(关闭连接)
用到的模块有socket,os.path中的 commonprefix,sqlite3,time,threading,re。
连接数据库,并且以追加写的格式打开两个文本文件,log.txt作为日志文件,cp.txt作为查询结果文件,代码实现:
con=sqlite3.connect("D://python程序设计/大作业/python.db")
f1=open('D://python程序设计/大作业/log.txt','a+')
f2=open('D://python程序设计/大作业/cp.txt','a+')
定义四个线程函数,其中三个是和日志文件相关,一个和查询结果文件相关。三个日志文件相关函数中,一个记录经理在何时进行了何种操作,含有一个参数,为操作类型;一个记录经理在何时新增了哪些内容,参数为增加的内容;还有一个记录经理删除了哪个菜品,参数是根据输入的菜品编号查询到的菜品名称。时间使用time模块的ctime()方法。
从client接收到的信息转化为str()类型,然后和规定的操作进行匹配。
查询。如果接收到的为select,先启动线程进行日志文件的写入,然后执行sql语句,进行数据库查询,对于每一个查询结果,调用一个线程进行查询结果文件写入,执行完后返回给client查询结果,代码实现:
t1=threading.Thread(target=w_log1,args=(data,))
t1.start()
jl=con.execute("select* from menu")
j2=[]
for i in jl:
t2=threading.Thread(target=w_cp,args=(i,))
t2.start()
j2.append(i)
answers=str(j2)
conn.sendall(answers.encode())
新增。如果接收到的是add,这个模块只进行日志文件写入,返给client一条“请输入新增内容”的提示信息,具体的增加在输入新增内容再进行操作。判断输入的内容是不是要新增的记录使用正则表达式,match()方法进行匹配,以数字开头,然后是字母,再数字,一条记录之间用逗号分隔,如果一次输入不止一条记录,记录之间用空格分隔,r'\b\d,[a-zA-Z]+,\d.*\b'。将输入的整个字符串以先以空格作为分隔符进行拆分,放到列表里。然后,遍历列表,对于该列表中的每一个元素再进行拆分,以逗号作为分隔符,放入另一个列表中,每个元素插入到对应的数据库表中。执行完后,发送给client“新增完毕”的提示信息,代码实现:
data=data.split(' ')
for i in data:
i=i.split(',')
t1=threading.Thread(target=w_log2,args=(i,))
t1.start()
con.execute("insert into menuvalues(?,?,?)",(int(i[0]),i[1],int(i[2])))
con.commit()
删除。如果接收到的是delete,只进行日志文件写入,提示client“请输入删除的菜品编号”。具体的删除操作在输入编号后进行,判断输入的是否是要删除的菜品编号,使用正则表达式,条件是只含有数字,r'\b\d*\d\b'。如果直接把接收到的数字写在sql语句里,计算机会把它看作是一个常量,并不是变量,所以分两步执行,先把sql语句单独赋给一个变量,将菜品编号连接到语句中,再使用execute()执行。因为要将删除的菜品名称写在日志文件中,所以在删除之前,首先要根据菜品编号查询对应的菜品名称,再进行删除操作。执行完后,发送给client“删除完毕”的提示消息。代码实现:
s='select mname from menuwhere mno='+str(data)
result=con.execute(s)
for i in result:
t1=threading.Thread(target=w_log3,args=(i,))
t1.start()
s='delete from menu wheremno='+str(data)
con.execute(s)
con.commit()
正常结束操作。如果接收到的信息的小写格式是bye,关闭两个文件,然后断开连接。
如果接收到除以上内容的其他信息,则提示client“输入的信息有误,请重输”。
登录成功,先进行查询操作,然后新增记录,再查询一次,输入其他内容,验证是否提示信息有误。接下来是删除操作,再进行查询。
如图4-2所示
相应的日志文件内容如图4-3所示。
在这次设计中,让我学会了如何综合利用学到的知识,如何将各项技术结合在一起,来完成一个比较大的系统。
虽然这次设计只是简单的模拟经理对餐馆上市菜品的管理,但是通过这个环节的设计,我体会到项目开发的困难和计算机编程技术对生产生活的重要作用,为社会带来极大的便利。
在设计过程中,遇到很多问题,比如,只能在client端输入一次信息,却无法得到设想的返回结果,之后查到原因是server未返回给client结果,消息的传递是相互的,一端发送,另一端必须也返回一个结果。印象最深的一个问题是,在关闭连接时,server总是提示一个属性错误,如图5-1所示。
原因是变量重名,将控制TCP连接的变量和一个字符串变量名字重复了,修改一下字符串的名字,错误就解决了。虽然这个错误很小,但是整个设计过程中,直到最后才发现错误的原因,让我体会到编程时一定要细心检查,即使这个错误对于整个系统并没有什么影响。python语言在使用变量时,虽然不用对变量进行定义,一个变量名可以代表不同的类型,但是也要进行区分,不同的变量类型最好不要相同的变量名,否则就会造成错误,不容易被发现。
import sqlite3
import re
con=sqlite3.connect("python.db")
con.execute("create table manager(name text,password text)")
con.execute("create table menu(mno int,mname text,price int)")
con.execute("insert into manager values('jl','python666')")
cd=[(1,'pork',15),(2,'chicken',20)]
con.executemany("insert into menu values(?,?,?)",cd)
con.commit()
#coding=utf-8
import socket
import sys
import sqlite3
HOST = '127.0.0.1'
PORT = 50007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((HOST, PORT))
except Exception as e:
print('Server not found or not open')
sys.exit()
con=sqlite3.connect("D://python程序设计/大作业/python.db")
result=con.execute('select * from manager')
for i in result:
user=i[0]
pwd=i[1]
c = input('Input the content:')
c=c.split(',')
if c[0]==user and c[1]==pwd:
print("登录成功!")
while True:
c = input('Input the content:')
s.sendall(c.encode())
data = s.recv(1024)
data = data.decode()
print('Received:', data)
if data=='bye':
s.close()
if c.lower() == 'bye':
break
else:
print("登录失败!")
s.close()
#coding=utf-8
import socket
from os.path import commonprefix
import sqlite3
import time
import threading
import re
HOST = ''
PORT = 50007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
print('Listening on port:',PORT)
conn, addr = s.accept()
print('Connected by', addr)
con=sqlite3.connect("D://python程序设计/大作业/python.db")
f1=open('D://python程序设计/大作业/log.txt','a+')
f2=open('D://python程序设计/大作业/cp.txt','a+')
def w_log1(opt):
f1.write('manager在'+time.ctime()+'进行了'+opt+'操作\n')
def w_log2(cp):
f1.write('manager在'+time.ctime()+'新增了'+str(cp)+'\n')
def w_log3(mname):
f1.write('manager在'+time.ctime()+'删除了'+str(mname)+'\n')
def w_cp(ss):
f2.write(str(ss)+'\n')
while True:
data=conn.recv(1024).decode()
data=str(data)
if data=='select':
t1=threading.Thread(target=w_log1,args=(data,))
t1.start()
jl=con.execute("select * from menu")
j2=[]
for i in jl:
t2=threading.Thread(target=w_cp,args=(i,))
t2.start()
j2.append(i)
answers=str(j2)
conn.sendall(answers.encode())
elif data=='add':
t1=threading.Thread(target=w_log1,args=(data,))
t1.start()
answers='请输入新增内容'
conn.sendall(answers.encode())
elif re.match(r'\b\d,[a-zA-Z]+,\d.*\b',data):
data=data.split(' ')
for i in data:
i=i.split(',')
t1=threading.Thread(target=w_log2,args=(i,))
t1.start()
con.execute("insert into menu values(?,?,?)",(int(i[0]),i[1],int(i[2])))
con.commit()
answers='新增完毕'
conn.sendall(answers.encode())
elif data=='delete':
t1=threading.Thread(target=w_log1,args=(data,))
t1.start()
answers='请输入删除的菜品编号'
conn.sendall(answers.encode())
elif re.match(r'\b\d*\d\b',data):
s1='select mname from menu where mno='+str(data)
result=con.execute(s1)
for i in result:
t1=threading.Thread(target=w_log3,args=(i,))
t1.start()
s2='delete from menu where mno='+str(data)
con.execute(s2)
con.commit()
answers='删除完毕'
conn.sendall(answers.encode())
elif data.lower()=='bye':
f1.close()
f2.close()
answers='bye'
conn.sendall(answers.encode())
break
else:
answers='输入的信息有误,请重输!'
conn.sendall(answers.encode())
conn.close()
s.close()