FunRec-task4

task4前后端基础及交互

(Datawhale32期组队学习)

基础点

  • 前端基础

    • Web前端
      • web是
      • 标准构成
      • html
      • css
      • js
    • Vue
      • 安装
      • 实例
      • 生命周期
      • 项目
    • H5项目
      • H5
      • Vue开发H5页面
  • 后端请求逻辑

    • Flask基础
      • 路由
      • 请求
      • 重定向与错误处理
      • SQLAlchemy
  • 前后端交互

    • 项目样式展示
    • 后端目录
      • 用户
      • 推荐
      • 热门
      • 详情
      • 用户行为

知识点

1前端基础

1.1 Web前端
1.1.1 web是

web(world wide web)全球广域网,万维网,基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。

  • 在Internet上的一种网络服务
  • HTML,CSSJS,ajax,DOM等前端技术,实现网站在客服端的正确显示及交互功能。
1.1.2 标准构成

结构+表现+行为

  • 结构:对网页元素进行整理和分类,语义分析对网页划分结构,html
  • 表现:设置网页元素外观,css
  • 行为:网页模型定义及交互,js
1.1.3 html
  • Hyper Text Markup Language
  • 标签要成对对应

超文本:

  • 超越文本限制,加入图、声、flash、media等
  • 超级链接文本,从一个文件跳另一个,与世界各地主机文件连接

格式:

<!DOCTYPE html> <!--声明为 HTML5 文档-->
<html>   
    <head> <!--文档的元(meta)数据-->
        <meta charset="utf-8">
        <title>我的第一个页面</title>
    </head>
    <body> <!--可见的页面内容-->
        <h1>一个一级标题</h1>
			 <p>一个段落。</p>
    </body>
</html>

html

1.1.4 css

CSS样式表或层叠样式表(级联样式表)

1.规则
css

  • 选择器:需要改变样式的html元素
  • 声明:属性+值,分号分隔

2.语法格式

<!--<标签名 style="属性1:属性值1; 属性2:属性值2; 属性3:属性值3;"> 内容 </标签名>-->

<style>
   /*选择器{属性:值;}*/
   p {
   	  color:#06C; 
   	  font-size:14px;  
   	} 
   /*文字的颜色是 蓝色*/
   h4 {
   	 color:#900;
   }
   h1 {
   	 color:#090; 
   	 font-size:16px; 
   	}
   body { 
   	 background:url(bg2.jpg);
   }
</style>
1.1.5 js

JavaScript,Web 的编程语言,是一种基于对象和事件驱动并具有相对安全性的客户端脚本语言

脚本语言不需要编译,在运行过程中由 js 解释器(js引擎)逐行来进行解释并执行

1.组成
js1

2.书写

  1. 行内
<input type="button" value="点我试试" onclick="alert('Hello World')"/>

不方便

  1. 内嵌
<script>
    alert('Hello  World~!');
</script>
  1. 外部js文件
<script src="myScript.js"></script>
//myScript.js文件内容
function myFunction()
{
    document.getElementById("demo").innerHTML="我的第一个 JavaScript 函数";
}
  • 把js代码独立到html页面外,方便
  • 引用外部 JS文件的 script 标签中间不可以写代码
1.2 Vue

构建用户界面的渐进式框架,自底向上逐层应用。关注视图层,便于第三方项目组合。

1.2.1 安装

1.通过< script>标签引入

直接下载并用 <script> 标签引入,Vue 会被注册为一个全局变量。

  • 开发版本:https://cn.vuejs.org/js/vue.js

  • 生产版本:https://cn.vuejs.org/js/vue.min.js

2.CDN安装

  • 制作原型或学习
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  • 用于生产环境
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
  • 使用原生 ES Modules
<script type="module">
  import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
</script>

