曹阳的博客 仅用于学习和分享

兄弟会官网项目总结

2019-12-21

Django兄弟会官网项目总结

项目简介

项目描述

  1. 项目名称:兄弟会官网管理系统
  2. 架构:三端分离MVT
  3. 成果:兄弟会官网页面和后台管理全部功能
  4. 开发周期:2019/12/21-2020/01/04
  5. 收获:知道啥是接口,啥是前后端分离了,明白怎么写接口,接收和发送接口数据了,知道vue不用脚手架怎么使用,怎么和CSS框架Django框架结合了,Django框架的运用更熟练了,用git小组合作了,会项目上线了

项目技术

  1. Python3.7
  2. Django2.2
  3. vue2.9.6(js框架)
  4. Materialize(css框架)
  5. MySQL5.7

包文件

(venv) D:\OngoingProjects\xdh_vue_django>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

Django              2.2.9  
django-cors-headers 3.2.0  
mysqlclient         1.4.6  
pip                 19.0.3  
pytz                2019.3  
setuptools          40.8.0  
sqlparse            0.3.0  

项目分工

小明:需求分析,数据库设计,前后台绝大多数接口,后台页面增删改查(blog)
齐健:需求分析,关于我们,课程介绍,后台页面增删查(banner),登录页面,配置路由,合项目,项目上线
陈赞:需求分析,首页,常见问题,后台增删改查页面(about,description,activities,stage,studenttestimonials,studentswork,teachersinformation,workinformation)
斌哥:需求分析,办学特色,技术社区,后台增删改查页面(banner,communityresources,indedx,schoolrelated,xyfc)
我:需求分析,学员风采,后台页面增删查(blog),文件上传接口,分页功能,多文件编辑接口,封导航组件,合项目,项目上线

需求分析

首页

轮播图  
兄弟会介绍  
    兄弟会的初衷-打造具备交付能力的程序员  
    兄弟会宣传介绍文案  
兄弟会与传统就业班区别  
    主要是教学模式的对比  
    要体现出兄弟会与传统就业班的优势  
师资力量  
    正常介绍即可  
就业喜报  
    如果有就写  
学科介绍  
    兄弟会4大学科基本介绍
    	Python
    	java
    	PHP
    	H5
    图文介绍  

课程介绍

培训课程,具体学科介绍  
兄弟会具体学科及分级介绍  
兄弟会解决了什么问题(需要牛逼的文案)  
重点突出兄弟会课程体系与传统培训体系的不同(需要牛逼的文案)  
学习的苦VS生活的苦(不吃学习的苦,就要吃生活的苦,文案牛逼一点)  

教学特色

办学理念-具有真实交付能力的程序员
项目驱动式项目(每阶段都有可锻炼的项目)  
企业级项目经理指导   
真实的企业级商业项目全程开发
学习经验分享
企业文化培训-培养身心健康的程序员  

学员风采

学员采访视频 
学员感言  
个人博客  
团建活动  
我们的作品  

常见问题

入学条件 
	分期贷款学员需要慎重考虑,因为需要还贷款
	编程基础
入学流程  
	正常流程
开班计划 
	兄弟连学校制定
学费标准 
	学校制定

就业喜报

尽量写真实的  

关于我们

兄弟连介绍  
兄弟会详细介绍(沈阳)
联系方式等  

公共部分

头部导航  
轮播图  
页脚
	友情链接  
	二维码
	联系电话

难点

  1. 下拉框表单传值
  2. 分页
  3. 路由
  4. 文件上传
  5. 多文件修改
  6. 模板自带的js和自定义js冲突
  7. 合项目
  8. 跨域问题
  9. 登录验证码

编码实现

三端分离

1. 后台页面:通过form表单发送post请求  
2. 后台接口:接收post请求,对数据库进行增删改查操作(给后台页面返回json状态码)  
3. 前台接口:从数据库中增删改查(给前台页面返回json数据)  
4. 前台页面:通过axios接收接口数据(就是访问路由),返回到页面中遍历出来  

后台页面端(xdh_admin_page)

查询

