项目视频地址 6.01-云笔记项目-1_哔哩哔哩_bilibili P31-P36
需求是这样的

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

创建应用,一共创建了三个,图就不都截出来了
![]()
注册应用

修改时区配置

修改数据库配置

修改模板配置

屏蔽一个中间件

添加静态文件配置

创建数据库

我们的项目结构是这样的

之后我们配置根路由

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


这里注意created_time与update_time在数据库中会存储UTC时间,如果你用模板层读的话会自动给你转成你设置时区的时间(如果前后端分离你把接口给前端的时候要让他做一些处理,或者你自己做一些处理)
外键形式为 一对多 ,一个用户有多个笔记,外键挂到多表上
搞清楚这些之后再想怎么存储,没有外键的正常存,有外键的需要多加一个字段,字段名称我们直接看一下mysql

多出来的字段名称为username_id
在这个项目的主页上我只放了两个链接,实际开发中主页要呈现的东西还是很多的


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

对user中的urls设置两个路由


html
- 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="注   册">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页面,如果没有被占用就在数据表中加上数据,然后返回注册成功页面

html
- 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>

- 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>

登录账户与注册账户的前端是相似的
html
- 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="登   录">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页面
- 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>

笔记部分一共有五个路由

依次是 笔记列表,增加笔记,删除笔记,编辑笔记,退出登录
登录后会自动转到笔记列表

html
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- 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
点击这里进入添加笔记路由

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

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

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

视图

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

html
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- <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>
点击修改可以对笔记进行更新

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

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

视图

如果是get就读取,如果是post就存储
html
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- <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>
点击这里可以删除笔记

点击后删除的笔记就没了

视图

通过查询字符串拿到id,笔记的id是唯一的,与其他用户无关,比如一个用户的笔记id有1,7,8,另一个用户的笔记就不可能再有1,7,8
拿到id后删了,之后302回笔记列表
点击这里可以退出登录

退出登录后返回登录页面

视图

上面基本已经完成了云笔记的基本功能,下面我们对该项目进行一些提升
密码密文存储用到了哈希算法,哈希算法多用于密码加密与文件的完整性校验
哈希算法有三个特点
python中有一个自带的库hashlib可以做哈希算法,我们下面做一个例子

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

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

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

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

任何设备使用hashlib中的md5()只要输入相同,得到的结果一定相同
项目中涉及密码的地方都在用户应用中,我们首先来改注册

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

之后修改登录页

修改之后可以正常登录

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

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

之后修改列表页视图

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

由于我们的表中的username设置的是unique,所以如果两台机子在同一时间起了同一个名字的时候会出现问题(并发写入问题)。
解决方式就是在添加到数据库之前做一个try,如果没try成功,就告诉用户该名字已被注册就行了

或者你告诉他出现了未知的错误,请重新操作
我们有很多地方需要校验登录状态,比如登录页和主页,如果用户已经登录了,就不要再让他看见登录页了,这个时候我们可以搞一个校验登录状态的装饰器
装饰器的基本使用方法可以看一下这个 其余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以及其他参数一起走,也就是正常执行

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

再到登录页面加入装饰器

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

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