本次项目做的是一个前后端分离的项目,大厂的项目流程如下图所示
产品需求文档,各个页面的功能需求以及展示都在其中
1. 主页
1.1 最多5个房屋logo图片展示,点击可跳转至房屋详情页面
1.2 提供登陆/注册入口,登陆后显示用户名,点击可跳转至个人中心
1.3 用户可以选择城区、入住时间、离开时间等条件进行搜索
1.4 城区的区域信息需动态加载
2. 注册
2.1 用户账号默认为手机号
2.2 图片验证码正确后才能发送短信验证码
2.3 短信验证码每60秒可发送一次
2.4 每个条件出错时有相应错误提示
3. 登陆
3.1 用手机号与密码登陆
3.2 错误时有相应提示
4. 房屋列表页
4.1 可根据入住离开时间、区域进行筛选,并可进行排序
4.2 房屋信息分页加载
4.3 区域信息动态加载
4.4 筛选条件更新后,页面立即刷新
5. 房屋详情页
5.1 需展示的详细信息参考设计图
5.2 提供预定入口
5.3 若是房东本人查看房屋信息时,预定入口不显示
6. 房屋预定
6.1 由用户确定入住时间
6.2 根据用户确定的入住离开时间实时显示合计天数与总金额
7. 我的爱家
7.1 显示个人头像、手机号、用户名(用户名未设置时为用户手机号)
7.2 提供修改个人信息的入口
7.3 提供作为房客下单的查询入口
7.4 提供成为房东所需实名认证的入口
7.5 提供作为房东发布房屋信息的入口
7.6 提供作为房东查询客户订单的入口
7.7 提供退出的入口
8. 个人信息修改
8.1 可以修改个人头像
8.2 可以修改用户名
8.3 登陆手机号不能修改
8.4 上传头像与用户名分开保存
8.5 上传新头像后页面理解显示新头像
9. 我的订单(房客)
9.1 按时间倒序显示订单信息
9.2 订单完成后提供评价功能
9.3 已评价的订单能看到评价信息
9.4 被拒绝的订单能看到拒单原因
10. 实名认证
10.1 实名认证只可进行一次
10.2 提交认证信息后再次进入只能查看信息,不能修改
10.3 认证信息包含姓名与身份证号
11. 我的房源
11.1 未实名认证的用户不能发布新房源信息,需引导到实名认证页面
11.2 按时间倒序显示已经发布的房屋信息
11.3 点击房屋可以进入详情页面
11.4 对实名认证的用户提供发布新房屋的入口
12. 发布新房源
12.1 需要用户填写全部房屋信息
12.2 房屋的文字信息与图片分开操作
13. 客户订单(房东)
13.1 按时间倒序显示用户下的订单
13.2 对于新订单提供接单与拒单的功能
13.3 拒单必须填写拒单原因
13.4 若客户进行了订单评价,需显示
14. 退出
14.1 提供退出功能
为了方便项目管理及多人协同开发,我们根据需求将功能划分为不同的模块。将来在项目中,每个模块都会对应一个子应用进行管理和解耦
①前后端不分离:为了提高SEO(搜索引擎排名),特别是首页,详情页和列表页。前端页面直接从服务器返回,通过render_templates(),将前端和后端写在一起
页面需要局部刷新,会选择使用AJAX来实现。
②前后端分离:服务器只提供数据,页面写在nginx中,nginx提供接口给后端,用户访问的时候先去nginx找页面,服务器通过用户的操作给他返回数据
前后端的好处:减少服务器的压力、服务器只提供数据,前端的束缚变少了,除了html在app上面也可以展示,它不利于SEO的优化
①创建一个空的文件夹用来做项目,配置好虚拟环境,安装flask、Flask-Session 、Flask-SQLAlchemy、Flask-WTF、redis 等第三方库,项目需要什么第三方库就导入什么
②用工厂模式导入配置文件(只有加载app时候会用到配置文件),常见蓝图api_1_0将以后的视图写在这里,别忘了注册进主入口,utils:自已写的工具自己定义的,static:静态资源文件,libs:第三方的工具,比如发送短信,logs:日志文件。设置好目录的层级关系,将跟业务逻辑有关系的放在一个文件夹里面,跟业务逻辑无关的放在一个文件夹里面
将蓝图、第三方的工具、自定义的工具放到跟业务逻辑有关的inside_frame中
运行manage文件报错,解决方法如下
注意避免定义相同的视图函数名称引起的报错,以及循环导入的错误,为了引用redis服务将它定义在外面并且设置全局变量
因为没有nginx服务器,所以将静态资源文件导入,通过蓝图的方式去引用前端的界面以及框架,直接访问静态资源文件可以直接打开界面,但是通过项目打开要去掉每个文件前的..虽然可以通过http://127.0.0.1:5000/static/html/index.html直接访问到页面,但是这样直接暴露文件的位置并不安全,为了达到用户只输入index.html就可以访问,需要做一定的处理,路由转换,将模型映射到数据库中
以上所有就是项目初期的搭建,功能从以下章节开始讲解
csrf(Cross-site request forgery)跨站请求伪造与同源策略,不同源的网站不可以共享资源
通过访问静态页面的蓝图添加cookie,用session添加实现csrf验证,为了解决图形验证码的问题要引入工具添加Pillow库,将图形验证码保存在redis中类型为string好设置过期时间,第一是快第二是不占用数据库资源,为了给区分好是哪个用户可以使用uuid来做唯一标识
接口文档的定义
# 接口名字 : 获取图片验证码
# 描述 :前端访问,可以获取到图片验证码
# URL :api/v1.0/image_codes/
# 请求方式 :GET
# 传入参数
名字 类型 是否必须 说明
image_code_id 字符串 是 验证码的编号
# 返回值
名字 类型 是否必须 说明
errno 字符串 否 错误代码
errmsg 字符串 否 错误内容
①保存短信验证码是为注册做准备的。
为了避免用户使用图形验证码恶意测试,后端提取了图形验证码后,立即删除图形验证码。即一个短信验证码只能用一次,当调用发送短信接口的时候就删除redis中的短信验证码
②Flask不具备发送短信的功能,所以我们借助第三方的容联云通讯短信平台来帮助我们发送短信验证码。
第一次注册会送8块钱,发一次短信要4分钱,不用实名官网如下https://www.yuntongxun.com/
开发文档如下
https://doc.yuntongxun.com/p/5f029ae7a80948a1006e776e
需要pip install ronglian_sms_sdk,测试代码如下,详细使用可以参考上述的开发文档
from ronglian_sms_sdk import SmsSDK
accId = '容联云通讯分配的主账号ID'
accToken = '容联云通讯分配的主账号TOKEN'
appId = '容联云通讯分配的应用ID'
def send_message():
sdk = SmsSDK(accId, accToken, appId)
tid = '容联云通讯创建的模板ID'
mobile = '手机号1,手机号2'
datas = ('变量1', '变量2')
resp = sdk.sendMessage(tid, mobile, datas)
print(resp)
为了避免频繁发送可以通过管道设置一个时间,在60s内如果是同一手机号,则返回频繁访问,定义注册接口将密码以密文的形式存在,2012年csdn就以明文密码为数据库信息就别攻破了,将为密码加密的脚本写成装饰器
发送短信验证码的代码是自上而下同步执行的。发送短信是耗时的操作。如果短信被阻塞住,用户响应将会延迟。响应延迟会造成前端用户界面的倒计时延迟。为了避免这个问题,一点击就倒计时而不是点击之后获得短信验证码了之后才倒计时,异步发送短信,将发送短信和响应分开执行,将发送短信从主业务中解耦出来。
@api.route('/sms_codes/' )
def get_sms_code(mobile):
'''获取短信验证码'''
# 获取图片验证码参数
image_code = request.args.get('image_code') # 图片验证码
image_code_id = request.args.get('image_code_id') # uuid
# 校验图片验证码参数
if not all([image_code, image_code_id]):
return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')
# 业务逻辑,从redis取出图片验证码
try:
real_image_code = redis_store.get('image_code_%s' % image_code_id)
except Exception as e:
logging.error(e)
return jsonify(errno=RET.DBERR, errmsg='redis数据库异常')
# 判断图片验证码是否过期
if real_image_code is None:
return jsonify(errno=RET.NODATA, errmsg='图片验证码失效')
# 没有过期删除redis中的图片验证码
try:
redis_store.delete('image_code_%s' % image_code_id)
except Exception as e:
logging.error(e)
# print(real_image_code) b'TKNE'
# 与用户填写的图片验证码做对比
real_image_code = real_image_code.decode()
if real_image_code.lower() != image_code.lower():
return jsonify(errno=RET.DATAERR, errmsg='图片验证码错误')
# 判断手机号是否是重复发送的代码,优化
try:
send_flag = redis_store.get('send_sms_code%s' % mobile)
except Exception as e:
logging.error(e)
else:
if send_flag is not None:
return jsonify(errno=RET.REQERR, errmsg='请求过于频繁')
# 判断手机号是否存在
try:
user = User.query.filter_by(mobile=mobile).first()
except Exception as e:
logging.error(e)
else:
if user is not None:
# 表示手机号已经被注册了,已经存在在数据库当中了
return jsonify(errno=RET.DATAEXIST, errmsg='手机号已经存在')
# 生成短信验证码
sms_code = '%06d' % random.randint(0, 999999)
# 保存真实的短息验证码到redis中
try:
pl = redis_store.pipeline()
# redis_store.setex('sms_code_%s' % mobile, constants.SMS_CODE_REDIS_WXPIRES, sms_code)
pl.setex('sms_code_%s' % mobile, constants.SMS_CODE_REDIS_WXPIRES, sms_code)
# 保存发送给这个手机号的记录
# redis_store.setex('send_sms_code%s' % mobile, constants.SEND_SMS_CODE_EXPIRES, 1)
pl.setex('send_sms_code%s' % mobile, constants.SEND_SMS_CODE_EXPIRES, 1)
pl.execute()
except Exception as e:
logging.error(e)
return jsonify(errno=RET.DATAERR, errmsg='保存短信验证码异常')
# 发短信,返回值
try:
ccp = CCP()
result = ccp.send_message(mobile, (sms_code, int(constants.SMS_CODE_REDIS_WXPIRES / 60)))
except Exception as e:
logging.error(e)
return jsonify(errno=RET.THIRDERR, errmsg='发送异常')
if result == 0:
return jsonify(errno=RET.OK, errmsg='发送成功')
else:
return jsonify(errno=RET.THIRDERR, errmsg='发送失败')
设计模式
使用celery一定要开启celery服务celery -A home.tasks.task_sms worker -l info
如果报错就在后面加上啊-P eventlet
①-A指对应的应用程序, 其参数是项目中 Celery实例的位置。
②worker指这里要启动的worker。
③-l指日志等级,比如info等级。
通过限制ip登录次数防止恶意撞库,将ip登录的错误次数以及过期时间保存在redis中,在模型中定义验证密码的方式,将密码以密文的状态存入数据库,清空session数据退出登录状态
①只有登录过了的人才可以查看个人中心,可以通过钩子函数中的before_requests来实现,但是当使用了钩子函数,他就会针对所有的注册蓝图文件生效,但对于主页等非登录用户也可以查看,所以直接在utils中定义一个装饰器用来检验用户是否登录,要引用functools.wrapp(view_func)不改变取装饰的原函数的属性,可以通过
__ name __ 或者__doc__验证不使用它则会改变被装饰的属性
②将用户头像保存到七牛云,数据库中只保存图片的hashkey值,为了不占用数据库资源,不保存完整链接,当需要的时候再取
-保存到程序本地,磁盘满了要扩容
-文件存储第三方解决方案(七牛云)
-自己搭建文件存储系统(FastDFS 快速分布式文件存储系统(电商)、HDFS hadoop分布式文件系统)
使用七牛云,他会读取数据二进制文件,只有当图片重复不会上传,与文件名无关,没有做文件限制,如果是其他文件,复制外链地址,点击就会下载文件
①城区的定义,从MySQL中取出来存入redis中,区域数据是我们动态查询的结果。但是区域数据不是频繁变化的数据,所以没有必要每次都重新查询。所以我们可以选择对区域数据进行缓存处理。
②前端模板JS模板-ART-TEMPLATE,为了避免循环占用内存,其实不用这个模板也可以显示城区,只不过后面的显示逻辑都会用到这个逻辑
newhouse.html
<script type="text/html" id="areas-tmpl">
{{ each areas as area }}
<option value={{ area.aid }}>{{ area.aname }}</option>
{{ /each }}
</script>
newhouse.js
// 使用js模板
var html = template("areas-tmpl", {areas: areas})
$("#area-id").html(html);
如果前端报错是500就要去看后端的报错,看看是否返回了正常的response,经常查询的数据要放入redis中,缓解MySQL的压力,逻辑是先查缓存,缓存没有再查MySQL,查出之后存入缓存,下次查询的时候就会优先查缓存了
①当点击首页的轮播图之后可以查看到房屋的详细信息,为了防止房东自己刷单写好评,需要做session验证,详细的配置信息通过前端的indexof方法显示,如果有数据则返回所在索引,如果没有则返回-1,详细信息需要储存在redis中,减缓数据库压力
②搜索房屋功能,接受参数的时候需要对每个参数做验证,通过断言验证日期,断言一旦后面的判断为false时就会抛出异常通过try except可以捕获到异常
③房屋的搜索,如果是通过时间来筛选的话,因为模型表中只有订单表中有时间,所以可以通过逆向思维先根据时间来查出订单中的房屋id再再房屋信息表中筛选出可以定的房间
处理搜索条件的时候,通过解包操作将所有的条件叠加查询,如果没有就为空,不影响查询的结果
订单分为客户订单和房东订单两个接口,分别实现不同功能,以及定义一个接受评论的路由,如果拒单就直接将理由保存为评论
对接第三方平台支付宝,百度支付宝,点击我是开发者用自己的支付宝扫码进入中心,创建一个沙箱应用用来做测试(他为你提供了一个卖家和买家)
通过openssl创建本地的公钥和私钥,再将支付宝的公钥导入到本地
流程如下
上述都是准备工作,之后就根据开发者文档准备代码调试,支付宝平台没有提供pythonsdk文档GitHub有
Python支付宝SDK:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
完整代码已推送至GitHub,地址为https://github.com/wchao403/flask-project.git,如果对你有帮助期待点赞加收藏