1. axios.get接收接口返回的数据
2. v-for遍历接到的接口数据
3. v-bind绑定属性(如图片`:src="item.link+item.img"`)
4. v-text或 { { } } 设置标签的内容

增加

1. v-bind绑定属性(如`:action="addUrl"`)
2. v-model双向数据绑定(如`v-model="link"`)
3. v-on绑定函数(如`@change="IT"`)
4. post方法form表单提交数据

接口端(xdh_vue_django)

后台接口(bgapi)

查询

def list(request):
    u_dict = {}
    try:
        u_dict = all_data(Blog)
        # 嵌套一层
        u_dict = success_response(u_dict)
    except:
        u_dict = error_response(u_dict, "服务器错误,查询异常!")
    return JsonResponse(u_dict)

def all_data(a_model_class, order="id"):
    # 先定义一个字典类型的返回内容
    u_list = {}
    # 要加的key
    i = 0
    # 查询模型
    objs = a_model_class.objects.order_by(order)
    # 遍历模型
    for obj in objs:
        # 转换成当前的查询集字典
        curr_data = model_to_dict(obj)
        # 判断有没有key是img的
        if "img" in curr_data.keys():
            # 要是有就拼settings里面的静态文件路径
            curr_data["img"] = settings.BASE_URL + curr_data["img"]
            # 头像图片
        if "avator_img" in curr_data.keys():
            curr_data["avator_img"] = settings.BASE_URL + curr_data["avator_img"]
        if "src" in curr_data.keys():
            curr_data["src"] = settings.BASE_URL + curr_data["src"]
        # 将处理后的当前数据赋值给字典
        u_list[i] = curr_data
        # 更新key的值
        i += 1
    # 返回数据
    return u_list

增加

def add(request):
    # 定义返回数据
    u_dict = {

    }
    # 判断请求方式
    if (request.method == 'POST'):
        try:
            # 开始处理图片
            # 1.获取表单内容
            data = request.POST.dict()
            print(data)
            # 2. 获取表单中的文件
            file = request.FILES.get('file', None)
            print("file",file)
            # 3. 执行保存文件,并返回文件路径
            # save_file ---> 自定义函数
            path = save_file(file)
            print("path:",path)
            # 构建img字段数据
            data["img"] = path
            # data.pop("file")
            # 保存数据
            Blog(**data).save()
            # 返回数据内容
            u_dict = {
                "msg": "ok",
                "code": "200"
            }
        except:
            u_dict = {
                "msg": "error",
                "code": "500"
            }
    if (request.method == 'GET'):
        u_dict = {
            "msg": "no",
            "code": "666"
        }
    return JsonResponse(u_dict)
	
def save_file(file):
    if file:
        # 上传了头像
        import hashlib
        # 待加密信息(随机数)
        ran_str = str(random.randint(0, 9999999))
        # 创建md5对象
        hl = hashlib.md5()
        to_md5_str = str(time.time())+ran_str
        # Tips
        # 此处必须声明encode
        # 若写法为hl.update(str)  报错为: Unicode-objects must be encoded before hashing
        hl.update(to_md5_str.encode(encoding='utf-8'))
        # 加密完的md5字符串
        md5_str = hl.hexdigest()
        # 文件扩展名
        file_extend_name = file.name.split('.').pop()
        # 拼接文件名
        filename = md5_str + '.' + file_extend_name

        with open(f'./static/uploads/' + filename, 'wb+') as f:
            f.write(file.read())
        # with open(f'./media/uploads/' + filename, 'wb+') as fp:
        #     fp.write(file.read())
        return filename
    else:
        return ""

删除

def delete(request):
    try:
        # 尝试删除
        id = request.GET.get("id")
        obj = Blog.objects.get(id=id)
        obj.delete()
        u_dict = {
            "msg": "ok",
            "code": "200"
        }
    except:
        u_dict = {
            "msg": "error",
            "code": "500"
        }
    return JsonResponse(u_dict)

修改

