BBS01--项目开发实际流程、表设计及创建、注册和登录

1 项目开发流程

# 1.需求分析
    架构师+产品经理+开发者组长
  	在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好写方案
  	在跟客户沟通交流中引导客户往我们之前想好的方案上面靠
  	形成一个初步的方案
 

# 2.项目设计
  架构师干的活
    编程语言选择
    框架选择
    数据库选择
    	主库:MySQL,postgreSQL,...
      	缓存数据库:redis、mongodb、memcache...
    功能划分
    	将整个项目划分成几个功能模块
    找组长开会
    	给每个组分发任务
   	项目报价
    	技术这块需要多少人,多少天(一个程序员一天1500~2000计算(大致))
      	产品经理公司层面 再加点钱
            公司财务签字确认
            公司老板签字确认
      	产品经理去跟客户沟通
     	
      	后续需要加功能 继续加钱

# 3.分组开发
    组长找组员开会,安排各自功能模块
    我们其实就是在架构师设计好的框架里面填写代码而已(码畜)
  
    我们在写代码的时候 写完需要自己先测试是否有bug  
    如果是一些显而易见的bug,你没有避免而是直接交给了测试部门测出来 那你可能就需要被扣绩效了
    (一定要跟测试小姐姐搞好关系)
    
    薪资组成	15K(合理合规合法的避税)
        底薪	10K
        绩效	3K
        岗位津贴 1K
        生活补贴 1K
      
# 4.测试
    测试部门测试你的代码
    压力测试
    ...
    
# 5.交付上线
    1.交给对方的运维人员
    2.直接上线到我们的服务器上 收取维护费用
    3.其他...

2 BBS全栈项目

0 一般步骤

数据表设计
forms组件代码书写
注册页面搭建
用户头像实时展示
注册功能实现
登录页面搭建(图片验证码)
登录功能实现

1 表设计

# 一个项目中最最最重要的不是业务逻辑的书写
  而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺

    
# bbs表设计
1.用户表
    继承AbstractUser
    扩展
        phone 电话号码
        avatar  用户头像
        create_time  创建时间

    外键字段
        一对一个人站点表

        
2.个人站点表
    site_name    站点名称
    site_title 	 站点标题
    site_theme	 站点样式

    
3.文章标签表
    name	标签名

    外键字段
        一对多个人站点

4.文章分类表
    name	分类名

    外键字段
        一对多个人站点

5.文章表
    title	    文章标题
    desc	    文章简介
    content	    文章内容
    create_time 发布时间

    数据库字段设计优化(******)
        (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)
      up_num			点赞数
      down_num			点踩数
      comment_num 		评论数

    外键字段
        一对多个人站点
        多对多文章标签
        一对多文章分类


6.点赞点踩表
    记录哪个用户给哪篇文章点了赞还是点了踩
    user		ForeignKey(to="User")				
    article		ForeignKey(to="Article")	
    is_up		BooleanField()

    1		1		1
    1		2		1
    1		3		0
    2		1		1


7.文章评论表
    记录哪个用户给哪篇文章写了哪些评论内容
    user			ForeignKey(to="User")				
    article			ForeignKey(to="Article")
    content			CharField()
    comment_time	        DateField()
    # 自关联
    parent			ForeignKey(to="Comment",null=True)		
    # ORM专门提供的自关联写法	
    parent			ForeignKey(to="self",null=True)

    id	  user_id	article_id	parent_id
    1	     1		    1		
    2	     2		    1		   1					
		
# 根评论子评论的概念
    根评论就是直接评论当前发布的内容的
		
    子评论是评论别人的评论
        1.PHP是世界上最牛逼的语言
            1.1 python才是最牛逼的
            1.2 java才是
		
    根评论与子评论是一对多的关系

2 数据库表创建及同步

from django.db import models

