23. 云笔记项目

项目视频地址 6.01-云笔记项目-1_哔哩哔哩_bilibili P31-P36

需求是这样的

我们简单分析一下,首先任意网站得有一个主页,主页的内容在后期修改会很频繁,所以主页要单独做一个应用,其余就是用户做一个应用,笔记做一个应用,用户与笔记各自给一个数据表,然后使用外键将两个数据表联系起来

目录

1  准备工作

2  创建数据表

3  主页部分

4  用户部分

4.1  注册账户

4.2  用户名已被注册

4.3  注册成功

4.4  登录账户

4.5  登录异常

5  笔记部分

5.1  笔记列表

5.2  添加笔记

5.3  更新笔记

5.4  删除笔记

5.5  退出登录

6  提升

6.1  密码密文存储

6.1.1  哈希算法

6.1.2  项目中应用

6.2  双重cookie验证

6.3  同时注册同一个用户名的冲突

6.4  校验登录状态装饰器


1  准备工作

创建项目

创建应用,一共创建了三个,图就不都截出来了

注册应用

修改时区配置

修改数据库配置

修改模板配置

屏蔽一个中间件

添加静态文件配置

创建数据库

我们的项目结构是这样的

之后我们配置根路由

2  创建数据表

无论是什么表,创建时间与更新时间这两个字段是必须要有的(业务上使用的比较频繁,比如创建时间可以统计每日的新增用户量,更新时间可以统计当日用户活跃状态,因为用户信息还有关注,收藏,点赞这种。如果不给的话也不会报错),密码最大长度一般设置为32位,后面用到哈希算法加密后会再提到

这里注意created_time与update_time在数据库中会存储UTC时间,如果你用模板层读的话会自动给你转成你设置时区的时间(如果前后端分离你把接口给前端的时候要让他做一些处理,或者你自己做一些处理)

外键形式为 一对多 ,一个用户有多个笔记,外键挂到多表上

搞清楚这些之后再想怎么存储,没有外键的正常存,有外键的需要多加一个字段,字段名称我们直接看一下mysql

多出来的字段名称为username_id

3  主页部分

在这个项目的主页上我只放了两个链接,实际开发中主页要呈现的东西还是很多的

4  用户部分

templates和static都各自放到了应用文件夹中

对user中的urls设置两个路由

4.1  注册账户

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页</title>
    <link rel="stylesheet" href="/static/css/initialization.css">
    <link rel="stylesheet" href="/static/css/register.css">
</head>
<body>
    <img src="/static/pic/login_background.png" alt="">
    <div id="login_div">
        <form action="/user/register/" method="post">
            <ul>
                <li>用户注册</li>
                <li><span></span><input type="text" name="username" class="content_input" maxlength="8"></li>
                <li><span></span><input type="password" name="password" class="content_input" maxlength="8"><span></span></li>
                <li><input type="submit" value="注&nbsp&nbsp&nbsp册"></li>
            </ul>
        </form>
    </div>
</body>
<script src="/static/js/register.js"></script>
</html>

css

img {
    position:fixed;
    width:100%;
    height:100%;
    z-index:-100;
}

.content_input {
    width:200px;
    height:30px;
    text-indent:30px;
    outline:none;
}

span {
    display:inline-block;
    width:20px;
    height:20px;
}

.content_input:focus {
    border:3px rgb(107,157,188) solid;
}

#login_div {
    position:absolute;
    right:350px;
    top:50%;
    width:225px;
    height:285px;
    // background-color:white;
    margin-top:-150px;


}

#login_div li {
    position:relative;
    margin-top:20px;
    text-align:center;
}

#login_div li:nth-child(1) {
    color:rgb(82,152,197);
    font-size:30px;
}


#login_div li:nth-of-type(2) span:before {
    content:'\e971';
    font-family:'icomoon';
    font-size:20px;
}

#login_div li:nth-of-type(3) span:nth-of-type(1):before {
    content:'\e98d';
    font-family:'icomoon';
    font-size:20px;
}

#login_div li:nth-of-type(3) span:nth-of-type(2):before {
    content:attr(data-content-after);
    font-family:'icomoon';
    font-size:20px;
}

#login_div span:nth-of-type(1) {
    position:absolute;
    left:20px;
}

#login_div span:nth-of-type(2) {
    position:absolute;
    right:20px;
}

input[type='submit'] {
    width:200px;
    height:50px;
    background-color:rgb(61,155,237);
    color:white;
    border:0px;
    font-size:24px;
}