def edit(request):
    '''
    编辑视图函数
    :param request:
    :return:
    '''

    u_dict = {
    }
    if (request.method == 'POST'):

        # 获取修改的id
        data = request.POST.dict()
        id = data["id"]

        # 2. 获取表单中的文件
        src = request.FILES.get('src', None)
        img = request.FILES.get('img', None)
        path1 = save_file(src)
        path2 = save_file(img)

        print("path1:", path1)
        print("path2:", path2)

        obj = AssessVideo.objects.get(id=id)
        # 删除源文件
        # 获取文件路径
        dict = model_to_dict(obj)
        old_path1 = dict['src']
        old_path2 = dict['img']

        # 如果没有上传文件
        if path1:
            # 执行删除
            del_file(old_path1)
        else:
            path1 = old_path1

        if path2:
            del_file(old_path2)
        else:
            path2 = old_path2

        # 构建img字段数据
        data["src"] = path1
        data["img"] = path2

        # try:
        # 执行更新
        obj = AssessVideo.objects.filter(id=id)
        obj.update(**data)
        # 成功状态码
        u_dict = {
            "info": "ok",
            "status": "200"
        }
        # except:
        #     # 失败状态码
        #     u_dict = {
        #         "info": "error",
        #         "status": "500"
        #     }
    if (request.method == 'GET'):
        # 请求方式不对状态码
        u_dict = {
            "info": "no",
            "status": "666"
        }
    return JsonResponse(u_dict)

前台接口(api)

def blog(request):
	1. filter从数据库中查数据  
    objs = Blog.objects.filter()
    u_list = {}
    i = 0
	2. 然后遍历目的是变成单条数据能转化成字典(键是字段值是数据) 
    for obj in objs:
        curr_data = model_to_dict(obj)
        curr_data.pop("id")
        curr_data.pop("is_del")
        curr_data["img"] = settings.BASE_URL + curr_data["img"]
		3. 然后再循环给每个字典加上键(键是0123值是字典)形成双层字典  
        u_list[i] = curr_data
        i += 1
	4. 然后把字典转化成json返回  
    return JsonResponse(u_list)

前台页面端(xdh_front_page)

// 学员风采的js文件
var vm = new Vue({
	el: '#app',
	data: {
		baseUrl: "http://192.168.1.107:8000",
		img: "",
		studentsay: "",
		blog: "",
		works: "",
		video: "",
	},
	methods: {},
	created() {
		var that = this;
		//毕业学员评价
		axios.get(this.baseUrl + "/api/xyfc/video/")
			.then(function(response) {
				// console.log(response.data[0])
				that.video = response.data
			});
		//我们的作品
		axios.get(this.baseUrl + "/api/xyfc/works/")
			.then(function(response) {
				// console.log(response.data[0])
				that.works = response.data
			});
		//学员感言
		axios.get(this.baseUrl + "/api/xyfc/studentsay/")
			.then(function(response) {
				// console.log(response.data)
				that.studentsay = response.data
			});
		//个人博客
		axios.get(this.baseUrl + "/api/xyfc/blog/")
			.then(function(response) {
				// console.log(response.data[0])
				that.blog = response.data
			});
	},
})

导航组件

vue方法

<!-- App.vue -->
<template>
  <div id="app">
    <header-nav></header-nav>
  </div>
</template>
<script>
  import headerNav from '@/components/header-nav'
export default {
  name: 'App',
  components:{
    headerNav
  }
}
</script>
<style>
</style>
  
<!-- components/header-nav.vue -->
<template>
  <div class="header">
  	<!-- 顶部导航栏 -->
  	<div class="container">
  		<nav class="z-depth-0 transparent">
  			<div class="nav-wrapper">
  				<a href="javascript:;" class="brand-logo"></a>
  				<ul class="right hide-on-med-and-down">
                    <li><a href="./index.html">首页</a></li>
                    <li><a href="./decription.html">课程详情</a></li>
                    <li><a href="./xyfc.html">学员风采</a></li>
                    <li><a href="./community.html">技术社区</a></li>
                    <li><a href="./problem.html">常见问题</a></li>
                    <li><a href="./aboutUs.html">关于我们</a></li>
  				</ul>
  			</div>
  		</nav>
  	</div>
  </div>
