zoukankan      html  css  js  c++  java
  • BBS(仿博客园系统)项目01(项目分析、表结构设计、注册功能实现)[转载]

    BBS(仿博客园系统)项目01(项目分析、表结构设计、注册功能实现)

     

    摘要:

    • 需求分析
    • 表结构设计
    • 注册功能实现

    一、需求分析:

    • 项目需求(产品经理、架构师、开发组组长与客户谈该项目相关要求)
    • 项目设计 (架构师需要思考:框架选择,数据库选择,主要功能模块,报价:包括工期、开发人员工资...)
    • 任务分发(开发组长开会,拆分项目各个任务,分发到各组)
    • 测试(自己测试/本地测试,测试员测试,白盒测试--开发人员、黑盒测试--测试人员)
    • 交付上线(运维配合上线)

    二、项目分析:

    表设计:

    1. 用户表(UserInfo)
      首先我们使用Django Web框架,所以用户表继承Django的auth_user表的结构,我们自定义创建UserInfo表,在原Django自动创建的auth_user表基础上

      补充添加一些字段:
      用户电话(phone)BigIntegerField类型
      用户头像(avatar)FileField类型(指定上传路径upload_to=‘’,默认头像文件default='avatar/xxx.jpg')
      用户创建时间(create_time) DateField类型(设置时间为新增记录时候自动设置为当前时间auto_now_add=True)
      一对一关系的blog字段(blog)OneToOneField(to='Blog', null=True)
      需要知道:用户user会有点赞点踩(UpAndDown)、评论(Comment)、站点(Blog)

    2. 个人站点表(Blog)
      站点名称(blog_name) CharField类型,建议长度32
      站点标题(blog_title)CharField类型,建议长度64
      站点样式主题(blog_theme) CharField类型,存储css文件路径,建议长度64
      需要知道:站点blog上会有标签(Tag)、分类(Category)、文章(Article)
    3. 文章标签表(Tag)
      标签名称(name)CharField类型,建议长度32
      一对多关系:Blog(一)>>>Tag(多):
      blog外键字段 ForeignKey(to='Blog',null=True)
    4. 文章分类表(Category)
      分类名称(name)CharField类型,建议长度32
      一对多关系:Blog(一)>>>Category(多):
      blog外键字段 ForeignKey(to='Blog',null=True)
    5. 文章表(Article)
      文章标题(title) CharField类型 建议长度64
      文章摘要(desc)CharField类型 建议长度255
      文章内容(content)TextField类型
      文章发布时间(create_time) DateField类型(设置时间为新增记录时候自动设置为当前时间auto_now_add=True)
      # 优化查询创建的字段:
      评论数(comment_num)IntegerField类型
      点赞数(up_num)IntegerField类型
      踩数(down_num)IntegerField类型
      # Article表与个人站点Blog、标签Tag、分类Category都有关联:
      一对多关系:
      Blog(一)>>>Article(多):blog外键字段 ForeignKey(to='Blog',null=True)
      Category(一)>>>Article(多):category外键字段 ForeignKey(to='Category',null=True)
      多对多关系:(这里我们手动创建第三张多对多关联表Article2Tag)
      Tag >>> Article :tag多对多外键字段 :ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag'))
    6. 点赞点踩表(UpAndDown)
      是否点赞(is_up)BooleanField类型,0代表点踩,1代表点赞
      点赞点踩会与用户表UserInfo、文章表Article关联:
      一对多关系:
      UserInfo(一)>>>UpAndDown(多):user外键字段 ForeignKey(to='UserInfo')  一个用户可以点多个赞和踩,一个踩或者赞只可能是一个用户点的
      Article(一)>>>UpAndDown(多):user外键字段 ForeignKey(to='Article')  一篇文章可以被点多个赞和踩,一个踩或者赞只可能对应在一片文章上
    7. 评论表(Comment)
      评论内容(content)CharField类型 建议长度255
      创建时间(create_time) DateField类型(设置时间为新增记录时候自动设置为当前时间auto_now_add=True)
      评论表会与用户表UserInfo、文章表Article关联:
      一对多关系:
      UserInfo(一)>>>Comment(多):user外键字段 ForeignKey(to='UserInfo')  一个用户可以发布多个评论,一个评论只可能是一个用户发布的
      Article(一)>>>Comment(多):user外键字段 ForeignKey(to='Article')  一篇文章可以有多个评论,一个评论只可能对应在一片文章上
      父评论(parent)ForeignKey类型 ForeignKey(to='self') 这个特殊,跟自己关联

      总结:BBS表直接的关系图:

     注册功能实现:

    1.  配置环境
      setting.py文件
      1、设置静态文件
      2、告诉Django,自定义创建用户表
      3、使用MySQL数据库,配置数据库参数
      STATIC_URL = '/static/'
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, 'static'),
          os.path.join(BASE_DIR, 'app01/static')
      ]
      
      AUTH_USER_MODEL = 'app01.UserInfo'
      
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'bbs01',
              'HOST': '127.0.0.1',
              'PORT': 3306,
              'USER': 'root',
              'PASSWORD': '123'
          }
      }  
      # 注意在init文件中写入:

        import pymysql
        pymysql.install_as_MySQLdb()

    2. 配置Django的form表单相关数据:
      app01文件夹下新建文件夹myform,接着在此文件下新建myform.py文件,此py文件中:
      from django import forms
      from app01 import models
      
      # 创建Django的form表单
      class MyForm(forms.Form):
          username = forms.CharField(
              max_length=8,
              min_length=3,
              label='用户名:',
              error_messages={
                  'required': '用户名不能为空',
                  'max_length': '用户名最大为8位',
                  'min_length': '用户名最小3位',
              },
              widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
          )
      
          password = forms.CharField(
              max_length=8,
              min_length=3,
              label='密码:',
              error_messages={
                  'required': '密码不能为空',
                  'max_length': '密码最大8位',
                  'min_length': '密码最小3位',
              },
              widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
          )
      
          confirm_password = forms.CharField(
              max_length=8,
              min_length=3,
              label='确认密码:',
              error_messages={
                  'required': '确认密码不能为空',
                  'max_length': '确认密码最大8位',
                  'min_length': '确认密码最小3位',
              },
              widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
          )
      
          email = forms.EmailField(
              label='邮箱:',
              error_messages={
                  'required': '邮箱不能为空',
                  'invalid': '邮箱格式错误'
              },
              widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
          )
      
          # 使用局部钩子和全局钩子对用户名和密码进行二次校验:
          # 全局钩子校验密码输入是否一致:
          def clean(self):
              password = self.cleaned_data.get('password')
              confirm_password = self.cleaned_data.get('confirm_password')
              if password != confirm_password:
                  self.add_error('confirm_password', '输入两次密码不一致')
              return self.cleaned_data  # 记得返回
      
          # 局部钩子校验用户名是否存在:
          def clean_username(self):
              username = self.cleaned_data.get('username')
              user_obj = models.UserInfo.objects.filter(username=username).first()
              if user_obj:
                  self.add_error('username', '用户名已存在')
              return username   # 记得返回
    3. 项目app01中models.py文件中创建Django的orm映射表关系模型数据:
      models.py中,创建表(前提是需要先创建一个数据库(bbs01))
      from django.db import models
      from django.contrib.auth.models import AbstractUser
      
      # Create your models here.
      class UserInfo(AbstractUser):
          phone = models.BigIntegerField(null=True)
          create_time = models.DateField(auto_now_add=True)
          blog = models.OneToOneField(to='Blog', null=True)
          avatar = models.FileField(upload_to='avatar/', default='avatar/default_avatar.jpg')
      
      
      class Blog(models.Model):
          blog_name = models.CharField(max_length=32)
          blog_title = models.CharField(max_length=64)
          blog_theme = models.CharField(max_length=64)
      
      
      class Article(models.Model):
          title = models.CharField(max_length=64)
          content = models.TextField()
          desc = models.CharField(max_length=255)
          create_time = models.DateField(auto_now_add=True)
          blog = models.ForeignKey(to='Blog', null=True)
          category = models.ForeignKey(to='Category', null=True)
          tag = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag'))
          # 优化查询
          comment_num = models.IntegerField()
          up_num = models.IntegerField()
          down_num = models.IntegerField()
      
      class Category(models.Model):
          name = models.CharField(max_length=32)
          blog = models.ForeignKey(to='Blog', null=True)
      
      class Article2Tag(models.Model):
          article = models.ForeignKey(to='Article')
          tag = models.ForeignKey(to='Tag')
      
      class Tag(models.Model):
          name = models.CharField(max_length=32)
          blog = models.ForeignKey(to='Blog', null=True)
      
      class UpAndDown(models.Model):
          is_up = models.BooleanField()
          user = models.ForeignKey(to='UserInfo')
          article = models.ForeignKey(to='Article')
      
      class Comment(models.Model):
          content = models.CharField(max_length=255)
          create_time = models.DateField(auto_now_add=True)
          user = models.ForeignKey(to='UserInfo')
          article = models.ForeignKey(to='Article')
          parent = models.ForeignKey(to='self', null=True)

      注意:记得运行数据库迁移命令makemigrations>>>migrater 使创建的表生效
    4. 配置路由,视图函数、前端register.html创建
      ## urls.py中:
      from django.conf.urls import url
      from django.contrib import admin
      from app01 import views
      
      urlpatterns = [
          url(r'^admin/', admin.site.urls),
          url(r'^register/', views.register)
      ]
      ## register.html文件创建:
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      {#    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>#}
          <script src="/static/jQuery-3.4.1.js"></script>
          <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
          <link rel="stylesheet" href="/static/reg.css">
          <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
      </head>
      <body>
      <div class="container-fluid">
          <div class="row">
              <div class="col-md-6 col-md-offset-3">
                  <h2 class="text-center">注册页面</h2>
                  <hr>
      {#          注意:这里使用form表单的目的在于我们需要通过form表单批量处理表单内输入的数据对象#}
                  <form id="myform">
                      {% csrf_token %}
                      {% for form in form_obj %}
      {#                  通过在input框外套个div,设置div的class包含form-group,#}
      {#                  就可以通过bootstrap的样式来调整input框之间的间距#}
                          <div class="form-group">
                              <label for="{{ form.auto_id }}">{{ form.label }}</label>
                              {{ form }}
                              <span class="has-error pull-right"></span>
                          </div>
                      {% endfor %}
                  </form>
                      <div class="form-group">
                          <label for="id_myfile">
                              <img id="id_avatar" src="/static/avatar/default_avatar.jpg" width="70" class="img-thumbnail">
                          </label>
                          点击图片上传头像
                          <input type="file" id="id_myfile" name="myfile" class="hide">
                      </div>
                      <button class="btn btn-primary pull-right" id="id_submit">提交注册</button>
              </div>
          </div>
      </div>
      
      <script>
          // 利用文件阅读器动态展示用户上传的图像
          $('#id_myfile').on('change',function () {
              // 获取上传文件对象
              let file_obj = this.files[0];
              // 生成一个文件阅读器内置对象
              let fileReader = new FileReader();
              // 把文件对象传给文件阅读器对象
              fileReader.readAsDataURL(file_obj);
              // 将读取出的文件对象替换到img标签
              // 因为渲染图片的速度远比文件阅读器读取文件的速度
              // 所以,这里需要用onload方法等待文件阅读器读取完毕之后在渲染图片,图片才会显示
              fileReader.onload = function () {
                  $('#id_avatar').attr('src', fileReader.result)
                  // img标签src属性可放:文件路径、文件二进制数据、url
              }
          });
          // 通过ajax发送请求,提交数据
          $('#id_submit').on('click',function () {
              // ajax无法传自己传文件,需要借助内置对象FormData传文件
              // 声明一个formData对象,formData对象既能传文件又可以传普通键值,通过append的方式添加
              let formData = new FormData();
              // jQuery对象方法each方法:
              // 将通过$('#myform').serializeArray()方法得到的json格式的对象数组遍历它每个键值对
              // 把遍历出来的每个键值对通过函数处理,function()中的index是遍历出来的键值对加的索引,
              // obj就是遍历出来的键值对(也就是这里form表单里面input框的name和填写的value)
              $.each(
                  // 通过serializeArray() 方法序列化form表单值来创建对象数组(名称和值),该方法返回的是个json对象
                  // 返回的 JSON 对象是由一个对象数组组成的,其中每个对象包含键值对 name和value
                  $('#myform').serializeArray(),
                  function (index, obj) {formData.append(obj.name,obj.value);}
                  // 1 {name: "username", value: "sgt"} 对应的index 和 obj
                  ); // function函数内将遍历出来的json数据通过点name和点value得到对应键值添加进formData中
      
              // 普通键值(input框填入的键值)添加完毕,接下来添加文件数据到formData中
              formData.append('myfile', $('#id_myfile')[0].files[0]);
              $.ajax({
                  url: '', // 不写默认当前页面路由提交
                  type: 'post', // post提交方式
                  data: formData, // 传数据和文件就得借助内置对象formData发送请求
                  processData: false, // 告诉浏览器不对数据做任何处理
                  contentType: false, // 不进行任何编码,formData数据对象Django后端能够识别
                  // 接下来
                  success:function (data) {
                      // 注册成功,跳转到后端传来的指定页面
                      if (data.code==100) {location.replace(data.url)}
                      // 注册失败,将后端传来的校验不成功的提示信息进行处理渲染到前端
                      // 这里需要注意一点:Django的form表单在前端渲染的时候会将每个input框的id名按规律
                      // 定义名字,比如像这样:id_username、id_password
                      else {
                          $.each(
                              data.msg,
                              function (index, obj) {
                                  // 注意:index obj 分别是:username ["用户名最小3位"]
                                  // 和上面的方法不一样,这个是将字典使用each方法,上面是将数组使用each方法
                                  let targetId = '#id_'+index;  // 拼接出input框的id名
                                  // 遍历出来的每个字段名字,通过上面拼接的对应input框对应的id名,通过这个
                                  // id名字就能找到它,它下面(通过next()方法)就是我们需要处理的span标签
                                  // 将它添加html文本(就是错误提示信息,注意obj是个列表),同时继续链式操作
                                  // 让错误的input框标红框,警告提示,通过parent()方法找到父标签div,将其
                                  // class加入一个has-error即可达到目的
                                  $(targetId).next().html(obj[0]).parent().addClass('has-error')
                              }
                          )
                      }
                  }
      
              })
          });
          // 上面通过ajax发送post请求进行注册过程基本全部完成,最后还实现了错误信息的渲染,此时还需进行完善一下
          // 用户如果注册一次提示错误,继续进行注册的话,就需要再次清空错误提示,所以:
          $('input').on('focus',function () {
              $(this).next().html('').parent().removeClass('has-error')
              // 方法同上面加入错误信息类似,也是找到input框下面的span标签,将她的html置空,就去掉了红色提示
              // 同时继续找到其父标签div去掉class里面的has-error,红框提示取消
          })
      </script>
      </body>
      </html>
      ## views.py视图函数
      from django.shortcuts import render,HttpResponse,redirect
      from app01.myform.myform import MyForm
      from app01 import models
      from django.http import JsonResponse
      
      # Create your views here.
      def register(request):
          form_obj = MyForm()
          # 先定义一个响应字典,后面注册请求来了返回时候要使用
          back_dic = {'code': 100, 'msg': ''}
          if request.method == 'POST':
              # 将register页面提交的普通数据获取到丢给MyForm(),实例化一个form_obj
              form_obj = MyForm(request.POST)
              # 通过form_obj得到Django自动校验提交表单处理结果
              if form_obj.is_valid():
                  # 校验通过,获取到通过的所有键值数据字典
                  data = form_obj.cleaned_data
                  # 这里需要注意,字典里面有个confirm_password的键值,这个键值在校验通过后是不需要的
                  # 所以需要去掉:
                  data.pop('confirm_password')
                  # 获取用户上传的头像文件对象
                  file_obj = request.FILES.get('myfile')
                  # 这里需要加一层判断,看看用户在前端是否上传了头像文件,还是用的默认头像
                  # 传了就添加进data,没传就直接新增数据
                  if file_obj:
                      # 用户上传了头像文件,往data里添加头像文件的键值
                      data['avatar'] = file_obj
                      # 注意要用create_user才能正常创建用户数据
                      # data是字典形式的键值对,直接打散传入,perfect!
                  models.UserInfo.objects.create_user(**data)
                  back_dic['msg'] = '注册成功'
                  # 在相应数据字典里添加注册成功后跳转的页面路由
                  back_dic['urs'] = '/login/'
              # 校验不通过,注册不成功
              else:
                  back_dic['code'] = 101  # 用101代表注册失败
                  # 将校验失败的信息字典传进去,等待前端渲染提示用户注册失败
                  back_dic['msg'] = form_obj.errors
              return JsonResponse(back_dic)
          return render(request, 'register.html', locals())
  • 相关阅读:
    oracle数据比对工具
    一条update语句优化小记
    执行计划生成及查看的几种方法
    使用Grep命令查找 UTF-16的文本的注意事项
    命令行下更好显示 mysql 查询结果
    Zabbix通过SNMP监控多核CPU Load时,使用外部检查计算CPU Load的平均值。
    Hyper-V Cluster Clustered Role and Resource Properties and Live migration setting
    Python自动登录PRTG各节点,截取整个网页保存为图片
    添加Hpyer-V内存使用情况监控
    在Zabbix上添加Win DHCP Scope的监控
  • 原文地址:https://www.cnblogs.com/dongxixi/p/11062339.html
Copyright © 2011-2022 走看看