#login_div li:nth-child(5) input[type='checkbox'] {
    position:absolute;
    left:25px;
    top:-10px;
    width:20px;
    height:20px;
}

#login_div li:nth-child(5) em {
    position:absolute;
    left:50px;
    top:-12px;
    font-size:16px;
}

#login_div li:nth-child(6) input[type='checkbox'] {
    position:absolute;
    left:25px;
    top:30px;
    width:20px;
    height:20px;
}

#login_div li:nth-child(6) em {
    position:absolute;
    left:50px;
    top:17px;
    font-size:16px;
    text-align:left;
}

js

function change_password() {
    if (password_input.type == 'password') {
        // 第二个参数是闭上眼睛的icomoon
        eye_span.setAttribute('data-content-after','')
        password_input.type = 'text'
    }
    else {
        password_input.type = 'password'
        // 第二个参数是睁开眼睛的icomoon
        eye_span.setAttribute('data-content-after','')
    }
}

password_input = document.querySelector("input[name='password']")
eye_span = document.querySelector('#login_div span:nth-of-type(2)')
// 第二个参数是睁开眼睛的icomoon
eye_span.setAttribute('data-content-after','')
eye_span.onclick =change_password

视图是这样的,user_data是之前我们创建的数据表,已在上方引用

如果是get就直接给页面,如果是post会先检查一遍用户名是否已被占用,如果被占用就弹出already_register.html页面,如果没有被占用就在数据表中加上数据,然后返回注册成功页面

4.2  用户名已被注册

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册异常</title>
</head>
<body>
    <p>这个用户名已经被注册了,5秒后返回注册页</p>
</body>
<script>
    p = document.querySelector('p')
    i = 5
    setInterval(function() {
        i = i - 1
        p.innerHTML = '这个用户名已经被注册了,' + i +'秒后返回注册页'
    },1000)
    setTimeout(function() {location.href = '/user/register/'},5000)
</script>
</html>

4.3  注册成功

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册成功</title>
</head>
<body>
    <p>注册成功,5秒后返回至登录页</p>
</body>
<script>
    p = document.querySelector('p')
    i = 5
    setInterval(function() {
        i = i - 1
        p.innerHTML = '注册成功,' + i +'秒后返回至登录页'
    },1000)
    setTimeout(function() {location.href = '/user/login/'},5000)
</script>
</html>

4.4  登录账户

  • checkbox如果勾选了,post的值是on,如果没勾选post的值是None

登录账户与注册账户的前端是相似的

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
    <link rel="stylesheet" href="/static/css/initialization.css">
    <link rel="stylesheet" href="/static/css/login.css">
</head>
<body>
    <img src="/static/pic/login_background.png" alt="">
    <div id="login_div">
        <form action="/user/login/" method="post">
            <ul>
                <li>用户登录</li>
                <li><span></span><input type="text" name="username" class="content_input" maxlength="8"></li>
                <li><span></span><input type="password" name="password" class="content_input" maxlength="8"><span></span></li>
                <li><input type="submit" value="登&nbsp&nbsp&nbsp录"></li>
                <li><input type="checkbox" name="remember_me"><em>记住我</em></li>
            </ul>
        </form>
    </div>
</body>
<script src="/static/js/login.js"></script>
</html>

css

img {
    position:fixed;
    width:100%;
    height:100%;
    z-index:-100;
}

.content_input {
    width:200px;
    height:30px;
    text-indent:30px;
    outline:none;
}

span {
    display:inline-block;
    width:20px;
    height:20px;
}

.content_input:focus {
    border:3px rgb(107,157,188) solid;
}

#login_div {
    position:absolute;
    right:350px;
    top:50%;
    width:225px;
    height:285px;
    // background-color:white;
    margin-top:-150px;


}

#login_div li {
    position:relative;
    margin-top:20px;
    text-align:center;
}

#login_div li:nth-child(1) {
    color:rgb(82,152,197);
    font-size:30px;
}


#login_div li:nth-of-type(2) span:before {
    content:'\e971';
    font-family:'icomoon';
    font-size:20px;
}

#login_div li:nth-of-type(3) span:nth-of-type(1):before {
    content:'\e98d';
    font-family:'icomoon';
    font-size:20px;
}

#login_div li:nth-of-type(3) span:nth-of-type(2):before {
    content:attr(data-content-after);
    font-family:'icomoon';
    font-size:20px;
}

#login_div span:nth-of-type(1) {
    position:absolute;
    left:20px;
}

#login_div span:nth-of-type(2) {
    position:absolute;
    right:20px;
}

