路飞学城 之 基础配置
一、项目需求
1、五大需求如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 线上销售课程的 -商城 -知识付费类 # 需求 -首页功能 -轮播图接口 -推荐课程接口(没写) -用户功能 -用户名密码登录 -手机号验证码登录 -发送手机验证码 -验证手机号是否注册过 -注册接口 -课程列表功能 -课程列表接口 -排序,过滤,分页 -课程详情 -课程详情接口 -视频播放功能 -视频托管(第三方,自己平台) -下单功能 -支付宝支付:生成支付链接,付款,回调修改订单状态 -购买成功功能 |
2、环境配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | # 1 使用命令创建luffy项目 # 2 创建虚拟环境 mkvirtualenv -p python38 luffy # 3 安装django pip install django==3.1.12 # 3 命令创建项目 django-admin startproject luffy_api # 4 pycharm创建 luffy后台项目创建,目录调整 ├── luffy_api ├── logs/ # 项目运行时/开发时日志目录 - 包 ├── manage.py # 脚本文件 ├── luffy_api/ # 项目主应用,开发时的代码保存 - 包 ├── apps/ # 开发者的代码保存目录,以模块[子应用]为目录保存 - 包 ├── libs/ # 第三方类库的保存目录[第三方组件、模块] - 包 ├── settings/ # 配置目录 - 包 ├── dev.py # 项目开发时的本地配置 └── prod.py # 项目上线时的运行配置 ├── urls.py # 总路由 └── utils/ # 多个模块[子应用]的公共函数类库[自己开发的组件] └── scripts/ # 保存项目运营时的脚本文件 - 文件夹 # 1 运行报错 -django项目运行,要先加载settings.py(dev.py) -运行时,执行的是 python manage.py runserver # 2 解决运行报错 - 修改manage.py 中 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev') -命令行中运行,肯定不会报错 -pycharm运行,可能会报错 -删除之前的django-server,再创建一个,它会自动关联 # 3 创建app python manage.py startapp home # 在哪执行,app就创建在哪里 切到apps目录下,创建app即可 python ../../manage.py startapp home # 4 注册app -在INSTALLED_APPS 直接写app的名字,会报错,报模块找不到的错误---》 # No module named 'home' # 1 模块就是没有 # 2 不在环境变量中 # 3 自己写了一个,跟它同名 -只需要把apps路径加入到环境变量即可 sys.path.insert(0,str(BASE_DIR)) # 把apps文件夹加入环境变量,以后注册app,直接写名字即可 sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) # 5 wsgi.py,asgi.py 配置文件也要改---》后面上线才用到 # 6 验证现在配置文件用的是dev.py(在dev文件中测试) # from django.conf import settings # print(settings) # luffy_api.settings.dev |
补充:
pip freeze
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # pip freeze > requirement.txt # cat requirement.txt asgiref==3.7.2 cffi==1.16.0 cryptography==41.0.4 Django==3.2.12 django-cors-headers==4.2.0 django-simpleui==2023.8.28 djangorestframework==3.14.0 Pillow==10.0.1 pycparser==2.21 PyMySQL==1.1.0 pytz==2023.3.post1 sqlparse==0.4.4 typing_extensions==4.8.0 |
二、
1、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | # django 默认使用 python原生的日志模块 -以后不要再用print输出了,都用日志输出 -print输出,上线也会有输出,如果用日志,日志有级别,上线后把级别调高,你开发阶段的输出就不再打印了 # 可以使用第三 logru 公司里可能会用 # django中集成日志 -1 复制日志配置到dev.py中 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { # 实际开发建议使用WARNING 'level': 'DEBUG', # 控制台只显示DEBUG以上,就是Info开始 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 实际开发建议使用ERROR 'level': 'INFO', #文件中只显示INFO以上,从WARNING 'class': 'logging.handlers.RotatingFileHandler', # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi 'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"), # 日志文件的最大值,这里我们设置300M 'maxBytes': 300 * 1024 * 1024, # 日志文件的数量,设置最大日志数量为10 'backupCount': 10, # 日志格式:详细格式 'formatter': 'verbose', # 文件内容编码 'encoding': 'utf-8' }, }, # 日志对象 'loggers': { 'django': { 'handlers': ['console', 'file'], 'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统 }, } } -2 在utils下新建 common_logger.py import logging logger = logging.getLogger('django') -3 在想使用日志的位置,导入直接使用即可,日志有级别,控制台和文件中打印的日志级别是不一样的 from utils.common_logger import logger class LoggerView(APIView): def get(self, request): # 以后不要再用print输出了,都用日志输出 logger.info('info级别') logger.warn('warn级别') logger.warning('warning级别') logger.error('error级别') logger.critical('critical级别') logger.debug('debug级别') return Response('看到我了') |
2、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ##### 1 写一个函数(只要走到这,程序出异常了--》记录日志--》越详细越好) from rest_framework.views import exception_handler from rest_framework.response import Response from utils.common_logger import logger def common_exception_handler(exc, context): res = exception_handler(exc, context) if res: # 有值:drf的异常,处理了,格式不是咱们想要的 err = res.data.get('detail') or res.data or '未知错误,请联系系统管理员' response = Response({'code': 888, 'msg': '请求异常-drf:%s' % err}) else: # 其他异常,没有处理,自己处理格式 response = Response({'code': 999, 'msg': '请求异常-其他:%s' % str(exc)}) # 记录日志,越详细越好, 请求错误:请求地址是:%s,请求方式是:%s,请求用户ip地址是:%s,错误是:%s,执行的视图函数是:%s request = context.get('request') path = request.get_full_path() method = request.method ip = request.META.get('REMOTE_ADDR') user_id = request.user.pk or '未登录用户' err = str(exc) view = str(context.get('view')) logger.error( '请求错误:请求地址是:%s,请求方式是:%s,请求用户ip地址是:%s,用户id是:%s,错误是:%s,执行的视图函数是:%s' % ( path, method, ip, user_id, err, view)) return response ##### 2 在配置文件中配置一下 REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'utils.common_excepitons.common_exception_handler', } |
补充:查找drf的配置,照着写
3、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 之前使用drf的Response,我们需要自己构造返回字典 return Response(data={code:100,msg:成功,result:[{},{}]}) return Response(data={code:100,msg:成功,token:asdasd,username:lqz}) # 我们封装 APIResponse,以后使用,效果如下 return APIResponse()---》{code:100,msg:成功} return APIResponse(result=[{},{}]) return APIResponse(token=afasfd,username=lqz) ### 代码 from rest_framework.response import Response # APIResponse() # APIResponse(result=[{},{}]) # APIResponse(token=afasfd,username=lqz) # APIResponse(token=afasfd,username=lqz,status=201,headers={xx:'xx'}) class APIResponse(Response): def __init__(self, code=100, msg='成功', status=None, template_name=None, headers=None, exception=False, content_type=None, **kwargs): data = {'code': code, 'msg': msg} if kwargs: data.update(kwargs) super().__init__(data=data, status=status, headers=headers, template_name=template_name, exception=exception, content_type=content_type) # Response(data=data, status=status, headers=headers) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | # 使用mysql作为数据库 -mysql在win上安装步骤:https://zhuanlan.zhihu.com/p/571585588 # 1 创建数据库--root用户创建---》后续使用代码操作数据库,不使用root用户,新建一个用户 luffy -如果使用root用户,一旦密码泄露,所有库都不安全了 -如果新建一个luffy用户,只授予luffy库的权限,即便泄露了密码,只是这个库不安全了 # 2 创建路飞用户,授予了luffy库所有表的所有权限 -查看用户:select user,host,authentication_string from mysql.user; -创建用户: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?'; grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?'; flush privileges; mysql8: CREATE USER 'luffy'@'%' IDENTIFIED BY 'Luffy123?'; GRANT ALL PRIVILEGES ON luffy.* TO 'luffy'@'%'; # 3 以后,操作luffy库,都用luffy用户登录,就不用root用户了 # 4 项目中配置使用mysql数据库,使用luffy用户 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'luffy', 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': 'luffy', 'PASSWORD': 'Luffy123?' } } # 5 运行会报错,因为没有装myslqclient -解决方式一:直接安装myslqclient ---》win平台看人品,mac基本装不上,linux需要单独处理 -解决方式二:使用pymysql -安装,在配置文件中加入: import pymysql pymysql.install_as_MySQLdb() # 6 数据库的用户名,密码,都是直接写死在代码中的,如果咱们代码被泄露了,数据库也就被人看到了 user=os.environ.get('LUFFY_USER','luffy') password=os.environ.get('LUFFY_PWD','Luffy123?') print(user) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'luffy', 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': user, 'PASSWORD': password } } |
三、
elementui,全局配置使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # 1 跟后端交互:axios cnpm install -S axios 以后想发送ajax请求,必须导入,使用 可以把axios放到vue实例中,以后任意组件中 this.$axios.get() main.js中加入 import axios from 'axios' Vue.prototype.$axios = axios; # 2 操作cookie: vue-cookies cnpm install -S vue-cookies import cookies from 'vue-cookies' Vue.prototype.$cookies = cookies; # 3 ui库:elementui cnpm install -S element-ui import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; // 全局都会有这个css的样式 Vue.use(ElementUI); # 4 去掉 标签的默认样式 1 编写全局css assets/css/global.css 2 main.js中导入 import '@/assets/css/global.css' # 5 全局配置文件 # 写一个settings.js export default { BASE_URL:'http://127.0.0.1:8000/' } # main.js中 import settings from "@/assets/js/settings"; Vue.prototype.$setting = settings |
去除标签的默认样式
CSS Tools: Reset CSS (meyerweb.com)
2、三个组件
Banner.vue 轮播图组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | < template > < div class="banner"> < el-carousel height="400px"> < el-carousel-item v-for="item in banner_list" :key="item.id"> < div v-if="item.link.indexOf('http')==0"> < a :href="item.link">< img :src="item.image" alt=""></ a > </ div > < div v-else> < router-link :to="item.link">< img :src="item.image" alt=""></ router-link > </ div > </ el-carousel-item > </ el-carousel > </ div > </ template > < script > export default { name: "Banner", data() { return { banner_list: [] } }, created() { this.$axios.get(this.$settings.BASE_URL + 'home/banner/').then(res => { console.log(res.data) if (res.data.code == 100) { this.banner_list = res.data.result } else { this.$message({ showClose: true, message: res.data.msg, type: 'error' }); } }) } } var a = 'ss' var res = a.indexOf('ss') console.log(res) </ script > < style scoped> .el-carousel__item { height: 400px; min-width: 1200px; } .el-carousel__item img { height: 400px; margin-left: calc(50% - 1920px / 2); } </ style > |
Header.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | < template > < div class="header"> < div class="slogan"> < p >老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</ p > </ div > < div class="nav"> < ul class="left-part"> < li class="logo"> < router-link to="/"> < img src="../assets/img/head-logo.svg" alt=""> </ router-link > </ li > < li class="ele"> < span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</ span > </ li > < li class="ele"> < span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</ span > </ li > < li class="ele"> < span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</ span > </ li > </ ul > < div class="right-part"> < div > < span >登录</ span > < span class="line">|</ span > < span >注册</ span > </ div > </ div > </ div > </ div > </ template > < script > export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', } }, methods: { goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, }, created() { sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; } } </ script > < style scoped> .header { background-color: white; box-shadow: 0 0 5px 0 #aaa; /*属性用于在元素周围创建阴影效果*/ } .header:after { content: ""; display: block; clear: both; } .slogan { background-color: #eee; height: 40px; } .slogan p { width: 1200px; margin: 0 auto; color: #aaa; font-size: 13px; line-height: 40px; } .nav { background-color: white; user-select: none; width: 1200px; margin: 0 auto; } .nav ul { padding: 15px 0; float: left; } .nav ul:after { clear: both; content: ''; display: block; } .nav ul li { float: left; } .logo { margin-right: 20px; } .ele { margin: 0 20px; } .ele span { display: block; font: 15px/36px '微软雅黑'; border-bottom: 2px solid transparent; cursor: pointer; } .ele span:hover { border-bottom-color: orange; } .ele span.active { color: orange; border-bottom-color: orange; } .right-part { float: right; } .right-part .line { margin: 0 10px; } .right-part span { line-height: 68px; cursor: pointer; } </ style > |
Footer.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | < template > < div class="footer"> < ul > < li >关于我们</ li > < li >联系我们</ li > < li >商务合作</ li > < li >帮助中心</ li > < li >意见反馈</ li > < li >新手指南</ li > </ ul > < p >Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</ p > </ div > </ template > < script > export default { name: "Footer" } </ script > < style scoped> .footer { width: 100%; height: 128px; background: #25292e; color: #fff; } .footer ul { margin: 0 auto 16px; padding-top: 38px; width: 810px; } .footer ul li { float: left; width: 112px; margin: 0 10px; text-align: center; font-size: 14px; } .footer ul::after { content: ""; display: block; clear: both; } .footer p { text-align: center; font-size: 12px; } </ style > |
3、前端首页
轮播图,ElmentUI 叫走马灯 组件 | Element
推荐课程 :卡片 组件 | Element
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | < template > < div class="home"> < Header ></ Header > < Banner ></ Banner > < div > < el-row > < el-col :span="6" v-for="(o, index) in 8" :key="o" class="course_detail"> < el-card :body-style="{ padding: '0px' }"> < img src="http://photo.liuqingzheng.top/2023%2002%2022%2021%2057%2011%20/image-20230222215707795.png" class="image"> < div style="padding: 14px;"> < span >热门课程</ span > < div class="bottom clearfix"> < time class="time">价格:199</ time > < el-button type="text" class="button">查看详情</ el-button > </ div > </ div > </ el-card > </ el-col > </ el-row > </ div > < img src="http://photo.liuqingzheng.top/2023%2003%2001%2016%2010%2034%20/1.png" alt="" width="100%" height="500px"> < Footer ></ Footer > </ div > </ template > < script > import Header from "@/components/Header.vue"; import Footer from "@/components/Footer.vue"; import Banner from "@/components/Banner.vue"; export default { name: 'HomeView', components: { Header, Footer, Banner } } </ script > < style scoped> .time { font-size: 13px; color: #999; } .bottom { margin-top: 13px; line-height: 12px; } .button { padding: 0; float: right; } .image { width: 100%; display: block; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both } .course_detail { padding: 50px; } </ style > |
四、
请求的url地址必须与浏览器上的url地址相同(包括端口、域名、协议等),否则会发生跨域问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #1 前后端打通---出现CORS policy 错误 #2 同源策略 同源策略(Same origin policy)是一种约定,约定了请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,如果违背了这个约定,浏览器就会报错 请求正常发送成功,服务端也响应了,但是回来到浏览器的时候,报错,被浏览器的同源策略拦截了 #3 跨域问题出现的原因,源自于浏览器的同源策略 #4 只有前后端分离的web项目,才会出,才需要解决跨域问题 #5 CORS(跨域资源共享)---》是一个后端技术--》后端只需要在响应头中加入固定的响应头,前端就不禁止了 # 6 CORS请求分成两类(浏览器发送请求之前判断) -简单请求: 1 只发送一次请求,就是真正的请求 -非简单请求 1 先发送一个options 预检请求,服务端如果写了cors,再发送真正的请求,如果没有写cors,浏览就不再发送真正的请求了 # 7 只要同时满足以下两大条件,就属于简单请求。 # 1 请求方法是以下三种方法之一: HEAD GET POST #2 HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain |
1 2 3 4 5 6 7 8 9 10 11 | from django.utils.deprecation import MiddlewareMixin class CORSMiddleWare(MiddlewareMixin): def process_response( self , request, response): # 简单请求 response[ 'Access-Control-Allow-Origin' ] = '*' # 允许所有客户端 # 非简单请求 if request.method = = 'OPTIONS' : # res['Access-Control-Allow-Methods'] = 'DELETE,PUT' response[ 'Access-Control-Allow-Methods' ] = '*' response[ 'Access-Control-Allow-Headers' ] = '*' return response |
settings 引入
1 2 3 | MIDDLEWARE = [ 'utils.common_middleware.CORSMiddleWare' ] |
3、第三方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | # 1 下载 pip install django-cors-headers # 2 注册app INSTALLED_APPS = ( ... 'corsheaders', ... ) # 3 加中间件 MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', ... ] # 4 settings配置文件 CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', 'token' ) |
五、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 用户表,使用auth的user表,自己写 - 如果用的是auth的user表,必须在迁移之前,就定好,扩写的要写完 # 咱们用户表,用的就是基于auth的user表,扩写,咱们先写用户表 1 创建用户app python .. / .. / manage.py startapp user 2 创建用户表,models.py中 from django.contrib.auth.models import AbstractUser class User(AbstractUser): # 扩写,加入手机号,加入头像 mobile = models.CharField(max_length = 11 , unique = True ) # 需要pillow包的支持 pip install pillow icon = models.ImageField(upload_to = 'icon' , default = 'icon/default.png' ) class Meta: db_table = 'luffy_user' verbose_name = '用户表' verbose_name_plural = verbose_name def __str__( self ): return self .username 3 dev.py中注册 # 注册user app INSTALLED_APPS = [ 'user' , ] AUTH_USER_MODEL = 'user.User' # 应用名.表名 4 迁移命令 两条 |
1 2 3 4 5 6 7 8 9 10 11 | # 1 瀑布开发模式 - 架构,数据库设计 - - - 》分任务开发(周期很长,可能半年) - - - 》测试 - - - 》上线 # 2 敏捷开发(devops) - 架构设计 - - 》很多板块 - 开发某个版块,再设计相关板块的数据库 # 一周左右 - 开发某个板块 - 测试测试该板块 - 运维上线该板块 - 开发另一个版本 |
2、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | from django.db import models class BaseModel(models.Model): # 是否显示,记录插入时间,最后修改时间,是否删除(软删除),排序 # 课程表,也需要这些字段 created_time = models.DateTimeField(auto_now_add = True , verbose_name = '创建时间' ) updated_time = models.DateTimeField(auto_now = True , verbose_name = '最后更新时间' ) is_delete = models.BooleanField(default = False , verbose_name = '是否删除' ) is_show = models.BooleanField(default = True , verbose_name = '是否上架' ) orders = models.IntegerField(verbose_name = '优先级' ) class Meta: abstract = True # 这个表,只用来继承,不会再数据库生成表 from utils.common_model import BaseModel class Banner(BaseModel): title = models.CharField(max_length = 16 , unique = True , verbose_name = '名称' ) image = models.ImageField(upload_to = 'banner' , verbose_name = '图片' ) # 地址,存放轮播图的地址 link = models.CharField(max_length = 64 , verbose_name = '跳转链接' ) info = models.TextField(verbose_name = '详情' ) # 也可以用详情表,宽高出处 class Meta: db_table = 'luffy_banner' verbose_name_plural = '轮播图表' def __str__( self ): return self .title |
补充:这里的is_delete字段,软删除
MySQL 数据库中,硬删除(也称为物理删除)指的是直接从数据库表中删除数据行,硬删除操作可能导致数据库索引失效
3、
APIResponse
1 2 3 4 5 6 7 | from rest_framework.mixins import ListModelMixin from utils.common_response import APIResponse class CommonListModelMixin(ListModelMixin): def list(self, request, *args, **kwargs): res = super().list(request, *args, **kwargs) return APIResponse(result=res.data) |
轮播图接口
1 2 3 4 5 6 7 8 9 | from rest_framework.viewsets import ViewSetMixin, GenericViewSet from . import models from .serializer import BannerSerializer from django.conf import settings from utils.common_mixin import CommonListModelMixin class BannerView(GenericViewSet, CommonListModelMixin): # 自动生成路由 queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT] serializer_class = BannerSerializer |
4、序列化类
1 2 3 4 5 6 7 8 | from rest_framework import serializers from .models import Banner class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner fields = [ 'id' , 'image' , 'link' ] |
5、子路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from django.contrib import admin from django.urls import path from home import views # pycharm报错,但实际上不报错 ,只需要把加入到环境变量的路径都做成source root即可 from rest_framework.routers import SimpleRouter from .views import BannerView router = SimpleRouter() # 127.0.0.1:8080/api/v1/home/banner/--- get router.register( 'banner' , BannerView, 'banner' ) urlpatterns = [ ] urlpatterns + = router.urls |
6、配置文件
1 2 3 4 5 6 7 8 9 | # dev配置 导入用户配置 from .user_settings import * ### user_settings.py # 用户自己配置,单独放到一个py文件中 BANNER_COUNT = 3 # 后续可能还用别的配置 |
7、补充
1 2 3 | # app 后端---》app一打开---》广告页---》可以滑动---》点击广告页---》一种浏览器打开页面跳转,一种情况打开一个他们app内的页面 # 原理就是轮播图接口 |
七、后台管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 任何项目,都会有后台管理 # 使用 django+vue ---》写---》 # django的admin做后台管理---》simpleui美化 # 1 下载simpleui---》app中注册 # 2 创建一个root用户,后台登录 # 3 后台登录,录入数据:admin中注册这个表 # 4 默认情况文件传到根路径下 需要配置:以后都会放到meida文件夹下 MEDIA_ROOT = os.path.join(BASE_DIR, 'media' ) # 5 接口中不带meida 路径的: 配置文件中 MEDIA_URL = 'media/' # 取出的文件地址,拼接上media这个目录 # 6 前端能访问图片 path( 'media/<path:path>' , serve, { 'document_root' : settings.MEDIA_ROOT}), |
八、短信验证登录功能
1、
1 2 3 4 5 6 | # 用户板块需要写的接口 1 用户名密码登录(多方式登录) 2 获取手机验证码接口 3 手机号+验证码登录 4 注册接口 5 验证手机号是否存在接口 |
2、验证手机号是否存在接口
user.view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | from django.shortcuts import render # Create your views here. from django.utils.datastructures import MultiValueDictKeyError from rest_framework.viewsets import ViewSet from rest_framework.decorators import action from .models import User from utils.common_response import APIResponse class MobileView(ViewSet): # @action(methods=['GET'], detail=False) # def check_mobile(self, request, *args, **kwargs): # # 取出前端传入手机号 # mobile = request.query_params.get('mobile') # # 去数据中查询,是否存在即可 # user = User.objects.filter(mobile=mobile).first() # if user: # return APIResponse(msg='手机号存在') # else: # return APIResponse(code=101, msg='手机号不存在') @action (methods = [ 'GET' ], detail = False ) def check_mobile( self , request, * args, * * kwargs): try : # 取出前端传入手机号 mobile = request.query_params[ 'mobile' ] # 去数据中查询,是否存在即可 User.objects.get(mobile = mobile) # 有且只有一个才不报错,否则报错 except MultiValueDictKeyError as e: raise Exception( '您没有携带手机号' ) # except ObjectDoesNotExis as e: # # raise Exception('手机号不存') except Exception as e: raise Exception( '未知错误,请联系系统管理员' ) return APIResponse(msg = '手机号存在' ) |
开启子路由
1 2 3 4 5 6 7 8 9 10 | from rest_framework.routers import SimpleRouter from .views import MobileView router = SimpleRouter() router.register( 'mobile' , MobileView, 'mobile' ) urlpatterns = [ ] urlpatterns + = router.urls |
验证
127.0.0.1:8000/api/v1/user/mobile/check_mobile/?mobile=‘手机号’