说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!
接着上一篇博客继续往下写 :https://blog.csdn.net/qq_41782425/article/details/86302733
目录
一丶区县信息后端编写
1.定义视图函数
@api.route("/areas")def get_area_info(): """ 获取区县信息 :return: """ pass
2.逻辑编写
- step1 从数据库中获取所有区县信息
try: # 从数据库中获取所有区县信息 area_li = Area.query.all()except Exception as e: current_app.logger.error(e) return jsonify(errno=RET.DBERR, errmsg="数据库异常")
- step2 在Area类中构建一个to_dict方法,将对象转换成字典,因为我们给前端传输数据是需要传输json格式数据的
def to_dict(self): """将对象转换为字典""" data = { "aid": self.id, "aname": self.name } return data
- step3 遍历对象列表,调用每个对象中的to_dict方法,将返回的值添加到area_dict_li列表中
# 将对象转换为字典area_dict_li = []for area in area_li: area_dict_li.append(area.to_dict())
- step4 返回正确响应数据
return jsonify(errno=RET.OK, errmsg="OK", data=area_dict_li)
3.测试接口
- step1 运行项目,打开Postman测试工具,向127.0.0.1:5000/api/v1.0/areas发送get请求
- step2 返回接口正确响应数据,因为数据库Area表没有数据,所以返回的响应数据为空
4.向数据库中ih_area_info以及ih_facility_info两张表插入测试数据
- step1 执行如下SQL语句
INSERT INTO `ih_area_info`(`name`) VALUES ('锦江区'),('青羊区'),('金牛区'),('武侯区'),('成华区'),('龙泉驿区'),('青白江区'),('新都区'),('温江区'),('郫都区'),('双流区'),('高新区'),('天府新区'),('新津县'),('大邑县'),('金堂县');INSERT INTO `ih_facility_info`(`name`) VALUES('无线网络'),('热水淋浴'),('空调'),('暖气'),('允许吸烟'),('饮水设备'),('牙具'),('香皂'),('拖鞋'),('手纸'),('毛巾'),('沐浴露、洗发露'),('冰箱'),('洗衣机'),('电梯'),('允许做饭'),('允许带宠物'),('允许聚会'),('门禁系统'),('停车位'),('有线网络'),('电视'),('浴缸');
ih_area_info表
ih_facility_info表
- step2 再回到Postman中Send发送请求,显示出正确响应数据
二丶为区县信息补充缓存机制
1.为什么要补充缓存机制
- step1 在网站首页,需要频繁获取区域数据
- step2 在我的爱家中,发布房源也需要频繁获取区域数据
- step3 以及在房源列表,搜索栏也要频繁获取区域数据
2.分析,因为区县一般都是固定的,不会经常变化,搜索页面和主页是用户经常访问的地方,那么即我们后端接口以及数据库就会被大量访问,而且是在很短时间内,这样可能会出现异常问题,导致访问速度缓慢,所以完美解决方法就是使用缓存
3.具体实现流程
- step1 当用户访问区县信息时,就会调用后端我们写的get_area_info接口,之前写的代码是,每次访问都会从数据库中拿去数据,现在将这一块改变一下,先从redis数据中拿去数据库(这里选择redis数据库,因为是内存级的数据库)
- step2 如果redis数据库中有区域数据,直接返回给前端
- step3 当redis数据库中没有区域数据时,此时再去mysql数据库中进行数据获取,拿到的区域数据不会马上返回给前端,而是将拿到的数据保存到redis数据库中,最后才返回给前端
4.逻辑代码编写
思考:将数据保存到redis数据库中,此时需要思考,以怎么形式保存数据,保存哪些数据到redis数据库
实现:是将整个返回给前端正确响应数据全部以json格式的字符串保存
- step1 将数据转换为json字符串
resp_dict = dict(errno=RET.OK, errmsg="OK", data=area_dict_li)resp_json_str = json.dumps(resp_dict)
- step2 将构建的json格式的字符串响应数据保存到redis数据库中, 并设置有效期
try: redis_store.setex("area_info",constants.AREA_INFO_REDIS_CACHE_EXPIRES, resp_json_str)except Exception as e: current_app.logger.error(e)
- step3 返回构造响应头数据,设置Content-Type为json,默认的为html
return resp_json_str, 200, {"Content-Type":"application/json"}
- step4 调用接口一开始从redis数据库中拿去区域数据
try: resp_json_str = redis_store.get("area_info")except Exception as e: current_app.logger.error(e)else: if resp_json_str is not None: return resp_json_str, 200, {"Content-Type": "application/json"}
- step5 当redis数据库中获取的数据不为空时,往日志info中记录一句话,方便测试是从mysql 还是 redis 获取的区域数据
current_app.logger.info("Area data from redis")
5.测试redis缓存是否成功
- step1 运行项目
- step2 在Postman测试工具中向后端接口
第一次Send发送请求,查看程序运行日志,并没有显示我们定义日志信息
第二次Send发送请求,则显示我们定义的info日志信息,说明区域数据是从redis数据库中获取的
- step3 查看redis数据库,并获取area_info键的值
三丶缓存数据同步问题
问:怎么保证redis缓存的数据和mysql数据保持一致性?
第一种方式:mysql数据库维护人员在修改区域表数据时,将redis数据库key删除
第二种方式:就是我们这种简单暴力有效的方式,设置数据的有效期,让redis数据库来帮我们把数据进行删除
四丶完善后端用户模块
1.对profile模块中的update_name进行修改
- step1 修改路由请求方式
@api.route("/users/name", methods=["PUT"])
- step2 从g对象中获取用户id
user_id = g.user_id
- step3 获取前端请求中的参数
req_dict = request.get_json()
- step4 判断参数是否为空
if not req_dict: return jsonify(errno=RET.PARAMERR, errmsg="参数不完整")
- step5 获取用户修改的名字
name = req_dict.get("name")
- step6 判断用户是否输入用户名
if name is None: return jsonify(errno=RET.PARAMERR, errmsg="名字不能为空")
- step7 将用户修改后的名字保存到数据库,需要注意的是这里不需要判断名字name是否重复,因为当初在创建数据库表字段name的时候设置了唯一索引
try: User.query.filter_by(id=user_id).update({"name": name}) db.session.commit()except Exception as e: db.session.rollback() current_app.logger.error(e) return jsonify(errno=RET.DBERR, errmsg="保存用户名字失败")
- step8 因为在登录和注册接口编写时候最后都将name保存到session中,所以当用户修改name后,也需要将新的name进行session保存
session["name"] = name
- step9 最后返回正确响应
return jsonify(errno=RET.OK, errmsg="修改成功", data={"name": name})
2.获取个人信息在我的爱家中进行显示
- step1 定义视图函数
@api.route("/user", methods=["GET"])@login_requireddef get_user_profile():
- step2 获取用户id
user_id = g.user_id
- step3 根据用户id在数据库中查询个人信息
try: user = User.query.get(user_id)except Exception as e: current_app.logger.error(e) return jsonify(errno=RET.DBERR, errmsg="获取用户信息失败")
- step4 判断用户对象是否为空
if User is None: return jsonify(errno=RET.NODATA, errmsg="无效操作")
- step5 在models模块中构建一个to_dict方法,用于构建我们返回给前端的数据
def to_dict(self): """将对象转换为字典数据""" user_dict = { "user_id": self.id, "name": self.name, "mobile": self.mobile, "avatar": constants.QINIU_URL_DOMAIN + self.avatar_url if self.avatar_url else "", "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S") } return user_dict
- step6 回到profile模块中,返回正确响应数据给前端
return jsonify(errno=RET.OK, errmsg="OK", data=user.to_dict())
3.获取用户的实名认证信息
- step1 定义视图函数
@api.route("/users/auth", methods=["GET"])@login_requireddef get_user_auth():
- step2 获取用户id
user_id = g.user_id
- step3 根据用户id在数据库中进行查询, 获取用户对象
try: user = User.query.get(user_id)except Exception as e: current_app.logger.error(e) return jsonify(errno=RET.DBERR, errmsg="获取用户实名认证信息失败")
- step4 判断用户对象是否为空
if user is None: return jsonify(errno=RET.NODATA, errmsg="无效操作")
- step5 在models模块中构建一个auth_to_dict方法,用于构建我们返回给前端的数据
def auth_to_dict(self): """将用户实名信息转换为字典数据""" auth_dict = { "user_id": self.id, "real_name": self.real_name, "id_card": self.id_card } return auth_dict
- step6 回到profile模块中,返回正确响应数据给前端
return jsonify(errno=RET.OK, errmsg="OK", data=user.auth_to_dict())
4.向数据库中保存用户实名认证信息
- step1 定义视图函数
@api.route("/users/auth", methods=["POST"])@login_requireddef set_user_auth():
- step2 获取用户id
user_id = g.user_id
- step3 获取前端发送请求中的参数
req_data = request.get_json()
- step4 判断参数是否为空
if not req_data: return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
- step5 获取用户输入的真实姓名和身份证号
real_name = req_data.get("real_name")id_card = req_data.get("id_card")
- step6 校验参数完整性
if not all([real_name,id_card]): return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
- step7 将用户填写的真实姓名和身份证号保存到数据库中,这里在数据库进行数据更新是为什么将real_name和id_card默认为空,因为在后端来说,用户只能设置一次实名认证,当real_name和id_card为None时,才代用户是第一次进行认证,当获取的real_name和id_card不为空时,说明用户之前已经认证过了
try: User.query.filter_by(id=user_id, real_name=None, id_card=None).update({"real_name":real_name, "id_card":id_card}) db.session.commit()except Exception as e: current_app.logger.error(e) db.session.rollback() return jsonify(errno=RET.DBERR, errmsg="保存用户实名信息失败")
- step8 向前端返回正确响应
return jsonify(errno=RET.OK, errmsg="OK")
五丶完善前端用户模块
1.在profile.html文件中进行如下修改
<form id="form-name" action="/api/profile/name" method="post">
2.在profile.js中进行如下编写
- step1 在个人信息栏显示用户信息
// 在页面加载是向后端查询用户的信息 $.get("/api/v1.0/user", function(resp){ // 当后端接口检验登录装饰器返回4101状态码,表示用户未登录,即跳转到登录页面 if (resp.errno == "4101") { location.href = "/login.html"; } // 后端返回正确响应码,将响应数据中的name填写到id = user-name的标签内容上 else if (resp.errno == "0") { $("#user-name").val(resp.data.name); // 头像链接存在则显示到src下 if (resp.data.avatar) { $("#user-avatar").attr("src", resp.data.avatar); } } }, "json");
- step2 将用户修改后的名字保存到数据库中
// 将用户修改后名字向后端进行发送 $("#form-name").submit(function(e){ e.preventDefault(); // 获取用户输入的名字 var name = $("#user-name").val(); if (!name) { alert("请填写用户名!"); return; } $.ajax({ url:"/api/v1.0/users/name", type:"PUT", data: JSON.stringify({name: name}), contentType: "application/json", dataType: "json", headers:{ "X-CSRFTOKEN":getCookie("csrf_token") }, success: function (data) { if (data.errno == "0") { $(".error-msg").hide(); showSuccessMsg(); } else if (data.errno == "4001") { $(".error-msg").show(); } else if (data.errno == "4101") { location.href = "/login.html"; } } }); })
3.在我的爱家my.js中进行如下编写
$(document).ready(function(){ $.get("/api/v1.0/user", function(resp){ // 用户未登录 if (resp.errno == "4101") { location.href = "/login.html"; } // 查询到了用户的信息 else if (resp.errno == "0") { $("#user-name").html(resp.data.name); $("#user-mobile").html(resp.data.mobile); if (resp.data.avatar) { $("#user-avatar").attr("src", resp.data.avatar); } } }, "json");});
4.在实名认证auth.js中进行如下编写
- step1 当页面加载完毕时,想后端接口发送请求来获取用户的姓名和身份证
$(document).ready(function(){ // 查询用户的实名认证信息 $.get("/api/v1.0/users/auth", function(resp){ // 4101代表用户未登录 if (resp.errno == "4101") { location.href = "/login.html"; } else if (resp.errno == "0") { // 如果返回的数据中real_name与id_card不为null,表示用户有填写实名信息 if (resp.data.real_name && resp.data.id_card) { $("#real-name").val(resp.data.real_name); $("#id-card").val(resp.data.id_card); // 给input添加disabled属性,禁止用户修改 $("#real-name").prop("disabled", true); $("#id-card").prop("disabled", true); // 隐藏提交保存按钮 $("#form-auth>input[type=submit]").hide(); } } else { alert(resp.errmsg); } }, "json");
- step2 当页面加载完毕时,如果用户已经进行了实名认证,那么就不显示保存按钮以及对输入框进行禁止修改
// 管理实名信息表单的提交行为 $("#form-auth").submit(function(e){ e.preventDefault(); // 如果用户没有填写完整,展示错误信息 var realName = $("#real-name").val(); var idCard = $("#id-card").val(); if (realName == "" || idCard == "") { $(".error-msg").show(); } // 将表单的数据转换为json字符串 var data = { real_name: realName, id_card: idCard }; var jsonData = JSON.stringify(data); // 向后端发送请求 $.ajax({ url:"/api/v1.0/users/auth", type:"post", data: jsonData, contentType: "application/json", dataType: "json", headers: { "X-CSRFTOKEN": getCookie("csrf_token") }, success: function (resp) { if (resp.errno == "0") { $(".error-msg").hide(); // 显示保存成功的提示信息 showSuccessMsg(); $("#real-name").prop("disabled", true); $("#id-card").prop("disabled", true); $("#form-auth>input[type=submit]").hide(); } } }); })
5.当用户进行实名制后,那么在我的房源页面,就不应该显示去实名认证按钮
- step1 实名制之前显示界面
- step2 在myhouse.js中进行判断验证,如果用户已经实名制了,那么就不显示此按钮,否则才会显示
$(document).ready(function(){ // 对于发布房源,只有认证后的用户才可以,所以先判断用户的实名认证状态 $.get("/api/v1.0/users/auth", function(resp){ if ("4101" == resp.errno) { // 用户未登录 location.href = "/login.html"; } else if ("0" == resp.errno) { // 未认证的用户,在页面中展示 "去认证"的按钮 if (!(resp.data.real_name && resp.data.id_card)) { $(".auth-warn").show(); return; } } })});
- step3 实名制之后显示页面
六丶测试用户模块中个人信息栏,我的爱家,实名认证
1.个人信息栏测试
- step1 登录网站,进入个人信息栏,显示如下
- step2 当修改用户名后,点击保存,显示保存成功提示
2.我的爱家栏测试,成功显示出用户头像,用户名以及手机号
3.用户实名认证栏测试
- step1 首次进行实名认证,界面显示如下
- step2 当未填写姓名和身份证号时,出现如下显示
- step3 当输入完整信息后,会显示保存成功,出现如下界面
- step4 查看数据库信息
- step5 此号码已经实名认证成功,再次进入实名认证,则显示如下界面