input[type='submit'] {
    width:200px;
    height:50px;
    background-color:rgb(61,155,237);
    color:white;
    border:0px;
    font-size:24px;
}

#login_div li:nth-child(5) input[type='checkbox'] {
    position:absolute;
    left:25px;
    top:-10px;
    width:20px;
    height:20px;
}

#login_div li:nth-child(5) em {
    position:absolute;
    left:50px;
    top:-12px;
    font-size:16px;
}

#login_div li:nth-child(6) input[type='checkbox'] {
    position:absolute;
    left:25px;
    top:30px;
    width:20px;
    height:20px;
}

#login_div li:nth-child(6) em {
    position:absolute;
    left:50px;
    top:17px;
    font-size:16px;
    text-align:left;
}

js

function change_password() {
    if (password_input.type == 'password') {
        // 第二个参数是闭上眼睛的icomoon
        eye_span.setAttribute('data-content-after','')
        password_input.type = 'text'
    }
    else {
        password_input.type = 'password'
        // 第二个参数是睁开眼睛的icomoon
        eye_span.setAttribute('data-content-after','')
    }
}

password_input = document.querySelector("input[name='password']")
eye_span = document.querySelector('#login_div span:nth-of-type(2)')
// 第二个参数是睁开眼睛的icomoon
eye_span.setAttribute('data-content-after','')
eye_span.onclick =change_password

视图

如果用户名与密码都正确,那么就进入笔记列表路由,如果其中有一个错误则会返回login_error页面

4.5  登录异常

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录异常</title>
</head>
<body>
    <p>你输入的账户名或密码错误,5秒后返回登录页</p>
</body>
<script>
    p = document.querySelector('p')
    i = 5
    setInterval(function() {
        i = i - 1
        p.innerHTML = '你输入的账户名或密码错误,' + i +'秒后返回登录页'
    },1000)
    setTimeout(function() {location.href = '/user/login/'},5000)
</script>
</html>

5  笔记部分

笔记部分一共有五个路由

依次是 笔记列表,增加笔记,删除笔记,编辑笔记,退出登录

5.1  笔记列表

登录后会自动转到笔记列表

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
        table {
            width:1400px;
            text-align:center;
        }
        td {
            float:left;
            width:250px;
            height:35px;
            line-height:35px;
            border:1px solid black;
            margin-left:-1px;
            margin-top:-1px;
        }
        table thead tr:nth-child(1) td{
            background-color:rgb(220,220,220);
        }
</style>
<body>
{{current_user}}的笔记 <a href="/note/create_note/">添加新笔记</a> <a href="/note/logout/">退出登录</a>
<table>
    <thead>
        <tr>
            <td>标题</td>
            <td>创建时间</td>
            <td>修改时间</td>
            <td>操作</td>
        </tr>
    </thead>
    <tbody>
        {% for note in user_note_data %}
        <tr>
            <td>{{note.title}}</td>
            <td>{{note.created_time}}</td>
            <td>{{note.update_time}}</td>
            <td>
                <a href="/note/edit_note/?id={{note.id}}">修改</a>
                <a href="/note/delete_note/?id={{note.id}}">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
</body>
</html>

视图

通过cookie确认用户身份,拿到该用户的所有笔记,拿到该用户的用户名,把以上变量传给note_list.html

5.2  添加笔记

点击这里进入添加笔记路由

点进去两个文本框都是没有东西的

随便输入一些内容后点击提交后,会在note_list中显示

  • 由于创建note数据表的时候对创建时间与修改时间设置了自动添加,所以在这里我们不需要手动去动它

如果你什么也没写直接提交也能存进去

视图

如果是get就直接给页面,如果是post就拿到标题,内容与用户id,之后在数据表中创建内容,如果外键那个你不确定字段是什么,你直接用desc查一下mysql就可以了

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/initialization.css">
</head>
<body>
    <form action="/note/create_note/" method="post">
        笔记标题<input type="text" name="title"><br>
        笔记内容<textarea name="content" cols="30" rows="10"></textarea><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

5.3  更新笔记

点击修改可以对笔记进行更新

点击后会获取到之前的内容

随便改一改点击提交后,note_list会得到更新后的笔记内容

视图

如果是get就读取,如果是post就存储

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/initialization.css">
</head>
<body>
    <form action="/note/edit_note/" method="post">
        笔记标题<input type="text" name="title" value="{{title}}"><br>
        笔记内容<textarea name="content" cols="30" rows="10">{{content}}</textarea><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

5.4  删除笔记

