跨域请求访问事情
1、场景描述
1、前端使用Vue + Vue-router + Vant + axios 2、封装axios 请求,请求url: http://localhost:8080 3、后端使用Python-Flask 访问API: http://localhost:5000 4、数据库为:MongoDB 5、以上两上URL端口不相同,即为跨域请求
2、使用技术
2.1前端使用Vue
2.1.1 axios封装--请求API
/** * 用于封装请求的方法和url值; * 2019-11-21 */ const CONTACT_API = { //获取联系人列表 getContactList: { method: 'get', url: '/contactList' }, //新建联系人 form-data newContactForm: { method: 'post', url: '/contact/new/form' }, //新建联系人 application/json newContactJson: { method: 'post', url: '/contact/new/json' }, editContact: { method: 'put', url: '/contact/edit' }, deleteContact: { method: 'delete', url: '/contact' } } export default CONTACT_API
2.1.2 axios封装--请求Http
//引用axios import axios from 'axios' //引入封装请求的API import service from './contactApi' import {Toast} from 'vant' // service 循环遍历输出不同的请求方法 //创建请求实例 let instance = axios.create({ baseURL: 'http://localhost:5000/', //contentType: "text/html;charset=utf8", //dataType: "json", timeout: 1000 }) //声明请求的HTTP对象; const Http = {}; //包裹请求方法的容器 //请求格式或参数的统一 for(let key in service) { let api = service[key]; Http[key] = async function( params, //请求参数get,url; post,patch,put(data) isFormData = false, //是否form-data请求 config = {} ) { //let url = api.url let newParams = {} if(params && isFormData){ newParams = new FormData() for(let i in params){ newParams.append(i,params[i]) } }else { newParams = params } console.log('newParams....',newParams) //不同请求判断 let response = {}; //请求响影的值; if(api.method ==='post' ||api.method ==='put' ||api.method =='patch'){ try{ console.log('config',config) response = await instance[api.method](api.url,newParams,config) }catch(err){ response = err } }else if (api.method ==='delete' || api.method ==='get') { config.params = newParams try{ //instance[api.method] ---这一部分是获取Hanson名; //(api.url,config) 为函数体,其中第1上参数为url,第二个参数为传递的params参数; response = await instance[api.method](api.url,config) }catch(err){ console.log(err) } } return response } // end function } //请求拦截器 //config 为请求的信息,封装有表头信息; instance.interceptors.request.use(config=>{ console.log('request...',config) Toast.loading({ mask: false, duration: 0, forbidClick: true, //禁止点击 message: '加载中.....' }) //发起请求前 return config }, ()=>{ Toast.clear() Toast('请求错误,请稍后重试') }) //请求响应的结果 //res为请求响应的结果; instance.interceptors.response.use(res=>{ console.log('response....',res) console.log('response-data',res.data) if(res.status === 200) { Toast.clear() return res.data } else { Toast('请求错误,请稍后重试') } },()=>{ Toast.clear() Toast('请求错误,请稍后重试') }) export default Http
2.1.3 Vue--contactlist组件
<template> <div> <van-contact-list :list="list" @add="onAdd" @edit="onEdit" @select="onSelect" /> <!-- 联系人编辑 --> <van-popup v-model="showEdit" position="bottom"> <van-contact-edit :contact-info="editingContact" :is-edit="isEdit" @save="onSave" @delete="onDelete" /> </van-popup> </div> </template> <script> import axios from "axios"; import { ContactList, Toast, ContactEdit, Popup } from "vant"; export default { data() { return { list: [{ name: '张三', tel: '13800000000', id: 0 }], instance: null, showEdit: false, editingContact: {}, isEdit: false } }, components: { [ContactList.name]: ContactList, [ContactEdit.name]: ContactEdit, [Popup.name]: Popup }, methods: { //获取列表信息 async getList() { let res = await this.$Http.getContactList() console.log('getList....',res) this.list = res }, //添加联系人; onAdd() { this.showEdit = true this.isEdit = false }, //编辑联系人 onEdit(info) { this.showEdit = true this.isEdit = true this.editingContact = info }, onSelect() { }, async onSave(info) { if(this.isEdit){ let res = this.$Http.editContact(info) console.log('edit....',res); this.showEdit = false this.getList() }else{ try{ this.$Http.newContactJson(info) Toast('新建成功') this.showEdit = false this.getList() }catch(err){ console.log(err) } } }, //删除记录 async onDelete(info) { try { let res = await this.$Http.deleteContact(info) Toast('删除成功') this.showEdit = false this.getList() } catch(err) { console.log(err) } } }, //Vue生命周期函数 created() { this.getList() } } </script> <style scoped> .van-contact-list__add{ z-index: 0; } .van-popup { height: 100%; } </style>
2.1.4 请求Http的挂载
==== main.js ===== import Vue from 'vue' import App from './App.vue' import router from './router' import Http from './service/http.js' Vue.config.productionTip = false //挂载http请求实例; Vue.prototype.$Http = Http new Vue({ router, render: h => h(App) }).$mount('#app')
2.2 后端使用Flask-MongoDB
2.2.1 查询
from flask import render_template,Response,json,make_response from app.model import Contactlist from . import vueapi #查询记录 @vueapi.route('/contactList') def get(): contacts = Contactlist.objects.filter() list = [] cons = { 'id': 0, 'name': '', 'tel': '' } print(contacts[0].id) for contact in contacts: cons = { 'id': str(contact.id), 'name': contact.name, 'tel': contact.tel } list.append(cons) print(str(contact.id) + ' ' + contact.name + ' ' + contact.tel) res = make_response(json.dumps(list)) res.headers['Access-Control-Allow-Origin'] = '*' return res
2.2.2 新增
#保存记录; @vueapi.route('/contact/new/json', methods=["POST"]) def save_data(): #获取参数信息; data = eval(request.data.decode('utf-8')) name = data.get('name') tel = data.get('tel') print(name+'...'+ tel) #获取ID count = Contactlist.objects.filter().count()+1 #实例化对象; contact = Contactlist(id=count,name= name,tel=tel) contact.save() contacts = [{ 'name': name, 'tel': tel },] #以下为解决跨域请求 res = make_response(json.dumps(contacts)) res.headers['Access-Control-Allow-Origin'] = '*' res.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, DELETE' res.headers['Access-Control-Allow-Credentials'] = 'true' return res
2.2.3 修改
#更新记录 @vueapi.route('/contact/edit',methods=['PUT']) def update_data(): #获取参数值 data = eval(request.data.decode('utf-8')) id = data.get('id') name = data.get('name') tel = data.get('tel') #根据参数获取记录 contact = Contactlist.objects.get_or_404(id=id) #修改查询出记录的内容; contact.id = id contact.name = name contact.tel = tel #保存记录 contact.save() res = make_response('OK') res.headers['Access-Control-Allow-Origin'] = '*' res.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, DELETE' res.headers['Access-Control-Allow-Credentials'] = 'true' return res
2.2.4 删除
#删除记录 @vueapi.route('/contact', methods=['delete']) def delete_data(): id = request.args['id'] contact = Contactlist.objects.get_or_404(id = id) if contact: contact.delete() res = make_response("OK") res.headers['Access-Control-Allow-Origin'] = '*' res.headers['Access-Control-Allow-Credentials'] = 'true' return res
2.3 数据库MongoDB
2.3.1 配置文件
config.py MONGODB_SETTINGS ={ 'db': 'api_list', 'host': '127.0.0.1', 'port': 27017 }
2.3.2 数据模型
from app import db class Contactlist(db.Document): id = db.IntField(primary_key=True) name = db.StringField() tel = db.StringField() meta = { 'collection': 'contacts', 'strict': False } def __repr__(self): return '<Contactlist %r>' % self.name
3、跨域请求问题点
3.1 安装flask_cors
pip install flask_cors
3.2 flask_cors引用和注册
from flask import Flask from flask_cors import * from flask_mongoengine import MongoEngine db = MongoEngine() def create_app(): app = Flask(__name__) app.config.from_object('config') db.init_app(app) #蓝图注册 from app.vueapi import vueapi as api_blueprint app.register_blueprint(api_blueprint, url_prfix="/vueapi") CORS(app, supports_credentials=True) return app
3.2最终解决方案为:
需要在flask数据访问函数中增加response的表头信息: from flask import render_template,Response,json,make_response res = make_response(json.dumps(list)) res.headers['Access-Control-Allow-Origin'] = '*' 下面这行很重要,否则会报上面的错误信息