# Create your models here.
"""
先写普通字段
之后再写外键字段
"""
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    phone = models.BigIntegerField(verbose_name='手机号',null=True)
    # 头像
    avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像')
    """
    给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog',null=True)


class Blog(models.Model):
    site_name = models.CharField(verbose_name='站点名称',max_length=32)
    site_title = models.CharField(verbose_name='站点标题',max_length=32)
    # 简单模拟 带你认识样式内部原理的操作
    site_theme = models.CharField(verbose_name='站点样式',max_length=64)  # 存css/js的文件路径


class Category(models.Model):
    name = models.CharField(verbose_name='文章分类',max_length=32)
    blog = models.ForeignKey(to='Blog',null=True)


class Tag(models.Model):
    name = models.CharField(verbose_name='文章标签',max_length=32)
    blog = models.ForeignKey(to='Blog', null=True)


class Article(models.Model):
    title = models.CharField(verbose_name='文章标题',max_length=64)
    desc = models.CharField(verbose_name='文章简介',max_length=255)
    # 文章内容有很多 一般情况下都是使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateField(auto_now_add=True)

    # 数据库字段设计优化
    up_num = models.BigIntegerField(verbose_name='点赞数',default=0)
    down_num = models.BigIntegerField(verbose_name='点踩数',default=0)
    comment_num = models.BigIntegerField(verbose_name='评论数',default=0)

    # 外键字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category',null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article','tag')
                                  )


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值 存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(verbose_name='评论内容',max_length=255)
    comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)
    # 自关联
    parent = models.ForeignKey(to='self',null=True)  # 有些评论就是根评论

3 注册功能

# 1.书写了一个注册需要的forms组件
    规律:不同的功能代码应该接耦合单独存储
        1.只有一个forms组件,那么可以直接创建一个py文件
        2.有多个forms组件,你可以创建文件夹,内部根据功能的不同创建不同的py文件

# 2.利用forms组件渲染前端标签
    1.我们不利用form表单提交 而是用ajax提交
    2.但是我们需要用到form标签来包含我们所有的获取用户数据的html代码
        $('#form').serializeArray()    获取到form标签内所有用户普通键值对的数据   [{},{},{},{}]
				
# 3.手动渲染获取用户头像的标签
    <label for="myfile">头像
    	{% load static %}
    	<img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px">
    </label>
    <input type="file" id="myfile" name="avatar" style="display: none" >
	
    只要是label里面的内容点击都会跳转到for指定的标签上

    
# 4.如何实时展示用户头像
    1.利用到了文件阅读器
    2.change事件
    3.onload等待加载完毕

    
# 5.一旦用户信息不合法如何精确的渲染提示信息
    1.forms组件渲染的标签id值都有一个固定的特点
        id_字段名
        ps:如何获取id值呢	form.auto_id
            <label for="{{ form.auto_id }}">{{ form.label }}</label>
    2.根据后端返回的字段以及字段对应的报错信息
        自己手动拼接对应字段的id值
    3.提示功能的完善
        1.jQuery的链式操作
        2.input获取焦点事件

3.1 注册forms

"""
我们之前是直接在views.py中书写的forms组件代码
但是为了接耦合 应该将所有的forms组件代码单独写到一个地方

如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
    myforms.py
但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据
forms组件功能的不同创建不同的py文件
    myforms文件夹
        regform.py
        loginform.py
        userform.py
        orderform.py
        ...
"""

# myforms.py
from django import forms
from app01 import models


class MyRegForm(forms.Form):
    username = forms.CharField(label='用户名', max_length=8, min_length=3,
                               error_messages={
                                   'max_length': '字符最多不能超过8位',
                                   'min_length': '字符最少不能少于3位',
                                   'required': '用户名不能为空',
                               },
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入用户名"})
                               )

    password = forms.CharField(label='用户密码', max_length=8, min_length=3,
                               error_messages={
                                   'max_length': '密码最多不能超过8位',
                                   'min_length': '密码最少不能少于3位',
                                   'required': '密码不能为空',
                               },
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'placeholder': "请输入密码"})
                               )

    confirm_password = forms.CharField(label='确认密码', max_length=8, min_length=3,
                                       error_messages={
                                           'max_length': '确认密码最多不能超过8位',
                                           'min_length': '确认密码最少不能少于3位',
                                           'required': '确认密码不能为空',
                                       },
                                       widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'placeholder': "请重复密码"})
                                       )
    email = forms.EmailField(label='用户邮箱',
                             error_messages={
                                 'required': '邮箱不能为空',
                                 'invalid': '邮箱格式不正确',
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control', 'placeholder': "请输入邮箱 格式: xxx@xxx.com "})
                             )

    # 钩子函数
    # 局部钩子:校验用户名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        is_exist = models.UserInfo.objects.filter(username=username)
        if is_exist:
            self.add_error('username', '用户已经存在')
        return username

    # 全局钩子:检验两次密码是否一致
    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

3.2 注册逻辑代码

##### views.py
from django.shortcuts import render,HttpResponse,redirect
from app01 import myforms
from app01 import models
from django.http import JsonResponse


def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        back_dic = {"code": 1000, 'msg': ''}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        # 判断数据是否合法
        if form_obj.is_valid():
            # print(form_obj.cleaned_data)  # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            # 将字典里面的confirm_password键值对删除
            clean_data.pop('confirm_password')  # {'username': 'jason', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get('avatar')
            """针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 直接操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())



