13 Python 阶段性总结《抽奖系统》
大家好,今天是python 的阶段性总结,经过前面的学习,我们需要用一个小Demo(抽奖系统)来巩固我们的所学所识;
1、抽奖系统介绍
这是一个通过命令行执行抽奖的操作系统,没有华丽的界面。
1.1 功能模块介绍
1、base
模块名 | 功能 |
---|---|
base | 不做业务逻辑,只做底层操作,例:用户、奖品增删改查 |
能获得什么?
- 类的创建
- json文件的读写,私有函数的的定义
- 字典的联系+循环的练习
- 条件语句的练习
- 异常语句的处理与抛出
2、admin
模块名 | 功能 |
---|---|
admin(管理员模块) | 继承了base 模块 ,用户的增删改查,奖品的增删改查 |
能获得什么?
- 类的继承
- 多态的练习,super 函数
- 条件语句的练习
- 循环的练习
3、user
模块名 | 功能 |
---|---|
admin(用户模块) | 用户的身份验证+抽奖功能 |
能获得什么?
- 类的继承
- 父类私有函数的调用
- 启蒙与强化开发思维
1.2 三大区域介绍
区域 | 作用 |
---|---|
control(逻辑区域) | 处理用户,管理员相关业务逻辑 |
common(公共区域) | 用来存放一切公共函数,公共常量,以及错误信息等 |
storage(存储区域) | 用来存储数据的 |
1.3 项目搭建
我们的《抽奖系统》,基本架构就长这样了,至于每个文件都都干些什么,我为大家介绍一下。
模块 | 文件 | 作用 |
---|---|---|
common | consts.py | 定义公共常量 |
error.py | 定义异常类 | |
utils.py | 工具类 | |
control | admin.py | 管理用户+奖品 |
base.py | 底层操作 | |
user.py | 用户自身操作 | |
storage | gift.json | 存放奖品数据 |
user.json | 存放用户数据 |
记得给json 文件初始化一下数据结构:
2、编写项目基础类 -- base.py
修建一栋房子,最重要的就是地基,所以我们先来编写base.py模块,来看看我们要做些什么。
- 文件的检查
- 用户信息的增删改查
- 奖品信息的增删改查
2.1 文件的检查
我们说过,storage模块是存储数据,所以我们要保证里面的json 文件它是存在的,这样我们才能往里面存入数据。
相信你前面已经将项目的整体架构搭建出来了,那我们就直接编写代码吧!
我们来定义一个基础的base类,我们叫做base。
接下来我们书写我们的构造函数。
在里边啊,我们要调入我们的user_json以及我们的gift_json,其实他们就是两个文件的地址。
通过self点user_json以及我们的gift_json来定义它,好在我们的类中使用。
接下来我们先写第一个函数,要验证我们的user这个user.json是否存在,
我们写一个内置函数__check_user_json。
大家可以看到上图,如果我们的json地址不存在,不是json 文件,不是文件的话,我们要抛出一个异常。
既然要抛出一个异常,我们就需要一个error我们。
这个时候就可以到公共模块commonly的error去定义它。
# 文件路径不存在
class NotPathError(Exception):
def __init__(self, msg):
self.msg = msg
# 文件不是JSON格式
class FormatError(Exception):
def __init__(self, msg='需要json格式'):
self.msg = msg
# 不是文件格式
class NotFileError(Exception):
def __init__(self, msg='这不是文件,请检查好文件格式'):
self.msg = msg
定义好异常类,我们就回到 base.py 中去使用它。
然后在 init 中调用 这个方法。
写完了检查 user.json 的方法,那gift.json 的是不是也大同小异,我们复制一份,稍微修改一下。
我们看一看上下是不是内容相同啊?
既然内容相同,我们是不是可以抽出来做一个工具类?
我们还是到公共模块commonly的utils.py去定义它。
# coding:utf-8
import os
from .error import NotFileError, NotPathError, FormatError
# 检查json文件的工具类 path:json文件的路径
def check_file(path):
# json 地址是否存在
if not os.path.exists(path):
raise NotPathError(" %s 文件不存在" % path)
# 是否是json文件
if not path.endswith('.json'):
raise FormatError()
# 是否是文件
if not os.path.isfile(path):
raise NotFileError()
编写检查 JSON 文件的工具类,我们就可以到base.py去调用我们的工具类。
大家想必已经看到上面的os 和 我们 继承的 异常类已经没有调用已经变灰了,这里教大家一个快捷键。
ctrl + shift + o
接下来我们编写一个主函数去执行一下代码,看看有没有报错。
执行结果:
没有报错,我们可以修改一下,看看我们的检查代码有没有用。
剩下的大家自己慢慢去尝试吧,到这里我们就完成了文件检查的功能。
2.2 用户信息的CRUD
首先,我们先编写对用户信息的增加吧。
大家想想,一个用户都能拥有哪些信息?
字段 | 备注 |
---|---|
userName | 用户名 |
userPassword | 用户密码 |
createTime | 创建时间 |
updateTime | 修改时间 |
status | 用户状态 |
roles | 角色 |
gifts | 抢到的商品 |
我目前能想到的就是这些,你们呢?
让我们开始代码的编写吧!
1.增
我们声明一个函数,由于参数过多,我们可以使用到 ** 号来帮助我们。
- 单星号函数参数:单星号函数参数接收的参数组成一个元组。
- 双星号函数参数:双星号函数参数接收的参数组成一个字典。
我们想想,有哪些字段是必须要传入的,用户名+密码必须的,role 也是必须的,创建时间和修改时间我们可以在方法内初始化,状态也是如此,商品的话,新用户开始都是为空的。
我们是不是还可以使用正则表达式判断一下,比如密码只能为纯数字。
你还能想到什么呢?请自由发挥。
接下来我们给不需要传入的参数设置默认值。
为了方便,我们可以使用字典的方式存入用户,key 的值使用用户名。
然后编写 写入到json的方法了,这里我们可以选择将这个写入方法单独编写一个函数,因为你想啊,既然user.json用到了,那gift.json 是不是也会用到。
我们到主函数里去调用看看。
我们可以看到并未报错,这时候看看json文件。
我们可以看到成功写入进来了。
看来我们的判断也是有用的,剩下的大家自己去慢慢研究吧,由于我们的base类啊,是一个基础类,主要的功能啊,基本都不公开,所以大家测试完记得将增加方法变成一个私有的方法。
而且现在的增加是有BUG的,具体大家尝试增加多个看看。
# 新增用户
def __write_user(self, **user):
if 'userName' not in user:
raise ValueError('用户名不能为空')
if 'userPassword' not in user:
raise ValueError('密码不能为空')
if 'roles' not in user:
raise ValueError('角色不能为空')
if re.search('\d', user['userPassword']) is None:
raise ValueError('密码为纯数字')
# 设置默认值
user['createTime'] = time.time()
user['updateTime'] = time.time()
user['status'] = True
user['gifts'] = []
users = {}
users.update(
{user['userName']: user}
)
self.__save(users, self.user_json)
# 写入到json文件
def __save(self, data, path):
json_data = json.dumps(data)
with open(path, 'w') as f:
f.write(json_data)
2.查
实现了增加,但是还有些许BUG,等我们将查询写了就可以修复了。
我们看看返回的数据。
大家可以看到创建时间和修改时间返回的是毫秒数,因为我们存的就是毫秒数,但是一般用户他不看毫秒数的呀,
他要的是这种格式的时间 :"yyyy-MM-dd HH:SS:DD" 。
所以我们是不是可以转换一下,但是我们也要考虑到有些情况下,并不是所有的人都要我们转换好的时间格式,他们想要自己转换,我们是不是可以直接将毫秒数直接返回给他,我们就可以声明一个参数来做判断。
转换的代码我们写到工具类里来进行使用。
def timestamp_to_string(timestamp):
time_obj = time.localtime(timestamp)
time_str = time.strftime('%Y-%m-%d %H:%M:%S', time_obj)
return time_str
最后查询用户的方法是这样的。
# 查询用户方法
def __read_users(self, time_to_str=False):
with open(self.user_json, 'r') as f:
data = json.loads(f.read())
# 是否将时间格式化
if time_to_str == True:
for k, v in data.items():
v['createTime'] = timestamp_to_string(v['createTime'])
v['updateTime'] = timestamp_to_string(v['updateTime'])
data[k] = v
return data
我们来看看执行的效果。
上面我们说到,查询方法写好后,就可以修复目前增加方法的BUG,我们先来看看目前增加方法有什么BUG吧。
我们打个断点,用debug执行,可以看到目前的users 是空的。我们往下执行。
执行到50行的时候,我们可以看到users有数据了,但是只有这一条数据,我们将其往json文件里面添加,其实就是把原来json文件的内容覆盖了,所以永远只有一条数据。
我们来修改一下代码。
我们,用上查询方法,我们可以看到users它是有起始数据的,这样我们后面update往上追加数据,然后再添加到数据库,这样就不会只有一条数据。
这样数据就不会只有一条了,剩下的就靠大家发挥自己的奇思妙想去完善代码了。
3.改
偷懒,我就只修改用户的角色了,剩下的靠大家去自由发挥了。
我们先简单做个用户判断,然后往下就是角色的判断,我们想一下,角色是不是只有两个身份,不是管理员就是普通用户,所以我们可以定义一个常量,我们还是老样子写到common(公共模块)下的consts.py里。
定义完常量,我们要想,如果用户输入的角色不在常量里,我们是不是要抛出异常,所以我们还要定义一下异常类。
class RoleError(Exception):
def __init__(self, msg):
self.meg = msg
我们现在回到base.py 继续编写剩下的代码。
# 修改用户
def __update_user(self, userName, role, status):
# 查询所有用户
users = self.__read_users()
# 通过用户名获取要修改的用户
user = users.get(userName)
# 如果用户不存在,就直接返回
if not user:
return False
# 如果角色不存在
if role not in ROLES:
raise RoleError('角色只有admin(管理员), normal(普通用户)) %s' % role)
user['status'] = status
user['role'] = role
user['updateTime'] = time.time()
users[userName] = user
self.__save(users, self.user_json)
return True
我们到主函数里去执行一下代码看看效果。
我们可以看到目前yiqie这个用户的角色是admin,我们往下执行。
现在我们看到admin 已经变成了normal,我们再将这个字典写入json文件中,就完成了修改。
对于修改,你还有什么想法,请自由发挥吧。
4.删
删除大家直接看代码吧,没什么难的。
# 删除用户
def __delete_user(self, userName):
users = self.__read_users()
user = users.get(userName)
if not user:
return False
delete_user = users.pop(userName)
self.__save(users, self.user_json)
return delete_user
我们执行看看效果。
2.3 奖品的CRUD
首先我们要确定一下我们的奖品的结构,看图:
我们在外面设置4个大奖池,4个大奖池里还有3个小奖池,最后才是奖品。
1.查
为了不踩增加用户的坑,这次我们先把奖品的查询先写了。
# 查询奖品
def __read_gifts(self):
with open(self.gift_json) as f:
data = json.loads(f.read())
return data
2、初始化奖品结构
# 初始化数据结构
def __init_gifts(self):
data = {
'level1': {
'level1': {},
'level2': {},
'level3': {}
},
'level2': {
'level1': {},
'level2': {},
'level3': {}
},
'level3': {
'level1': {},
'level2': {},
'level3': {}
},
'level4': {
'level1': {},
'level2': {},
'level3': {}
}
}
gifts = self.__read_gifts()
# 如果有数据,就不进行初始化
if len(gifts) != 0:
return
self.__save(data, self.gift_json)
return True
既然是初始化方法,那我们不能然我们自己调用,而是让它自己去调用。
我们先看自己的json 文件现在是没有数据的。
现在我们运行一下。
初始化完成。
2.增
首先我们是确定了奖品的数据结构的,而且字段都是定死的,那我们是不是可以去定义一下常量。
接下里编写增加奖品方法。所需参数 :一级奖池,二级奖池,奖品名,数量
我们先做两个小判断,判断一级奖池和二级奖池是否是我们定义好的,如果不是我们就抛出异常,我们去定义异常。
class LevelError(Exception):
def __init__(self, msg):
self.msg = msg
然后我们通过查询方法获得奖品结构。
这是我们要想,如何才能将奖品存到最终的奖品池里呢?
# 先从一级奖池取到二级奖池
current_gift_pool = gifts[first_level]
# 再从二级奖池取到奖品池
current_second_gift_pool = current_gift_pool[sencond_level]
这样我们就获取到了最终的奖品池。
然后我们还要加个判断,用户有可能输入0个奖品,我们就给它判断如果小于等于0就赋值1;
if gift_count <= 0:
gift_count = 1
接下来我们就要判断奖品是否已经存在,如果存在我们就要原有数量基础上做数量增加就行,否则就添加奖品。
# 奖品是否已存在
if gift_name in current_second_gift_pool:
current_second_gift_pool[gift_name]['count'] = current_second_gift_pool[gift_name]['count'] + gift_count
else:
current_second_gift_pool[gift_name] = {
'name': gift_name,
'count': gift_count
}
然后我们依次将奖品放到奖池中,最后再添加到json 文件。
# 把奖品放回到二级奖池
current_gift_pool[sencond_level] = current_second_gift_pool
# 再将二级放回到一级
gifts[first_level] = current_gift_pool
self.__save(gifts, self.gift_json)
接下来我们来看看执行效果:
再执行一次
3.改
修改其实和增加是大同小异的。不过这里我们偷个懒,大家想一想,抽奖抽到了奖品,奖品数量-1,是不是相当于修改。所以我们就要判断,如果是管理员,那就是修改奖品数量,如果是用户,那就奖品数量-1。
def __gift_update(self, userName, first_level, second_level,
gift_name, gift_count=1):
接下来跟增加方法一样先做判断。
if first_level not in FIRSTLEVELS:
raise LevelError('该一级奖池不存在 %s ' % first_level)
if second_level not in SECONDLEVELS:
raise LevelError('该二级奖池不存在 %s ' % second_level)
gifts = self.__read_gifts()
哎,这不是跟增加方法差不多嘛,那我们是不是可以将它们抽出来,作为一个公共函数,当然还有其它的地方也可以抽到公共函数里,我们直接看代码。
# 判断奖品并获得
def __check_and_getgift(self, first_level, second_level, gift_name):
if first_level not in FIRSTLEVELS:
raise LevelError('该一级奖池不存在 %s ' % first_level)
if second_level not in SECONDLEVELS:
raise LevelError('该二级奖池不存在 %s ' % second_level)
gifts = self.__read_gifts()
# 获取一级奖池
level_one = gifts[first_level]
# 获取二级奖池
level_two = level_one[second_level]
# 如果奖品不在奖池内直接返回False
if gift_name not in level_two:
return False
return {
'level_one': level_one,
'level_two': level_two,
'gifts': gifts
}
然后在修改方法里调用。
def __gift_update(self, userName, first_level, second_level,
gift_name, gift_count=1):
assert isinstance(gift_count, int), '数量是整数类型'
data = self.__check_and_getgift(first_level, second_level, gift_name)
if data == False:
return data
细心的大家就会发现,我在开头还用了一下断言。
往下,我们获取data 里的数值。
# 获取一级奖池
current_gift_pool = data.get('level_one')
# 获取二级奖池
current_second_gift_pool = data.get('level_two')
# 获取奖品
current_gift = current_second_gift_pool[gift_name]
# 获取整个奖池
gifts = data.get('gifts')
接下里我们就该判断是不是管理员啦。
# 判断角色
if roles == 'admin':
if gift_count <= 0:
raise ValueError('礼物数不能为0')
current_gift['count'] = gift_count
else:
if current_gift['count'] - gift_count < 0:
raise NegativeNumberError('没有奖品啦!')
current_gift['count'] -= gift_count
# 奖品加入到用户里
user['gifts'].append(gift_name)
users[user.get("userName")] = user
self.__save(users, self.user_json)
接下来还是像添加一样,依次赋值。
# 奖品放到二级奖池
current_second_gift_pool[gift_name] = current_gift
# 二级奖池放到一级奖池
current_gift_pool[second_level] = current_second_gift_pool
# 一级奖池加到整个奖品结构中
gifts[first_level] = current_gift_pool
# 添加到JSON文件中
self.__save(gifts, self.gift_json)
接下来我们来测试一下。
用户json:
奖品json:
我们来执行一下修改代码,先是用户角色。
我们可以发现,数量都已改变。
我们换到管理员身份。
数量也修改成功,还是一样,如果你还有其他的奇思妙想,请自由发挥。
4.删
删除就非常简单了,大家直接看代码吧。
def __delete_gift(self, first_level, second_level,
gift_name):
data = self.__check_and_getgift(first_level, second_level, gift_name);
# 删除奖品
def __delete_gift(self, first_level, second_level,
gift_name):
data = self.__check_and_getgift(first_level, second_level, gift_name);
if data == False:
return data
current_gift_pool = data.get('level_one')
current_second_gift_pool = data.get('level_two')
gifts = data.get('gifts')
# 移除奖品
delete_gift_data = current_second_gift_pool.pop(gift_name)
# 二级放一级
current_gift_pool[second_level] = current_second_gift_pool
# 一级放总体
gifts[first_level] = current_gift_pool
self.__save(gifts, self.gift_json)
return delete_gift_data
base.py 到这里就完成了!
3、 编写项目管理员类--admin.py
我们来定义一个管理员的admin类,它需要继承base类使用它里面编写好的底层方法。
接下来还是我们的初始化的流程。
def __init__(self, username, user_json, gift_json):
self.userName = userName
super().__init__(user_json, gift_json)
请问,代码中的super(),它的作用是什么呢?
在开发中,如果既要使用父类的方法,在不改变父类方法的情况下又想增加功能, 就可以使⽤扩展的⽅式 。
在需要的位置使⽤ super().⽗类⽅法来调⽤⽗类⽅法的执⾏ 。不太记得同学进入传送门复习:
Python面向对象的三大特性【封装、继承、多态】_一切总会归于平淡的博客-CSDN博客
3.1 用户信息的CRUD
在这里,我们实现base.py 的底层方法,在完成管理员对用户的CRUD。
1.查
这里的查询就相当于登录,也可以用作于权限判断。
所以我们要做好判断,比如用户是否存在,用户是否被封禁,身份是否是管理员。
我们直接上代码。
def get_user(self):
# 调用base的查询方法
users = self._Base__read_users()
current_user = users.get(self.userName)
if current_user == None:
raise ValueError('该用户 %s 不存在' % self.userName)
if current_user.get('status') == False:
raise ValueError('该用户 %s 已被封禁' % self.userName)
if current_user.get('roles') != 'admin':
raise ValueError('不是管理员')
self.user = current_user
self.role = current_user.get('roles')
self.name = current_user.get('userName')
self.status = current_user.get('status')
代码最后我们还将值赋给self,方便使用,然后放放到初始化方法 init 里 ,这样就可以执行自动调用了。
我们到主函数里去执行,看看效果。
完成!
2.增
由于底层都交给base写了,admin 只需要调用即可,所以代码非常简单,但是我们还是做一下基础的业务判断,比如是否是管理员。
def add_user(self, userName, userPassword, roles):
if self.role != 'admin':
raise Exception('不是管理员')
self._Base__write_user(userName=userName, userPassword=userPassword, roles=roles)
我们执行看看效果:
增加成功,如果你其他的想法,请自由发挥。
2.改
修改也是非常简单的,但是你想,如果增加都要判断是否是管理员,那修改,删除也都要判断,既然是公共的,我就可以将其抽取出来,做成一个公共函数。
def __check(self, message):
self.get_user()
if self.role != 'admin':
raise Exception(message)
这样我们就不需要每个方法都写一遍了,而是直接调用。
def update_user(self, userName, roles, status=True):
self.__check('不是管理员')
self._Base__update_user(userName=userName, role=roles, status=status)
执行交给大家直接去试了。
4.删
删除也是非常的简单,调用代码,传入参数。
def delte_user(self, userName):
self.__check('不是管理员')
self._Base__delete_user(userName=userName)
测试效果,大家自己去试吧。
3.2 奖品信息的CRUD
1.增
def add_gift(self, first_level, sencond_level, gift_name, gift_count):
self.__check("不是管理员")
self._Base__write_gift(first_level=first_level, sencond_level=sencond_level,
gift_name=gift_name, gift_count=gift_count)
2.查
def read_gift(self):
self.__check("不是管理员")
gift = self._Base__read_gifts()
return gift
3.改
def update_gift(self, first_level, sencond_level,
gift_name, gift_count=1):
self.__check("不是管理员")
self._Base__gift_update(userName=self.userName, first_level=first_level, second_level=sencond_level,gift_name=gift_name, gift_count=gift_count)
4.删
def delete_gift(self, first_level, sencond_level, gift_name, ):
self.__check("不是管理员")
self._Base__delete_gift(first_level, sencond_level, gift_name)
是不是很简单。
4、编写项目用户类 -- user.py
用户这里我们只做一件事那就是抽奖!
首先开始跟管理员类差不多,可以说是一摸一样!
class User(Base):
def __init__(self, userName, user_json, gift_json):
self.userName = userName
super().__init__(user_json, gift_json)
self.get_user()
def get_user(self):
# 调用base的查询方法
users = self._Base__read_users()
current_user = users.get(self.userName)
if current_user == None:
raise ValueError('该用户 %s 不存在' % self.userName)
if current_user.get('status') == False:
raise ValueError('该用户 %s 已被封禁' % self.userName)
if current_user.get('roles') != 'normal':
raise ValueError('管理员要有管理员的样子')
self.user = current_user
self.role = current_user.get('roles')
self.name = current_user.get('userName')
self.status = current_user.get('status')
4.1 抽奖!
首先我们知道我们的一级奖池是4个级别,二级奖池是3个级别。
所以我们可以通过随机数随机到的数字区间来决定大家有多大的运气抽中奖品。
我们可以生成一个数字列表,然后通过random来帮我们获取。
第一层 :1-50 : 50%; 51 -80 :30%;81 - 95 :15% ;95 - 100 :5%。第二层 :1-80 : 80%; 81 - 95 :15%;95 - 100 :5%。
随机到某个数字,我们就获取到最终的奖池,我们就可以抽到奖品了,最后我们要判断一下奖池里有没有奖品,如果没有,不好意思,如果有,我们还要随机一下,你抽到什么奖品,如果你抽到的奖品数量为0,不好意思,下次一定。
了解大概思路,我们开始实现代码。
首先我们要初始化一个1-100的数字列表。
接下来编写抽奖方法。
还是老样子,我们先获取一级奖池。
def choice_gift(self):
# 一级奖池 二级奖池
first_level, second_level = None, None
# 随机数
level_one_count = random.choice(self.gift_random)
# 概率
if 1 <= level_one_count <= 50:
first_level = 'level1'
elif 51 <= level_one_count <= 80:
first_level = 'level2'
elif 81 <= level_one_count < 95:
first_level = 'level3'
elif level_one_count >= 95:
first_level = 'level4'
else:
raise CountError('level_one_count need 0~100')
# 查询出所有奖池
gifts = self._Base__read_gifts()
# 得到一级奖池
level_one = gifts.get(first_level)
现在获取二级奖池
# 开始获取二级奖池
level_two_count = random.choice(self.gift_random)
# 概率
if 1 <= level_two_count <= 80:
second_level = 'level1'
elif 81 <= level_two_count < 95:
second_level = 'level2'
elif 95 <= level_two_count < 100:
second_level = 'level3'
else:
raise CountError('level_two_count need 0~100')
获取到二级奖池后,我们就可以判断奖池里是否有奖品了,如果没有,不好意思,你有点黑。
level_two = level_one.get(second_level)
if len(level_two) == 0:
print('不好意思,你个小黑子')
return
但是,如果你中了,我们就要判断一下你抽中了什么,还要判断数量是否为0;
# 随机列表下标获得奖品
gift_name = random.choice(gift_names)
# 获取奖品
gift_info = level_two.get(gift_name)
# 判断奖品是否还有
if gift_info.get('count') <= 0:
print('哎呦,你干嘛~~~~,这都不中~~~')
return
最后,你来到这里,恭喜你,中奖了!
我们直接调用修改方法。
self._Base__gift_update(userName=self.userName, first_level=first_level, second_level=second_level,gift_name=gift_info.get('name'))
print('你居然中了 %s ?蹭蹭欧气!' % gift_name)
接下来我们测试一下。
这是现在奖品的数据。
我们现在调用抽奖的方法。
现在去查看一下数据。
5、结语(附Demo地址)
到这里,抽奖系统在我这里就完成了,但是在你那里或许还没有,我相信你还有很多奇思妙想去改造这个抽奖系统,把它完善得更好。期待你的表现!
最后别忘了,点赞+关注!我是一起总会归于平淡,我们下篇博客再见!
代码地址放下面了,有需要的自取。