点击这里可以删除笔记

点击后删除的笔记就没了

视图

通过查询字符串拿到id,笔记的id是唯一的,与其他用户无关,比如一个用户的笔记id有1,7,8,另一个用户的笔记就不可能再有1,7,8

拿到id后删了,之后302回笔记列表

5.5  退出登录

点击这里可以退出登录

退出登录后返回登录页面

视图

6  提升

上面基本已经完成了云笔记的基本功能,下面我们对该项目进行一些提升

6.1  密码密文存储

6.1.1  哈希算法

密码密文存储用到了哈希算法,哈希算法多用于密码加密与文件的完整性校验

哈希算法有三个特点

  • 无论多长的字符串通过哈希算法都会产生一个定长的字符串
  • 很难破译
  • 输入内容不同时,输出内容一定不同。输入内容相同时,输入内容一定相同

python中有一个自带的库hashlib可以做哈希算法,我们下面做一个例子

  • 我们下面都以md5为例,如果想了解具体哈希算法的情况可以查一下相关的资料

m.update()的参数必须为字节字符串,我们可以使用encode()将普通字符串转换为字节字符串

通过哈希函数md5处理后,得出来的结果是32位

如果想算一个新的的哈希值需要重新搞一个md5对象

如果直接对m使用update(b'123'),相当于求123456123的哈希值,而不是123的

任何设备使用hashlib中的md5()只要输入相同,得到的结果一定相同

6.1.2  项目中应用

项目中涉及密码的地方都在用户应用中,我们首先来改注册

  • 这里虽然名称每次都相同,但是每一次都会实例化一个对象,所以无所谓

这样注册后就会显示为密文了

之后修改登录页

修改之后可以正常登录

6.2  双重cookie验证

我们上面只设置了一个username_id的cookie,用户只需要改几个数就可以成功登录他人的笔记,这个安全性太低了,在我们增加完密码密文后,我们可以把密码的密文形式也加入到cookie中,这样两两配合,安全性更高

首先在登录的时候,要把密码存储进去

用户id与用户密码的密文形式,一般情况下用户自己也是不知道的,所以相对安全

之后修改列表页视图

如果你只有user_id或密码错误的时候,都会返回请不要修改cookie

6.3  同时注册同一个用户名的冲突

由于我们的表中的username设置的是unique,所以如果两台机子在同一时间起了同一个名字的时候会出现问题(并发写入问题)。

解决方式就是在添加到数据库之前做一个try,如果没try成功,就告诉用户该名字已被注册就行了

或者你告诉他出现了未知的错误,请重新操作

6.4  校验登录状态装饰器

我们有很多地方需要校验登录状态,比如登录页和主页,如果用户已经登录了,就不要再让他看见登录页了,这个时候我们可以搞一个校验登录状态的装饰器

装饰器的基本使用方法可以看一下这个 其余python操作_Suyuoa的博客-CSDN博客

我们在utils中创建一个py文件,名为check_login

逻辑是这样的,传入参数request,获取user_id和password,获取COOKIES的get如果获取不到也不会报错。之后去数据库拿到用户名与密码,在数据库中使用get如果拿不到是会报错的,所以用try搞一个容错。之后将COOKIES中的密码与数据库中的密码进行比对,如果比对成功就302到笔记列表页。如果比对不成功就会把这个请求放过去,该怎么着就怎么着,比如我访问了首页,比对之后发现不对,那么还是访问首页,没有变化

这里如果是第一次看装饰器可能看起来比较乱,我们理一下装饰器的执行顺序。首先要被校验的视图作为func传入外层check_login(),check_login()中有一个内层的check_login,def只定义,不执行,所以就走到了return check_login,现在return到了内层check_login,然后正式校验。此时request以及其余所有参数传进来(其余参数你可以传进来,怎么传进来怎么传出去,我不在这里进行任何处理),如果密码对了就302走了(这里注意302的路由上面不要加装饰器,不然会一直循环这个装饰器)。如果密码不对我一样要走,走的时候返回被校验是函数func,带着拿来的request以及其他参数一起走,也就是正常执行

之后我们在首页视图加入装饰器

  • check_login中的func可以理解为要被检验的函数

再到登录页面加入装饰器

经测试如果cookie正常则访问首页与登录页均会跳转到笔记列表页,如果不正常就当没有这个校验器

当然像是添加笔记,删除笔记,编辑笔记,这些视图都有可能被直接用路由访问到,我们都可以加一个登录状态装饰器,装饰器这种东西复用起来也比较方便

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Suyuoa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值