• 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

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>注册页title>
    6. <link rel="stylesheet" href="/static/css/initialization.css">
    7. <link rel="stylesheet" href="/static/css/register.css">
    8. head>
    9. <body>
    10. <img src="/static/pic/login_background.png" alt="">
    11. <div id="login_div">
    12. <form action="/user/register/" method="post">
    13. <ul>
    14. <li>用户注册li>
    15. <li><span>span><input type="text" name="username" class="content_input" maxlength="8">li>
    16. <li><span>span><input type="password" name="password" class="content_input" maxlength="8"><span>span>li>
    17. <li><input type="submit" value="注   册">li>
    18. ul>
    19. form>
    20. div>
    21. body>
    22. <script src="/static/js/register.js">script>
    23. html>

    css

    1. img {
    2. position:fixed;
    3. width:100%;
    4. height:100%;
    5. z-index:-100;
    6. }
    7. .content_input {
    8. width:200px;
    9. height:30px;
    10. text-indent:30px;
    11. outline:none;
    12. }
    13. span {
    14. display:inline-block;
    15. width:20px;
    16. height:20px;
    17. }
    18. .content_input:focus {
    19. border:3px rgb(107,157,188) solid;
    20. }
    21. #login_div {
    22. position:absolute;
    23. right:350px;
    24. top:50%;
    25. width:225px;
    26. height:285px;
    27. // background-color:white;
    28. margin-top:-150px;
    29. }
    30. #login_div li {
    31. position:relative;
    32. margin-top:20px;
    33. text-align:center;
    34. }
    35. #login_div li:nth-child(1) {
    36. color:rgb(82,152,197);
    37. font-size:30px;
    38. }
    39. #login_div li:nth-of-type(2) span:before {
    40. content:'\e971';
    41. font-family:'icomoon';
    42. font-size:20px;
    43. }
    44. #login_div li:nth-of-type(3) span:nth-of-type(1):before {
    45. content:'\e98d';
    46. font-family:'icomoon';
    47. font-size:20px;
    48. }
    49. #login_div li:nth-of-type(3) span:nth-of-type(2):before {
    50. content:attr(data-content-after);
    51. font-family:'icomoon';
    52. font-size:20px;
    53. }
    54. #login_div span:nth-of-type(1) {
    55. position:absolute;
    56. left:20px;
    57. }
    58. #login_div span:nth-of-type(2) {
    59. position:absolute;
    60. right:20px;
    61. }
    62. input[type='submit'] {
    63. width:200px;
    64. height:50px;
    65. background-color:rgb(61,155,237);
    66. color:white;
    67. border:0px;
    68. font-size:24px;
    69. }
    70. #login_div li:nth-child(5) input[type='checkbox'] {
    71. position:absolute;
    72. left:25px;
    73. top:-10px;
    74. width:20px;
    75. height:20px;
    76. }
    77. #login_div li:nth-child(5) em {
    78. position:absolute;
    79. left:50px;
    80. top:-12px;
    81. font-size:16px;
    82. }
    83. #login_div li:nth-child(6) input[type='checkbox'] {
    84. position:absolute;
    85. left:25px;
    86. top:30px;
    87. width:20px;
    88. height:20px;
    89. }
    90. #login_div li:nth-child(6) em {
    91. position:absolute;
    92. left:50px;
    93. top:17px;
    94. font-size:16px;
    95. text-align:left;
    96. }

    js

    1. function change_password() {
    2. if (password_input.type == 'password') {
    3. // 第二个参数是闭上眼睛的icomoon
    4. eye_span.setAttribute('data-content-after','')
    5. password_input.type = 'text'
    6. }
    7. else {
    8. password_input.type = 'password'
    9. // 第二个参数是睁开眼睛的icomoon
    10. eye_span.setAttribute('data-content-after','')
    11. }
    12. }
    13. password_input = document.querySelector("input[name='password']")
    14. eye_span = document.querySelector('#login_div span:nth-of-type(2)')
    15. // 第二个参数是睁开眼睛的icomoon
    16. eye_span.setAttribute('data-content-after','')
    17. eye_span.onclick =change_password

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

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

    4.2  用户名已被注册

    html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>注册异常title>
    6. head>
    7. <body>
    8. <p>这个用户名已经被注册了,5秒后返回注册页p>
    9. body>
    10. <script>
    11. p = document.querySelector('p')
    12. i = 5
    13. setInterval(function() {
    14. i = i - 1
    15. p.innerHTML = '这个用户名已经被注册了,' + i +'秒后返回注册页'
    16. },1000)
    17. setTimeout(function() {location.href = '/user/register/'},5000)
    18. script>
    19. html>

    4.3  注册成功

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>注册成功title>
    6. head>
    7. <body>
    8. <p>注册成功,5秒后返回至登录页p>
    9. body>
    10. <script>
    11. p = document.querySelector('p')
    12. i = 5
    13. setInterval(function() {
    14. i = i - 1
    15. p.innerHTML = '注册成功,' + i +'秒后返回至登录页'
    16. },1000)
    17. setTimeout(function() {location.href = '/user/login/'},5000)
    18. script>
    19. html>

    4.4  登录账户

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

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

    html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>登录页title>
    6. <link rel="stylesheet" href="/static/css/initialization.css">
    7. <link rel="stylesheet" href="/static/css/login.css">
    8. head>
    9. <body>
    10. <img src="/static/pic/login_background.png" alt="">
    11. <div id="login_div">
    12. <form action="/user/login/" method="post">
    13. <ul>
    14. <li>用户登录li>
    15. <li><span>span><input type="text" name="username" class="content_input" maxlength="8">li>
    16. <li><span>span><input type="password" name="password" class="content_input" maxlength="8"><span>span>li>
    17. <li><input type="submit" value="登   录">li>
    18. <li><input type="checkbox" name="remember_me"><em>记住我em>li>
    19. ul>
    20. form>
    21. div>
    22. body>
    23. <script src="/static/js/login.js">script>
    24. html>

    css

    1. img {
    2. position:fixed;
    3. width:100%;
    4. height:100%;
    5. z-index:-100;
    6. }
    7. .content_input {
    8. width:200px;
    9. height:30px;
    10. text-indent:30px;
    11. outline:none;
    12. }
    13. span {
    14. display:inline-block;
    15. width:20px;
    16. height:20px;
    17. }
    18. .content_input:focus {
    19. border:3px rgb(107,157,188) solid;
    20. }
    21. #login_div {
    22. position:absolute;
    23. right:350px;
    24. top:50%;
    25. width:225px;
    26. height:285px;
    27. // background-color:white;
    28. margin-top:-150px;
    29. }
    30. #login_div li {
    31. position:relative;
    32. margin-top:20px;
    33. text-align:center;
    34. }
    35. #login_div li:nth-child(1) {
    36. color:rgb(82,152,197);
    37. font-size:30px;
    38. }
    39. #login_div li:nth-of-type(2) span:before {
    40. content:'\e971';
    41. font-family:'icomoon';
    42. font-size:20px;
    43. }
    44. #login_div li:nth-of-type(3) span:nth-of-type(1):before {
    45. content:'\e98d';
    46. font-family:'icomoon';
    47. font-size:20px;
    48. }
    49. #login_div li:nth-of-type(3) span:nth-of-type(2):before {
    50. content:attr(data-content-after);
    51. font-family:'icomoon';
    52. font-size:20px;
    53. }
    54. #login_div span:nth-of-type(1) {
    55. position:absolute;
    56. left:20px;
    57. }
    58. #login_div span:nth-of-type(2) {
    59. position:absolute;
    60. right:20px;
    61. }
    62. input[type='submit'] {
    63. width:200px;
    64. height:50px;
    65. background-color:rgb(61,155,237);
    66. color:white;
    67. border:0px;
    68. font-size:24px;
    69. }
    70. #login_div li:nth-child(5) input[type='checkbox'] {
    71. position:absolute;
    72. left:25px;
    73. top:-10px;
    74. width:20px;
    75. height:20px;
    76. }
    77. #login_div li:nth-child(5) em {
    78. position:absolute;
    79. left:50px;
    80. top:-12px;
    81. font-size:16px;
    82. }
    83. #login_div li:nth-child(6) input[type='checkbox'] {
    84. position:absolute;
    85. left:25px;
    86. top:30px;
    87. width:20px;
    88. height:20px;
    89. }
    90. #login_div li:nth-child(6) em {
    91. position:absolute;
    92. left:50px;
    93. top:17px;
    94. font-size:16px;
    95. text-align:left;
    96. }

    js

    1. function change_password() {
    2. if (password_input.type == 'password') {
    3. // 第二个参数是闭上眼睛的icomoon
    4. eye_span.setAttribute('data-content-after','')
    5. password_input.type = 'text'
    6. }
    7. else {
    8. password_input.type = 'password'
    9. // 第二个参数是睁开眼睛的icomoon
    10. eye_span.setAttribute('data-content-after','')
    11. }
    12. }
    13. password_input = document.querySelector("input[name='password']")
    14. eye_span = document.querySelector('#login_div span:nth-of-type(2)')
    15. // 第二个参数是睁开眼睛的icomoon
    16. eye_span.setAttribute('data-content-after','')
    17. eye_span.onclick =change_password

    视图

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

    4.5  登录异常

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>登录异常title>
    6. head>
    7. <body>
    8. <p>你输入的账户名或密码错误,5秒后返回登录页p>
    9. body>
    10. <script>
    11. p = document.querySelector('p')
    12. i = 5
    13. setInterval(function() {
    14. i = i - 1
    15. p.innerHTML = '你输入的账户名或密码错误,' + i +'秒后返回登录页'
    16. },1000)
    17. setTimeout(function() {location.href = '/user/login/'},5000)
    18. script>
    19. html>

    5  笔记部分

    笔记部分一共有五个路由

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

    5.1  笔记列表

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

    html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Titletitle>
    6. head>
    7. <style>
    8. table {
    9. width:1400px;
    10. text-align:center;
    11. }
    12. td {
    13. float:left;
    14. width:250px;
    15. height:35px;
    16. line-height:35px;
    17. border:1px solid black;
    18. margin-left:-1px;
    19. margin-top:-1px;
    20. }
    21. table thead tr:nth-child(1) td{
    22. background-color:rgb(220,220,220);
    23. }
    24. style>
    25. <body>
    26. {{current_user}}的笔记 <a href="/note/create_note/">添加新笔记a> <a href="/note/logout/">退出登录a>
    27. <table>
    28. <thead>
    29. <tr>
    30. <td>标题td>
    31. <td>创建时间td>
    32. <td>修改时间td>
    33. <td>操作td>
    34. tr>
    35. thead>
    36. <tbody>
    37. {% for note in user_note_data %}
    38. <tr>
    39. <td>{{note.title}}td>
    40. <td>{{note.created_time}}td>
    41. <td>{{note.update_time}}td>
    42. <td>
    43. <a href="/note/edit_note/?id={{note.id}}">修改a>
    44. <a href="/note/delete_note/?id={{note.id}}">删除a>
    45. td>
    46. tr>
    47. {% endfor %}
    48. tbody>
    49. table>
    50. body>
    51. html>

    视图

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

    5.2  添加笔记

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

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

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

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

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

    视图

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

    html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Titletitle>
    6. <link rel="stylesheet" href="/static/css/initialization.css">
    7. head>
    8. <body>
    9. <form action="/note/create_note/" method="post">
    10. 笔记标题<input type="text" name="title"><br>
    11. 笔记内容<textarea name="content" cols="30" rows="10">textarea><br>
    12. <input type="submit" value="提交">
    13. form>
    14. body>
    15. html>

    5.3  更新笔记

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

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

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

    视图

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

    html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Titletitle>
    6. <link rel="stylesheet" href="/static/css/initialization.css">
    7. head>
    8. <body>
    9. <form action="/note/edit_note/" method="post">
    10. 笔记标题<input type="text" name="title" value="{{title}}"><br>
    11. 笔记内容<textarea name="content" cols="30" rows="10">{{content}}textarea><br>
    12. <input type="submit" value="提交">
    13. form>
    14. body>
    15. 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正常则访问首页与登录页均会跳转到笔记列表页,如果不正常就当没有这个校验器

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

  • 相关阅读:
    Bugku MISC做题笔记
    前段导出XLSX表格
    从 DMAIC 方法论说起,记一个长链接 bug 的排查全过程
    Disco Diffusion 快速入门
    计算机毕业设计springboot+vue基本微信小程序的外卖点餐平台
    SpringMVC(3)——REST风格
    Android打包apk报错:Execution failed for task ‘:app:lintVitalRelease‘.
    使用低代码可视化开发平台快速搭建应用
    pytorch的axis的理解
    Centos7 安装Redis详细教程
  • 原文地址:https://blog.csdn.net/potato123232/article/details/126328141