##### register.html
<script>
    $("#myfile").change(function () {
        // 文件阅读器对象
        // 1 先生成一个文件阅读器对象
        let myFileReaderObj = new FileReader();
        // 2 获取用户上传的头像文件
        let fileObj = $(this)[0].files[0];
        // 3 将文件对象交给阅读器对象读取
        myFileReaderObj.readAsDataURL(fileObj)  // 异步操作  IO操作
        // 4 利用文件阅读器将文件展示到前端页面  修改src属性
        // 等待文件阅读器加载完毕之后再执行
        myFileReaderObj.onload = function(){
             $('#myimg').attr('src',myFileReaderObj.result)
        }
    })

    $('#id_commit').click(function () {
        // 发送ajax请求     我们发送的数据中即包含普通的键值也包含文件
        let formDataObj = new FormData();
        // 1.添加普通的键值对
        {#console.log($('#myform').serializeArray())  // [{},{},{},{},{}]  只包含普通键值对#}
        $.each($('#myform').serializeArray(),function (index,obj) {
            {#console.log(index,obj)#}  // obj = {}
            formDataObj.append(obj.name,obj.value)
        });
        // 2.添加文件数据
        formDataObj.append('avatar',$('#myfile')[0].files[0]);

        // 3.发送ajax请求
        $.ajax({
            url:"",
            type:'post',
            data:formDataObj,

            // 需要指定两个关键性的参数
            contentType:false,
            processData:false,

            success:function (args) {
                if (args.code==1000){
                    // 跳转到登陆页面
                    window.location.href = args.url
                }else{
                    // 如何将对应的错误提示展示到对应的input框下面
                    // forms组件渲染的标签的id值都是 id_字段名
                    $.each(args.msg,function (index,obj) {
                        {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                        let targetId = '#id_' + index;
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    })
    // 给所有的input框绑定获取焦点事件
    $('input').focus(function () {
        // 将input下面的span标签和input外面的div标签修改内容及属性
        $(this).next().text('').parent().removeClass('has-error')
    })
</script>
              
# 扩展
  一般情况下我们在存储用户文件的时候为了避免文件名冲突的情况
  会自己给文件名加一个前缀	
	uuid
	随机字符串
	...

4 登陆功能

# 1.自己手动搭建获取用户用户名 密码 验证码前端标签代码

# 2.图片验证码如何自己完成
    ps:
        img标签src属性后面可以写的内容
            1.直接写网络图片地址
            2.仅仅只是一个url后缀(自动朝该url发送get请求获取数据)
            3.图片二进制数据
	
    1.需要借助于pillow模块
        Image,ImageDraw,ImageFont
    2.需要借助于内存管理器io模块
	BytesIo,StringIO
    3.字体样式其实是受.ttf结尾的文件控制的
    4.手动产生随机验证码(搜狗公司的笔试题)
        random模块
        chr内置方法
        在session中保存验证码

4.1 验证码

"""
img标签的src属性
    1.图片路径
    2.url
    3.图片的二进制数据

我们的计算机上面致所有能够输出各式各样的字体样式
内部其实对应的是一个个.ttf结尾的文件

http://www.zhaozi.cn/ai/2019/fontlist.php?ph=1&classid=32&softsq=%E5%85%8D%E8%B4%B9%E5%95%86%E7%94%A8
"""


"""
图片相关的模块
    pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont
"""
Image:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
"""

from io import BytesIO,StringIO
"""
内存管理器模块
BytesIO:临时帮你存储数据 返回的时候数据是二进制
StringIO:临时帮你存储数据 返回的时候数据是字符串
"""


##### views.py

import random


def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)

def get_code(request):
    # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
    # with open(r'static/img/111.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤2:利用pillow模块动态产生图片
    # img_obj = Image.new('RGB',(430,35),'green')
    # img_obj = Image.new('RGB',(430,35),get_random())
    # # 先将图片对象保存起来
    # with open('xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # # 再将图片对象读取出来
    # with open('xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤3:文件存储繁琐IO操作效率低  借助于内存管理器模块
    # img_obj = Image.new('RGB', (430, 35), get_random())
    # io_obj = BytesIO()  # 生成一个内存管理器对象  你可以看成是文件句柄
    # img_obj.save(io_obj,'png')
    # return HttpResponse(io_obj.getvalue())  # 从内存管理器中读取二进制的图片数据返回给前端


    # 最终步骤4:写图片验证码
    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字体样式 大小

    # 随机验证码  五位数的随机验证码  数字 小写字母 大写字母
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65,90))
        random_lower = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 从上面三个里面随机选择一个
        tmp = random.choice([random_lower,random_upper,random_int])
        # 将产生的随机字符串写入到图片上
        """
        为什么一个个写而不是生成好了之后再写
        因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
        间隙就没法控制了
        """
        img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
        # 拼接随机字符串
        code += tmp
    print(code)
    # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())
  
  
##### login.html
  <script>
    $("#id_img").click(function () {
        // 1 先获取标签之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src',oldVal += '?')
    })
</script>

4.2 登录、注销、修改密码

def login(request):
    if request.method == 'POST':
        back_dic = {'code': 1000, 'msg': ''}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        if code.lower() == request.session.get('code').lower():
            user_obj = auth.authenticate(request, username=username, password=password)
            if user_obj:
                auth.login(request, user_obj)
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 1001
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 1002
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)
    return render(request, 'login.html')



@login_required
def set_password(request):
    if request.method == 'POST':
        back_dic = {'code': 1000, 'msg': ''}
        username = request.user.username
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')

        if confirm_password != new_password:
            back_dic['code'] = 1001
            back_dic['msg'] = '两次密码不一致'
        else:
            # 校验原密码
            is_right = request.user.check_password(old_password)
            if is_right:
                request.user.set_password(new_password)
                request.user.save()
                auth.login(request, request.user)
                back_dic['msg'] = '修改成功'
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '原密码错误'
        return JsonResponse(back_dic)


def logout(request):
    auth.logout(request)
    return redirect('/home/')
posted @ 2022-07-28 09:20  Edmond辉仔  阅读(149)  评论(0编辑  收藏  举报