</template>
<script>
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
  
<!-- router/index.js -->
import Vue from 'vue'
import Router from 'vue-router'
import '@/assets/css/common.css'
import '@/assets/css/materialize.css'
Vue.use(Router)
export default new Router({
  routes: []
})

js方法

<script type="text/javascript">
	$("#header").load("./header.html");
	$("#footer").load("./footer.html");
	dataType:"jsonp";
</script>

路由

index.html

<div id="app"></div>
<script src="assets/js/index.js"></script>

index.js

// 1.封组件
var banner = {
	template: `<div>banner图</div>`
};
var community = {
	template: `<div>社区资源</div>`
};

// 2.创建router对象
var router = new VueRouter({
    //配置路由对象
    routes:[
        //路由匹配的规则
        {
            path:"/banner",
            component:banner
        },
        {
            path:"/community",
            component:community,
        },
    ]
});
var App = {
	template: `
			<div>
				<div class="am-g tpl-g">
					<!-- 侧边导航栏 -->
					<div class="left-sidebar">
						<!-- 用户信息 -->
						<div class="tpl-sidebar-user-panel">
							<div class="tpl-user-panel-slide-toggleable">
								<div class="tpl-user-panel-profile-picture">
									<img src="assets/img/user04.png" alt="">
								</div>
								<span class="user-panel-logged-in-text">
									<i class="am-icon-circle-o am-text-success tpl-user-panel-status-icon"></i>禁言小张
								</span>
								<a href="javascript:;" class="tpl-user-panel-action-link">
									<span class="am-icon-pencil"></span>账号设置
								</a>
							</div>
						</div>
						<!-- 菜单 -->
						<ul class="sidebar-nav">
							<li class="sidebar-nav-heading">
								Components
								<span class="sidebar-nav-heading-info">附加组件</span>
							</li>
							<li class="sidebar-nav-link" style="margin-top:-25px;">
								<a href="index.html">
									<router-link style="margin-top:-10px;" to="/banner">
										<i class="am-icon-home sidebar-nav-link-logo"></i>banner图
									</router-link>
									<router-link style="margin-top:-10px;" to="/community">
										<i class="am-icon-home sidebar-nav-link-logo"></i>社区资源
									</router-link>
								</a>
							</li>
						</ul>
					</div>
					<div class="tpl-content-wrapper">
						<router-view></router-view>
					</div>
				</div>
			</div>
			</div>
			`
};

// 3.创建vue实例
var vm = new Vue({
    el:'#app',
    data(){
        return{
        }
    },
    components:{
        App
    },
    router,
    template:`<App/>`
})

接口返回值

格式

status:状态码,根据status判断提示信息
info:提示信息
data:返回数据

正确

def success_response(u_dict):
    '''
    正确返回数据 过滤器
    :param u_dict: 返回的数据主体内容
    :return: 嵌套后的数据
    '''
    u_dict = {
        "status": "200",
        "info": "返回信息成功!",
        "data": u_dict
    }
    return u_dict

错误

def error_response(u_dict, err_info):
    '''
    返回错误json
    :param u_dict: json返回的主要内容
    :param err_info: 错误提示信息
    :return:
    '''
    u_dict = {
        "status": "500",
        "info": err_info,
        "data": u_dict
    }
    return u_dict

接口通,逻辑错误,比如没写参数

status:500
info:调用的时候传个参数告诉你哪错了
data:

语法错误(接口错了),data返回内容框架自定义,可以把错误提示以json形式返回

status:500
info:
data:{
	
}

测试

1.0版本问题

后台测试
  1. 后台登录需要处理,今天晚上完成
  2. 登录存储使用Vuex来进行
  3. 所有文章内容要使用富文本来处理
  4. 所有日期要使用标准日期插件来处理
  5. 修改图片上传时,要带上缩略图
  6. 默认上传图片时的缩略图修改下,可以不显示或者显示一张“请上传”字样的图片
  7. 各个页面的标题要有
  8. 各个列表的标头要使用中文
  9. 添加或者修改的时候,验证有问题要弹窗提示用户
  10. 添加或修改的时候,左边的栏目要有
  11. 刷新页面的时候,会显示一个编辑框,然后1秒钟消失,这个需要处理一下
  12. 文章原来内容部分可以换成简介,但实际内容一定要使用富文本
  13. 请求接口获取数据要前后端统一加密协议,然后将代码压缩后上传,以避免接口被盗刷