3.NPM安装(使用淘宝 NPM 镜像

  1. npm版本要大于3.0
cnpm install npm -g
  1. 安装vue
# 使用npm安装
$ cnpm install vue

# 使用cnpm安装
$ cnpm install vue
1.2.2 实例

1.语法

<div id="example">
    <h1>title : {{title}}</h1>
    <h1>url : {{url}}</h1>
</div>
<script>
    var vm = new Vue({
        el: '#example',
        data: {
            title: "一个Vue实例",
            url: "https://cn.vuejs.org/",
        },
        methods: {
            details: function() {
                return  this.title + " ------";
            }
        }
    })
</script>

Vue 构造器中有一个el 参数,它是 DOM 元素中的 id。在上面实例中 id 为 example,这表示接下来的改动全部在以上指定的 div 内,div 外部不受影响。

2.定义数据对象

  • data:定义属性,实例中有2个属性分别为:title、url
  • methods:定义的函数,可以通过 return 来返回函数值。
  • {{ }}:输出对象属性和函数返回值。

当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,html 视图将也会产生相应的变化。

1.2.3 生命周期

创建-运行-销毁

生命周期中发生(存在)事件(函数)称为生命周期钩子

Vue生命周期

  1. 开始阶段

    • new Vue({}) 创建vue实例
    • Init events&Lifecycle 此时vue实例为空,只有默认
    • beforeCreate()钩子函数运行,但是此时的vue是一个空壳,运行显示未定义。
  2. 初始化数据阶段

    • init injections&reactivity 初始化数据和事件(data、methods)
    • created()钩子函数运行获取vue初始化数据
  3. 编译模板阶段

    • creatd()钩子函数到beforeMount钩子函数之间,内存中将这个模板字符串渲染为内存中的DOM,但是还在内存中,没有挂载到页面中(止咳获取,不会触发update和其他钩子)
    • beforeMount钩子函数是可以在渲染前最后一次获取到vue中的数据,但是无法更改
  4. 挂载(渲染真实DOM)页面阶段

    • 内存中的虚拟DOM挂载到了浏览器的页面上!!!我们可以操作页面上的DOM元素了!
    • mounted()钩子函数可以操作DOM,mounted只会执行一次
  5. 运行阶段

    • 有两个钩子函数,只有当data中的数据发生改变时,才会触发
    • 实例中data改变,不是及时渲染到页面上的,而是在内存中对之前的虚拟DOM中的data数据进行更改,生成新的虚拟DOM之后才会挂载(渲染)到页面上
    • beforeUpdate()钩子函数,只有在data中的数据发生改变,且新生成的虚拟DOM没有挂载(渲染)到页面上
    • updated()钩子函数,更改data数据之后,虚拟DOM中的data发生改变页面上的data也发生改变(即新生成的虚拟DOM挂载上页面了)
  6. 销毁阶段

    • 用户关闭整个页面或者执行了某些$destroy时
    • beforeDestroy:销毁前执行的钩子函数,可以继续使用vue实例中的数据,如data,methods,filters,derictives等
    • destroyed:已经销毁了,vue实例中的数据不可用
1.2.4 项目

1.使用前

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject

#安装
npm install -g @vue/cli
# 或者
yarn global add @vue/cli

#检查其版本是否正确
vue --version

#升级包
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

2.使用

vue create hello-world
# 进入项目具体路径
cd hello-world

# 下载依赖
npm install

# 启动运行项目
npm run serve 

# 项目打包
npm run build

#使用图形化界面创建
vue ui

3.项目目录

├── v-proj
|	├── node_modules  	// 当前项目所有依赖,一般不可以移植给其他电脑环境
|	├── public			
|	|	├── favicon.ico	// 标签图标
|	|	└── index.html	// 当前项目唯一的页面
|	├── src
|	|	├── assets		// 静态资源img、css、js
|	|	├── components	// 小组件
|	|	├── App.vue		// 根组件
|	|	├── main.js		// 全局脚本文件(项目的入口)
|	|	└── router.js   // 路由脚本文件	
|	├── README.md
└	└── package.json  //配置文件,使用npm install安装

2后端请求逻辑

轻量级,可定制,web框架,实现一个网站或Web服务

2.1Flask基础

建立虚拟环境并安装flask
python Hello.py,浏览器打开localhost:5000

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
   return 'Hello World'

if __name__ == '__main__':
   app.run()
#app.run(host, port, debug, options)

route()函数是一个装饰器,将请求的url映射到对应的函数上。

2.1.1路由

1.路由:用户请求的url与视图函数的映射

  • Flask利用路由表将url映射到对应视图函数,将视图函数结果返回WSGI服务器。
  • route装饰器:将一个URL规则绑定到 一个视图函数上
@app.route('/test')
def test():
  return 'this is response of test function.'
  • add_url_rule():在路由表中注册映射关系。其实route装饰器内部也是通过调用add_url_rule()方法实现的路由注册。
def test():
  return 'this is response of test function.'
app.add_url_rule('/test',view_func=test)

2.指定HTTP方法

  • 支持HTTP的GET请求。
  • 其他方法可用methods关键字进行设置,类型list,指定多种HTTP方法
@app.route('/user', methods = ['POST', 'GET'])
def get_users():
  if request.method == 'GET':
    return ... # 返回用户列表
  else:
    return ... # 创建新用户 

3.匹配动态URL

  • 动态url:将同一类URL映射到同一视图函数,将URL可变部分用<>声明为变量
@app.route('/user/<uname>')
def get_userInfo(uname):
  return '%s\'s Informations' % uname
#可设置参数类型 int
@app.route('/user/<int:uname>')
def get_userInfo(uname):
    return '%s\'s Informations' % uname

有四种转换器:在这里插入图片描述

  • 一个视图函数可以解决多个问题,因此每个视图函数可以配置多个路由规则。
@app.route('/user')
@app.route('/user/<uname>')
@app.route('/user/<int:uname>')
def get_userInfo(uname=None):
    if uname:
    	return '%s\'s Informations' % uname
    else:
        return 'this is all informations of users'

4.构建URL
Flask框架帮忙计算url

@app.route('/test')
def test_url_for():
    print(url_for('get_userInfo', uname='zhangsan'))  # 输出:/user/zhangsan
    print(url_for('test_url_for', num=2))  # 输出:/test?num=2
2.1.2请求

HTTP请求,包括了来自客户端的请求对象(Request),服务器端的响应对象(Respose)和会话对象(Session)等

1.请求对象request

  • Form 是一个字典对象,包含表单当中所有参数及其值的键和值对;
  • args 是解析查询字符串的内容,它是问号(?)之后的URL的一部分,当使用get请求时,通过URL传递参数时可以通过args属性获取;
  • Cookies 是用来保存Cookie名称和值的字典对象;
  • files 属性和上传文件有关的数据。
from flask import request, session, make_response

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        if request.form['username'] == 'admin':
            session['username'] = request.form['username']
            response = make_response('Admin login successfully!')
            response.set_cookie('login_time', time.strftime('%Y-%m-%d %H:%M:%S'))
            return 'Admin login successfully!'
        else:
            return 'No such user!'
    elif request.method == 'GET':
        if request.args.get("username") == 'admin':
            session['username'] = request.form['username']
            return 'Admin login successfully!'
        else:
            return 'No such user!'
        
app.secret_key = '123456'
  • 根据method属性判断当前请求的类型
  • 通过form属性可以获取表单信息
  • 通过session来存储用户登陆信息。使用session时一定要设置一个密钥app.secret_key,并且密钥要尽量复杂
  • make_response,构建response对象的第二个参数代表响应状态码,缺省就是”200”
  • response.set_cookie()函数,来设置Cookie项,之后这个项值会被保存在浏览器中,等下次请求时可以从request对象中获取到cookies对象。
  • 官方API文档
  • 前后端交互,json类型使用get_data()获取(flask中的jsonify对象来处理json类型数据)
from flask import Flask, jsonify, request
@app.route('/login', methods=["POST"])
def login():
    request_str = request.get_data()
    request_dict = json.loads(request_str)

2.响应对象response

  1. 视图函数 return 多个值
@app.route("/user_one")
def user_one():
    return "userInfo.html", "200 Ok", {"name": "zhangsan"; "age":"20"}
- "userInfo.html"字符串,也是网页的内容,必须
- "200 Ok",状态码及解析,必须
- 请求头,非必须
  1. 使用Response创建
    创建配置参数即可
from flask import Response

@app.route("/user_one")
def user_one():
    response = Response("user_one")
    response.status_code = 200
    response.status = "200 ok"
    response.data = {"name": "zhangsan"; "age":"20"}
    return response
  1. 使用make_response函数
@app.route("/user_one")
def user_one():
    response = make_response('user_one', 200, {"name": "zhangsan"; "age":"20"})
    return response
- 字符串
- 第二个传状态码
- 第三个传请求头。
- json的数据格式可通过 jsonify 函数将其转化成json格式,再通过response对象发送给前端
@app.route('/hot_list', methods=["GET"])
def hot_list():
    if request.method == "GET":
        user_id = request.args.get('user_id')
        page_id = request.args.get('page_id')
        if user_id is None or page_id is None:
            return make_response(jsonify({"code": 2000, "msg": "user_id or page_id is none!"}), 200)
2.1.3重定向与错误处理

1.重定向页面:当一个请求过来还需要请求另一个视图函数才可时,用redirect(location, code=302, Response=None)

from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route("/demo")
def demo():
    url = url_for("demo2")  # 路由反转,根据视图函数名获取路由地址
    return redirect(url)

@app.route("/demo2")
def demo2():
    return "this is demo2 page"

@app.route("/")
def index():
    # 使用方法:redirect(location, code=302, Response=None) 
    return redirect("/demo", 301) 
状态码说明
300Multiple Choice,让用户选择
301Moved Permanently,永久重定向
302Found,临时重定向
303See Other,查看其它位置
304Not Modified,资源未发生变化
305Use Proxy,需要通过代理访问

2.错误处理:遇到特定错误代码时重写错误页面,可以使用 errorhandler() 装饰器

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

遇到404错误时,会调用page_not_found()函数,返回元组数据,第一个元素是”page_not_found.html”的模板页,第二个元素代表错误代码,返回值会自动转成 response 对象。

2.1.4 SQLAlchemy
  • ORM 工具包,为应用程序开发人员提供了SQL的全部功能和和ORM操作。
  • Object Relation Mapping指的是将对象参数映射到底层RDBMS表结构的技术,ORM API提供了执行CRUD操作的方法

1.安装

pip install pymysql

2.操作

#1.创建连接
from sqlalchemy import create_engine

def mysql_db(host='127.0.0.1',dbname='3306'):
    engine = create_engine("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8".format(host,dbname))
    print(engine)  # Engine(mysql+pymysql://root:***@127.0.0.1:49168/3306?charset=utf8)

create_engine("mysql://user:password@hostname/dbname?charset=utf8",
                       echo=True,
                       pool_size=8,
                       pool_recycle=60*30)
- "数据库+数据库连接框架://用户名:密码@IP地址:端口号/数据库名称?连接参数";
- echo是设置当前ORM语句是否转化为SQL打印;
- pool_size是用来设置连接池大小,默认值为5;
- pool_recycle设置连接失效的时间,超过时间连接池会自动断开

3.创建数据库表类

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'UserInfo'
    index = Column(Integer(), primary_key=True)
    user_id = Column(Integer(), unique=True)
    username = Column(String(30))
    passwd = Column(String(500))

    def __init__(self,index, user_id, username, passwd):
        self.index = index
        self.user_id = user_id
        self.username = username
        self.passwd = passwd

declarative_base()函数,可以将python类和数据库表进行关联映射,并通过 _tablename_ 属性将数据库模型类和表进行管理。其中Column() 表示数据表中的列,Integer()和String()表示数据库的数据类型。

4.操作数据库
借助sqlalchemy中的session来创建程序与数据库之间的会话。

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

def mysql_db(host='127.0.0.1',dbname='test'):
    engine = create_engine("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8mb4".format(host,dbname))

    session = sessionmaker(bind=engine)
    Base.metadata.create_all(engine)
    return engine, session()
- flush:预提交,提交到数据库文件,还未写入数据库文件中
- commit:提交了一个事务
- rollback:回滚
- close:关闭session连接
#1增加用户
engine, session = mysql_db()
user = User("100","zhangsan","11111")
session.add(user)#add操作会把user加入当前session维护的持久空间(可以从session.dirty看到)中
session.commit()

#2查询数据
engine, session = mysql_db()
users = session.query(User).filter_by(passwd='11111').all()

for item in users:
    print(item.username,item.passwd)
#只有等到.count() .first() .all() 具体函数时才会去数据库执行。
#使用 **filter()** 方法查询

#3修改数据
session.query(User).filter_by(username="zhangsan").update({'passwd': "123456"})

users = session.query(User).filter_by(username="zhangsan").first()
users.username = "zhangsan-test"
session.add(users)
session.commit()

#4删除数据
session.query(User).filter(User.username == "zhangsan-test").delete()
session.commit()

users = session.query(User).filter(User.username == "lisi").first()

filterfilter_by
支持所有比较运算符,相等比较用比较用==只能使用"=","!=“和”><"
过滤用类名.属性名过滤用属性名
不支持组合查询,只能连续调用filter变相实现参数是**kwargs,支持组合查询
支持and,or和in等

3前后端交互

3.1项目样式展示

在这里插入图片描述

3.2后端目录

在这里插入图片描述

  • 前端主要使用的是Vue框架+mint-ui
  • 后端主要使用的是Flask+Mysql+Mongodb+Redis来完成的
  • 前后端采用分离的额方式,通过json的数据格式进行数据传递。
  • 后端的主要逻辑在在server.py中,其中主要包含用户注册,登录,推荐列表,热门列表,获取新闻详情页以及用户的行为等功能。
3.2.1用户

1.注册

def register():
    """用户注册"""
    request_str = request.get_data()
    request_dict = json.loads(request_str)
    
    user = RegisterUser()
    user.username = request_dict["username"]
    user.passwd = request_dict["passwd"]

    # 查询当前用户名是否已经被用过了
    result = UserAction().user_is_exist(user, "register")
    if result != 0:
        return jsonify({"code": 500, "mgs": "this username is exists"})

    user.userid = snowflake.client.get_guid() # 雪花算法生成唯一的用户id
    
    user.age = request_dict["age"]
    user.gender = request_dict["gender"]
    user.city = request_dict["city"]

    save_res = UserAction().save_user(user)   # 将注册用户信息加入mysql
    if not save_res:
        return jsonify({"code": 500, "mgs": "register fail."})

    return jsonify({"code": 200, "msg": "register success."})
  • 用户名唯一
  • 生成唯一id(雪花算法,防止并发问题导致用户id出现冲负问题)
  • 注册信息加入mysql

2.登录

@app.route('/recsys/login', methods=["POST"])
def login():
    """用户登录
    """
    request_str = request.get_data()
    request_dict = json.loads(request_str)
    
    user = RegisterUser()
    user.username = request_dict["username"]
    user.passwd = request_dict["passwd"]

    # 查询数据库中的用户名或者密码是否存在
    try:
        result = UserAction().user_is_exist(user, "login")
        # print(result,"login")
        if result == 1:
            return jsonify({"code": 200, "msg": "login success"})
        elif result == 2:
            # 密码错误
            return jsonify({"code": 500, "msg": "passwd is error"})
        else:
            return jsonify({"code": 500, "msg": "this username is not exist!"})
    except Exception as e:
        return jsonify({"code": 500, "mgs": "login fail."})
  • 前端通过将输入的账号密码通过POST请求传给 /recsys/login,通过UserAction().user_is_exist()方法查询数据库中的用户名或者密码是否存在
  • 1表示账号密码正确,2表示密码错误,0表示用户不存在。
3.2.2推荐
@app.route('/recsys/rec_list', methods=["GET"])
def rec_list():
    """推荐页"""
    user_name = request.args.get('user_id')
    page_id = request.args.get('page_id')

    # 查询用户的id
    user_id = UserAction().get_user_id_by_name(user_name)  
    if not user_id:
        return False

    if user_id is None or page_id is None:
        return jsonify({"code": 2000, "msg": "user_id or page_id is none!"}) 
    try:
        # 获取推荐列表新闻信息
        rec_news_list = recsys_server.get_rec_list(user_id, page_id)
        if len(rec_news_list) == 0:
            return jsonify({"code": 500, "msg": "rec_list data is empty."})
        return jsonify({"code": 200, "msg": "request rec_list success.", "data": rec_news_list, "user_id": user_id})
    except Exception as e:
        print(str(e))
        return jsonify({"code": 500, "msg": "redis fail."}) 

前端通过请求 “/recsys/rec_list” 接口,后端通过前端传递过来的用户姓名,从数据库中获取用户id,再根据用户id去推荐服务(recsys_server)中获取到推荐列表

推荐列表是通过推荐服务的 get_rec_list(user_id, page_id) 接口获取

  • user_id:通过用户id,我们可以去redis中查找已经给用户构建好的新闻列表,将新闻信息返回给前端。
  • page_id:通过page id定位到目前已经给用户推荐到列表的位置,然后在从该位置之后去新的新闻内容。
 def get_rec_list(self, user_id, page_id):
        """给定页面的展示范围进行展示  user_id 后面做个性化推荐的时候需要用到"""
        # 根据page id计算需要获取redis中哪些范围的news_id, 假设每一页展示10个新闻
        s = (int(page_id) - 1) * 10
        e = s + 9

        # 返回的是一个news_id列表
        news_id_list = self.reclist_redis_db.zrange("rec_list", start=s, end=e) 

        # 根据news_id获取新闻的具体内容,并返回一个列表,列表中的元素是按照顺序展示的新闻信息字典
        news_info_list = []
        news_expose_list = []
        for news_id in news_id_list:
            news_info_dict = self._get_news_simple(news_id)
            news_info_list.append(news_info_dict)
            news_expose_list.append(news_info_dict["news_id"])  # 记录在用户曝光表上[user_exposure]

        self._save_user_exposure(user_id,news_expose_list)  # 曝光落表
        return news_info_list
  1. 先根据page id,计算从redis中推荐列表取的范围。
  2. 得到新闻id列表之后,通过_get_news_simple() 方法从mysql何redis中获取新闻列表所需的展现内容
  3. 已经在推荐列表中给用户曝光过的新闻,当天内不会再通过热门页对用户进行曝光。_save_user_exposure()方法来将已经曝光过的新闻存储到redis中,这样在热门推荐中,针对用户的曝光会对热门推荐的内容进行过滤。

返回数据:

"data": [
    {
      "news_id": "4bfb8aab-bcd8-4c74-b7fd-92b28ca5df69", 
      "cate": "国内", 
      "read_num": 0,
      "likes": 0, 
      "collections": 0, 
      "ctime": "2021-11-30 12:07", 
      "title": "北京市政协十三届五次会议将于2022年1月5日召开"
    },
        ...
    {
      "news_id": "4ded60ac-aa2f-408b-af4d-09ca0c58b50a", 
      "cate": "国内", 
      "read_num": 6, 
      "likes": 1, 
      "collections": 0, 
      "ctime": "2021-11-30 10:44", 
      "title": "江西万载县委原书记胡全顺获刑十一年六个月"
    }]
3.2.3热门
  • 前端通过请求’/recsys/hot_list’接口,通过传递用户姓名和当前页号来获取热门新闻列表。
  • 热门新闻信息主要是通过推荐服务(recsys_server)中的get_hot_list()方法来获取到热门新闻推荐列表
@app.route('/recsys/hot_list', methods=["GET"])
def hot_list():
    """热门页面"""
    if request.method == "GET":
        user_name = request.args.get('user_id')
        page_id = request.args.get('page_id')

        if user_name is None or page_id is None:
            return jsonify({"code": 2000, "msg": "user_name or page_id is none!"}) 

        # 查询用户的id
        user_id = UserAction().get_user_id_by_name(user_name)  
        if not user_id:
            return False

    try:
        # # 获取热门列表新闻信息
        rec_news_list = recsys_server.get_hot_list(user_id) 

        if len(rec_news_list) == 0:
            return jsonify({"code": 200, "msg": "request redis data fail."})
        # rec_news_list = recsys_server.get_hot_list(user_id, page_id)
        return jsonify({"code": 200, "msg": "request hot_list success.", "data": rec_news_list, "user_id": user_id})
    except Exception as e:
        print(str(e))
        return jsonify({"code": 2000, "msg": "request hot_list fail."}) 

与推荐逻辑类似,主要是get_hot_list()和get_rec_list()的区别

3.2.4详情

新闻的详细信息,喜好+收藏

@app.route('/recsys/news_detail', methods=["GET"])
def news_detail():
    """一篇文章的详细信息"""
    user_name = request.args.get('user_name')
    news_id = request.args.get('news_id')
    
    user_id = UserAction().get_user_id_by_name(user_name)  

    # if news_id is None or user_id is None:
    if news_id is None or user_name is None:
        return jsonify({"code": 2000, "msg": "news_id is none or user_name is none!"}) 
    try:
        news_detail = recsys_server.get_news_detail(news_id)
  
        if UserAction().get_likes_counts_by_user(user_id,news_id) > 0:
            news_detail["likes"] = True
        else:
            news_detail["likes"] = False

        if UserAction().get_coll_counts_by_user(user_id,news_id) > 0:
            news_detail["collections"] = True
        else:
            news_detail["collections"] = False
        # print("test",news_detail)
        return jsonify({"code": 0, "msg": "request news_detail success.", "data": news_detail})
    except Exception as e:
        print(str(e))
        return jsonify({"code": 2000, "msg": "error"}) 
  • 用户名字从mysql中获取用户id信息。防止用户id或者 page id出现空值的情况,需要进行判断。紧接着通过recsys_server服务的get_news_detail()方法,根据新闻的id进行获取内容。
  • 根据mysql中再次查询用户与该新闻是否存在记录,并将结果返回给前端,将状态点亮展示。(采用两个字段likes和collections,通过True,False来判断用户对该文章之前是否点击过喜欢或收藏。)

返回数据格式:

{
  "code": 0, 
  "data": {
  	"news_id": "4ded60ac-aa2f-408b-af4d-09ca0c58b50a", 
    "cate": "军事", 
    "title": "运-20加油机首次现身台海上空 堪称“战力倍增器”", 
    "content": "原标题:视频丨运-20加油机首次现身台海上空,堪称“战力倍增器”据台湾“中央社”报道,台防务部门晚间发布最新动态,11月28日白天解放军空军有27架次多型战机出现在了台湾所谓“西南空域”。首度被台媒披露现身台海的运油-20,是以国产运-20大型远程运输机为平台改装的空中加油机。据媒体测算,运油-20加油机装载燃油超过100吨,能大幅提升战机的空中续航能力,堪称“战力倍增器”。", 
    "collections": true, 
    "read_num": 6, 
    "likes": true, 
    "ctime": "2021-11-30 10:44", 
    "url": "https://news.sina.com.cn/c/2021-11-30/doc-ikyakumx1093113.shtml"
  }, 
  "msg": "request news_detail success."
}
3.2.5用户行为
  • 阅读:即用户在点击一篇新闻的详细页
  • 喜欢:点击按钮触发系统记录喜欢行为
  • 收藏:点击按钮触发系统记录收藏行为
  1. 用户点进一篇新闻的详情页时候,前端会发送一个请求,并给后端传递一个json格式数据
{
	"user_name":"wang",
	"news_id":"0a745412-db48-4e37-bf13-9a5b56028f7e",
	"action_time":1638532127190,
	"action_type":"read"
}
  1. 点击喜欢或收藏按钮的时候同样会产生一个请求,并发送json数据
//点击喜欢
{
	"user_name":"wang",
	"news_id":"0a745412-db48-4e37-bf13-9a5b56028f7e",
	"action_time":1638532127190,
	"action_type":"like:ture" 
}

//点击收藏
{
	"user_name":"wang",
	"news_id":"0a745412-db48-4e37-bf13-9a5b56028f7e",
	"action_time":1638532127190,
	"action_type":"collections:true" 
}
  1. 通过前端的传递的数据,后端对应的接口可以通过传递的参数对用户行为进行记录
@app.route('/recsys/action', methods=["POST"])
def actions():
    """用户的行为:阅读,点赞,收藏"""
    request_str = request.get_data()
    request_dict = json.loads(request_str)
    
    username = request_dict.get('user_name')
    newsid = request_dict.get('news_id')
    actiontype = request_dict.get("action_type")
    actiontime = request_dict.get("action_time")

    userid = UserAction().get_user_id_by_name(username)   # 获取用户 id
    if not userid:
        return jsonify({"code": 2000, "msg": "user not register"})

    action_type_list = actiontype.split(":")

    if len(action_type_list) == 2:
        _action_type = action_type_list[0]
        if action_type_list[1] == "false": # 如果这个参数为false的话, 表示数据库中存在记录  需要删除数据 
            if _action_type=="likes": 
                UserAction().del_likes_by_user(userid,newsid)    # 删除用户喜欢记录
            elif _action_type=="collections":
                UserAction().del_coll_by_user(userid,newsid)     # 删除用户收藏记录 
        else: 
            if _action_type=="likes":  # 如果这个参数为true的话, 表示数据库中不存在记录  需要添加数据 
                userlikes = UserLikes()
                userlikes.new(userid,username,newsid)
                UserAction().save_one_action(userlikes)       # 记录用户喜欢记录
            elif _action_type=="collections":
                usercollections = UserCollections()
                usercollections.new(userid,username,newsid)
                UserAction().save_one_action(usercollections)   # 记录用户收藏记录 

    try:
        # 落日志
        logitem = LogItem()
        logitem.new(userid,newsid,action_type_list[0])
        LogController().save_one_log(logitem)

        # 更新redis中的展示数据   新闻侧
        recsys_server.update_news_dynamic_info(news_id=newsid,action_type=action_type_list)
        return jsonify({"code": 200, "msg": "action success"})

    except Exception as e:
        print(str(e))
        return jsonify({"code": 2000, "msg": "action error"})

注意代码包含逻辑:

  1. 用户行为记录:前端传递过来的数据中存在一个字段 “action_type”:“like:ture” 或 “action_type”:“like:false”

    • truefalse记录用户行为(点击、取消)
    • false传值后,like状态改变,说明数据库存在该记录
    • true传值后,like状态改变,说明数据库 不存在该记录
  2. 用户行为落日志:记录的一些线上信息,分析这样的用户行为来更好的了解用户兴趣,从而进行更加个性化的推荐

    • LogController() 的 save_one_log() 方法对数据进行了存储到了mysql中
    • 日志数据,可以帮助我们更新用户画像中的一些动态特征
    • 后面构建模型时,我们也能获取到用户的一些点击率,收藏率的建模
  3. 新闻动态数据更新

    • 新闻的阅读人数、喜欢人数和收藏人数动态变换,需要更新redis中新闻的这些动态的数据
    • 通过推荐服务里面的 update_news_dynamic_info()方法进行更新
    • 获取redis中的信息,根据前端传递过来的行为来更新对用新闻属性的值。更改完之后,从新将新的结果从新存储到redis中
def update_news_dynamic_info(self, news_id,action_type):
    """更新新闻展示的详细信息"""
    news_dynamic_info_str = self.dynamic_news_info_redis_db.get("dynamic_news_detail:" + news_id)
    news_dynamic_info_str = news_dynamic_info_str.replace("'", '"' ) # 将单引号都替换成双引号
    news_dynamic_info_dict = json.loads(news_dynamic_info_str)
 
    if len(action_type) == 2:
        if action_type[1] == "true":
            news_dynamic_info_dict[action_type[0]] +=1
        elif action_type[1] == "false":
            news_dynamic_info_dict[action_type[0]] -=1
    else:
        news_dynamic_info_dict["read_num"] +=1
 
    news_dynamic_info_str = json.dumps(news_dynamic_info_dict)
 
    news_dynamic_info_str = news_dynamic_info_str.replace('"', "'" )
    res = self.dynamic_news_info_redis_db.set("dynamic_news_detail:" + news_id, news_dynamic_info_str)
    return res

个人需要补充的点

  • debug

参考

  • https://github.com/datawhalechina/fun-rec
  • https://blog.csdn.net/qq_41889956/article/details/91378695
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值