文章目录
创建后端项目
mkdir luffy # 这里是项目根目录,后面存放前后端2个项目在里面
cd luffy
django-admin startproject luffyapi
调整目录
├── docs/ # 项目相关资料保存目录
├── logs/ # 项目运行时/开发时日志目录
├── manage.py
├── luffyapi/ # 项目主应用,开发时的代码保存
│ ├── apps/ # 开发者的代码保存目录,以模块[子应用]为目录保存
│ ├── libs/ # 第三方类库的保存目录[第三方组件、模块]
│ ├── settings/
│ ├── dev.py # 项目开发时的本地配置
│ ├── prod.py # 项目上线时的运行配置
│ ├── urls.py # 总路由
│ ├── utils/ # 多个模块[子应用]的公共函数类库[自己开发的组件]
└── scripts/ # 保存项目运营时的脚本文件
- 分不同环境进行项目配置
开发者本地的环境、目录、数据库密码和线上的服务器都会不一样,所以我们的配置文件可以针对不同的系统分成多分.
- 在项目主应用下,创建一个
settings
的配置文件存储目录 - 根据线上线下两种情况分别创建2个配置文件
dev.py
和prod.py
- 把原来项目主应用的
settings.py
配置内容复制2份到dev.py
和prod.py
里面 - 把原来的
settings.py
配置文件修改文件名或者删除
根据在manage.py
中根据不同的情况导入对应的配置文件了
日志配置
在settings/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': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
# 日志位置,日志文件名,日志保存目录必须手动创建
'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/luffy.log"),
# 日志文件的最大值,这里我们设置300M
'maxBytes': 300 * 1024 * 1024,
# 日志文件的数量,设置最大日志数量为10
'backupCount': 10,
# 日志格式:详细格式
'formatter': 'verbose'
},
},
# 日志对象
'loggers': {
'django': {
'handlers': ['console', 'file'],
'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统
},
}
}
异常处理
新建utils/exceptions.py
from rest_framework.views import exception_handler
from django.db import DatabaseError
from rest_framework.response import Response
from rest_framework import status
import logging
logger = logging.getLogger('django')
def custom_exception_handler(exc, context):
"""
自定义异常处理
:param exc: 异常类
:param context: 抛出异常的上下文
:return: Response响应对象
"""
# 调用drf框架原生的异常处理方法
response = exception_handler(exc, context)
if response is None:
view = context['view']
if isinstance(exc, DatabaseError):
# 数据库异常
logger.error('[%s] %s' % (view, exc))
response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
return response
settings/dev.py
配置文件中添加
REST_FRAMEWORK = {
# 异常处理
'EXCEPTION_HANDLER': 'luffyapi.utils.exceptions.custom_exception_handler',
}
创建数据库
create database luffy default charset=utf8mb4;
为当前项目创建数据库用户[这个用户只能看到这个数据库]
create user luffy_user identified by 'luffy';
grant all privileges on luffy.* to 'luffy_user'@'%';
flush privileges;
配置数据库连接
打开settings/dev.py
文件,并配置
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": "127.0.0.1",
"PORT": 3306,
"USER": "luffy_user",
"PASSWORD": "luffy",
"NAME": "luffy",
}
}
在项目主模块的 __init__.py
中导入pymysql
import pymysql
pymysql.install_as_MySQLdb()
调整错误
数据库版本检测导致的错误
数据库的版本检测代码注释掉。
第二个错误也是因为数据库版本的默认编码导致,query返回的内容格式使用有误。
新增一行代码,把query查询结果转换格式为 bytes类型
搭建前端项目
创建项目目录
cd ~/Desktop/luffy
vue init webpack lufei_pc
例如,我要把项目保存在~/Desktop
桌面目录下,可以如下操作:
cd ~/Desktop
vue init webpack lufei_pc
根据需要在生成项目时,我们选择对应的选项, 效果:
根据上面的提示,我们已经把vue项目构建好了,运行测试服务器。
打开项目已经,在pycharm的终端下运行vue项目,查看效果。
npm run dev
接下来,我们根据终端上效果显示的对应地址来访问项目(如果有多个vue项目在运行,8080端口被占据了,服务器会自动改端口,所以根据自己实际在操作中看到的地址来访问。)
访问:http://localost:8080。效果:
我们也可以把我们的前端项目进行git源代码管理
初始化前端项目
清除默认的HelloWorld组件和APP.vue
中的默认样式和logo.png
图片
接下来,我们可以查看效果了,一张白纸~
安装路由vue-router
下载路由组件
npm i vue-router -S
执行效果:
url : 协议://域名:端口/path?query_string#/home
配置路由
- 初始化路由对象
在src
目录下创建routers
路由目录,在routers
目录下创建index.js
路由文件
index.js
路由文件中,编写初始化路由对象的代码 .
import Vue from "vue"
import Router from "vue-router"
// 这里导入可以让让用户访问的组件
Vue.use(Router);
export default new Router({
// 设置路由模式为‘history’,去掉默认的#
mode: "history",
routes:[
// 路由列表
]
})
- 注册路由信息
打开main.js
文件,把router
对象注册到vue中。
代码:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './routers/index';
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
});
- 在视图中显示路由对应的内容
在App.vue
组件中,添加显示路由对应的内容。
代码:
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
</style>
- 创建并提供前端首页的组件
routers/index.js
// import Vue from "vue"
// import Router from "vue-router"
//
//
// // 这里导入可以让让用户访问的组件
import Home from "../components/Home"
// Vue.use(Router);
//
// export default new Router({
// // 设置路由模式为‘history’,去掉默认的#
// mode: "history",
// routes:[
// // 路由列表
{
name:"Home",
path:"/",
component:Home,
},
{
name:"Home",
path:"/home",
component:Home,
},
]
// })
- 创建Home组件
components/Home.vue
<template>
<div id="home">
前端首页
</div>
</template>
<script>
export default {
name:"Home",
data(){
return {
}
}
}
</script>
<style scoped>
</style>
前端初始化全局变量和全局方法
在src
目录下创建settings.js
站点开发配置文件:
export default {
Host:"http://127.0.0.1",
}
在main.js
中引入
// // The Vue build version to load with the `import` command
// // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
// import Vue from 'vue'
// import App from './App'
// import router from './routers/index';
import settings from "./settings"
// Vue.config.productionTip = false;
Vue.prototype.$settings = settings;
//
// /* eslint-disable no-new */
// new Vue({
// el: '#app',
// router,
// components: { App },
// template: '<App/>'
// });
引入ElementUI
npm i element-ui -S
上面的命令等同于
npm install element-ui --save
执行命令效果:
配置ElementUI到项目中
在main.js
中导入ElementUI,并调用。
代码:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
// import Vue from 'vue'
// import App from './App'
// import router from './routers/index';
// 开发配置文件
// import settings from "./settings"
// Vue.prototype.$settings = settings;
// elementUI 导入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 调用插件
Vue.use(ElementUI);
// Vue.config.productionTip = false;
/* eslint-disable no-new */
// new Vue({
// el: '#app',
// router,
// components: { App },
// template: '<App/>'
// });
效果:
成功引入了ElementUI以后,接下来我们就可以开始进入前端页面开发,首先是首页。
接下来我们把之前完成的首页,直接拿过来使用[注意除了组件以外,还有静态文件也需要拿过来,包括App.vue里面的公共样式],并运行项目。
App.vue,全局css初始化代码
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
/* 声明全局样式和项目的初始化样式 */
body,h1,h2,h3,h4,p,table,tr,td,ul,li,a,form,input,select,option,textarea{
margin:0;
padding: 0;
font-size: 15px;
}
a{
text-decoration: none;
color: #333;
}
ul,li{
list-style: none;
}
table{
border-collapse: collapse; /* 合并边框 */
}
/* 工具的全局样式 */
.full-left{
float: left!important;
}
.full-right{
float: right!important;
}
[class*=" el-icon-"], [class^=el-icon-]{
font-size: 50px;
}
.el-carousel__arrow{
width: 120px;
height: 120px;
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner{
background: #ffc210;
border-color: #ffc210;
border: none;
}
.el-checkbox__inner:hover{
border-color: #9b9b9b;
}
.el-checkbox__inner{
width: 16px;
height: 16px;
border: 1px solid #9b9b9b;
border-radius: 0;
}
.el-checkbox__inner::after{
height: 9px;
width: 5px;
}
</style>
首页显示
添加代码
Home.vue
<template>
<div class="home">
<Header></Header>
<Banner></Banner>
<Footer></Footer>
</div>
</template>
<script>
import Header from "./common/Header"
import Banner from "./common/Banner"
import Footer from "./common/Footer"
export default {
name: "Home",
data(){
return {}
},
methods:{
},
components:{
Header,
Footer,
Banner,
}
}
</script>
<style scoped>
</style>
components/common/Header.vue
<template>
<div class="header-box">
<div class="header">
<div class="content">
<div class="logo full-left">
<router-link to="/"><img src="/static/image/logo.svg" alt=""></router-link>
</div>
<ul class="nav full-left">
<li><span>免费课</span></li>
<li><span>轻课</span></li>
<li><span>学位课</span></li>
<li><span>题库</span></li>
<li><span>老男孩教育</span></li>
</ul>
<div class="login-bar full-right">
<div class="shop-cart full-left">
<img src="/static/image/cart.svg" alt="">
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box full-left">
<span>登录</span>
|
<span>注册</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data(){
return{
}
},
created(){
},
methods:{
}
}
</script>
<style scoped>
.header-box{
height: 80px;
}
.header{
width: 100%;
height: 80px;
box-shadow: 0 0.5px 0.5px 0 #c9c9c9;
position: fixed;
top:0;
left: 0;
right:0;
margin: auto;
z-index: 99;
background: #fff;
}
.header .content{
max-width: 1200px;
width: 100%;
margin: 0 auto;
}
.header .content .logo{
height: 80px;
line-height: 80px;
margin-right: 50px;
cursor: pointer; /* 设置光标的形状为爪子 */
}
.header .content .logo img{
vertical-align: middle;
}
.header .nav li{
float: left;
height: 80px;
line-height: 80px;
margin-right: 30px;
font-size: 16px;
color: #4a4a4a;
cursor: pointer;
}
.header .nav li span{
padding-bottom: 16px;
padding-left: 5px;
padding-right: 5px;
}
.header .nav li span a{
display: inline-block;
}
.header .nav li .this{
color: #4a4a4a;
border-bottom: 4px solid #ffc210;
}
.header .nav li:hover span{
color: #000;
}
.header .login-bar{
height: 80px;
}
.header .login-bar .shop-cart{
margin-right: 20px;
border-radius: 17px;
background: #f7f7f7;
cursor: pointer;
font-size: 14px;
height: 28px;
width: 88px;
margin-top: 30px;
line-height: 32px;
text-align: center;
}
.header .login-bar .shop-cart:hover{
background: #f0f0f0;
}
.header .login-bar .shop-cart img{
width: 15px;
margin-right: 4px;
margin-left: 6px;
}
.header .login-bar .shop-cart span{
margin-right: 6px;
}
.header .login-bar .login-box{
margin-top: 33px;
}
.header .login-bar .login-box span{
color: #4a4a4a;
cursor: pointer;
}
.header .login-bar .login-box span:hover{
color: #000000;
}
</style>
components/common/Bannner.vue
<template>
<el-carousel height="720px" :interval="3000" arrow="always">
<el-carousel-item>
<img src="/static/image/alex.jpeg" alt="">
</el-carousel-item>
<el-carousel-item>
<img src="/static/image/banner1.png" alt="">
</el-carousel-item>
<el-carousel-item>
<img src="/static/image/banner1.png" alt="">
</el-carousel-item>
</el-carousel>
</template>
<script>
export default {
name: "Banner",
}
</script>
<style scoped>
.el-carousel__item h3 {
color: #475669;
font-size: 18px;
opacity: 0.75;
line-height: 300px;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
}
</style>
components/common/Footer.vue
<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>
也可以把App.vue
的style标签的css代码放到static
外部目录下引用过来
main.js
import "../static/css/reset.css";
reset.css
body,h1,h2,h3,h4,h5,ul,p{
padding: 0;
margin:0;
font-weight: normal;
}
body{
margin-top: 80px;
}
a{
text-decoration: none;
color: #4a4a4a;
}
a:hover{
color: #000;
}
ul{
list-style: none;
}
img{
width: 100%;
}
.header .el-menu li .el-submenu__title{
height: 26px!important;
line-height: 26px!important;
}
.el-menu--popup{
min-width: 140px;
}
.el-checkbox__inner{
width:16px;
height: 16px;
border: 1px solid #999;
}
.el-checkbox__inner:after{
width: 6px;
height: 8px;
}
.el-form-item__content{
margin-left:0px!important;
width: 120px;
}
跨域CORS
我们现在为前端和后端分别设置两个不同的域名:
位置 | 域名 |
---|---|
前端 | www.luffycity.cn |
后端 | api.luffycity.cn |
编辑/etc/hosts
文件,可以设置本地域名
sudo vim /etc/hosts
在文件中增加两条信息
127.0.0.1 localhost
127.0.0.1 api.luffycity.cn
127.0.0.1 www.luffycity.cn
如果安装了NGINX服务
通过浏览器访问前端vue项目,会出现nginx的欢迎页面,主要因为我们当前项目已经有一个nginx监听了80端口,所以访问www.luffycity.cn
网址时,会自动被转发到127.0.0.1
本机,因为没有网址默认端口是80端口,所以被nginx进行处理了当前请求,因此我们暂时先把nginx关闭先。
# 查找nginx的进程
ps -ef|grep nginx
# 关闭进程
sudo kill -9 nginx进程号
关闭了nginx以后,访问www.luffy.cirty.cn
网址,效果:
上面并不是错误,而是没人监听了这个地址和端口了,解决方法:
暂停运行前端项目,并修改配置文件config/index.js
host: 'www.luffycity.cn', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: true,
保存修改信息,并重启项目
通过浏览器访问drf项目,会出现以下错误信息
可以通过settings/dev.py
的ALLOWED_HOSTS
,设置允许访问
# 设置哪些客户端可以通过地址访问到后端
ALLOWED_HOSTS = [
'api.luffycity.cn',
]
django-cors-headers扩展跨域访问
让用户访问的时候,使用api.luffycity.cn:8000
修改pycharm的manage.py
的配置参数
同时,之前的前端项目中,我们创建的src/settings.js
文件中的后端接口地址也要对应修改:
export default {
Host:"http://api.luffycity.cn:8000/"
}
现在,前端与后端分处不同的域名,我们需要为后端添加跨域访问的支持
否则前端无法使用axios无法请求后端提供的api数据,我们使用CORS来解决后端对跨域访问的支持。
使用django-cors-headers扩展
在 Response(headers={"Access-Control-Allow-Origin":'客户端地址/*'})
文档:https://github.com/ottoyiu/django-cors-headers/
安装
pip install django-cors-headers
添加子应用
INSTALLED_APPS = (
...
'corsheaders',
...
)
中间层设置【必须写在第一个位置】
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
setttings/dev.py
,添加白名单
# CORS组的配置信息
CORS_ORIGIN_WHITELIST = (
'www.luffycity.cn:8080',
)
CORS_ALLOW_CREDENTIALS = True # 允许ajax跨域请求时携带cookie
完成了上面的步骤,我们就可以通过后端提供数据给前端使用ajax访问了。
前端使用 axios就可以访问到后端提供给的数据接口,但是如果要附带cookie
信息,前端还要设置一下。
前端引入axios插件并配置允许axios发送cookie
信息[axios本身也不允许ajax发送cookie到后端]
npm i axios -S
在main.js
中引用axios
插件
import axios from 'axios'; // 从node_modules目录中导入包
// 允许ajax发送请求时附带cookie
axios.defaults.withCredentials = true;
Vue.prototype.$axios = axios; // 把对象挂载vue中