前台测试
  1. 整体项目的测试数据要多加一些,越全越好
  2. 博客封面图要加上链接
  3. 视频封面图要加上链接

1.1版本问题

新加的需求
  1. 所有链接加正则判断
  2. 富文本编辑器换成百度编辑器
  3. 两端(js,服务器)都要判断不能为空
  4. 文章内容不需要,换成一个标题,不能用富文本编辑器传标题(社区资源)
    网上资源:写链接
    本地资源:上传到服务器
    (链接和上传文件不能全为空)
已有需求bug
  1. 所有文件的上传失败或没选都要加提示(banner图,首页学科)
  2. 提示错误再点一次提交无反应 -> js问题
  3. 登录时间设为一天,别一会就重新登录
  4. 编辑后图片未改变(banner图)
  5. 所有新增和编辑的视频和图片都要要显示出来(学员视频,办学特色)
  6. 视频类型判断,如果不是视频要弹出”请上传视频”
  7. 所有预览图都要设置个样式,别那么大(课程详情)
  8. 确认删除之后就不要再alert了(关于我们)
  9. (首页信息)删除总让先登录

1.1.1版本问题

  1. 轮播图办学特色那个页面找回来
  2. 社区资源文件
  3. 社区资源新增500
  4. 视频新增功能,视频变不了,不会做————-
  5. 视频新增功能没有自动返回
  6. 学员作品错一次就废了
  7. 博客各种错
  8. 博客英文还提示去掉
  9. 弹框要统一和标题
  10. 标题太长了500要提示————–
  11. 添加成功不返回到列表页
  12. 新增功能错一次就废了
  13. 没富文本编辑器
  14. 是否删除后不要弹出删除成功
  15. 办学特色没有内页————–
  16. 办学特色,富文本编辑器套娃
  17. 连接在新标签页打开
  18. 首页学科,错一下就废了
  19. 就业信息不能选过了的时间,不会做———-
  20. 就业信息edit我日期没为空,他给我弹空了
  21. 就业信息删除先登录因为–»>js没带token过来
  22. 富文本编辑器,太low了不想做————

重新配置服务器系统的TODO

  • 生成requirements.txt
  • 重置系统 ubuntu
  • 安装宝塔
  • 用户名 密码
  • 上传项目
  • 安装python3.7
  • 安装pip venv
  • 创建venv
  • 激活虚拟环境
  • 安装requirements.txt
  • 配置数据库远程连接
  • 创建数据库
  • 执行迁移
  • 执行SQL语句
  • 开启服务

使用 token 来进行登录的过程

其中 接口端使用的是 token表 来存储用户生成的token_data

如果登录成功,接口端返回一个token_data给,请求端接收后,使用localStorage技术将数据存储到浏览器缓存中

请求接口一共有两种方式,GET请求和POST请求 在每次请求中都会带着?token=13201897689213jghasdg467431276awskld

由于在前端页面中的list页面是get请求那数据,所以在get后面加上了一个

在POST请求中,需要再请求接口时,加上一个token_key,这个token_key,是通过:时间戳+token_data+salt进行md5加密得到的,js端加密,在接口端同样算法进行计算来验证数据的正确性

这样就能有效的放置,攻击者恶意调用接口来进行数据的操作

还有一些不足的地方没有加:

  1. js部分还需要进行代码混淆和代码的压缩,这样可以防止通过js来查看加密的逻辑
  2. token的过期时间还需要加上,或者是多设备登录互相挤掉的问题,这个需要在保存token数据的时候不只是更新一个字段,而是添加一个字段,之后通过过期时间来删除之前的字段记录即可

Similar Posts

Content