前端跨域问题解决
一、同源策略
同源策略是一个重要的安全策略,它用于限制一个Origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
Origin:指web文档的来源,Web 内容的来源取决于访问的URL的方案 (协议),主机 (域名) 和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。
二、跨域
关于URL是否同源,根据上图中的①②③进行判断即可,只要有一点不同,就达到跨域的条件。顺带一提,即便是向域名对应的ip进行资源请求,仍然会跨域。
IE的特殊性:Internet Explorer 的同源策略有两点差异,一是IE未将端口号纳入同源策略的检查,其次是两个高度互信的域名也不受同源策略的检查。
常见的跨域情景:
浏览器内常见的跨域报错:
跨域出现的场景:
一般常见于开发阶段,本地启动项目后,当前页面域名和后台服务器域名不相同,导致跨域。在项目上线后,会通过统一域名、后端配置域名白名单等方式避免跨域。
下方的解决方案中,我们通过koa2框架搭建服务器,实现一系列的情景模拟。
三、跨域的解决方案
1.JSONP
原理:通过script标签没有跨域限制的特性,进行资源的请求和获取。
限制:需要目标服务器进行配合,且仅支持get请求
我们直接通过代码和注释,理解jsonp的使用前端代码如下:
<script>
window.jsonp = function(res){
console.log(res);
}
</script>
<script src="http://localhost:9527/jsonp?val=123&cb=jsonp"></script>
后端代码如下:
// 定义jsonp接口
router.get('/jsonp', async (ctx, next) => {
/*
1.后端通过query获取前端传来的请求参数
其中包括:
· 交予后端进行功能逻辑操作的数据,如val
· 交予后端进行jsonp操作的函数名,如cb
*/
const {cb, val} = ctx.query
// 2.调用回调函数,进行传参,将处理好的数据返回给前端
if(val === '123'){
const requestData = {
code: 10001,
data: '登陆成功'
}
//在响应体中触发目标函数,并将处理好的数据requestData作为实参传入
ctx.body = `${cb}(${JSON.stringify(requestData)})`;
}
})
前端通过window对象,在全局挂载了一个待触发的函数。
后端通过响应体触发这个函数,并将数据作为入参,传给前端。
了解简单的实现后,前端可以对jsonp的功能再进行一层封装:
/*
1. 生成script标签,我们需要script标签进行接口的调用
2. 处理参数数据,分别整理好接口,接口参数,函数名等数据,并进行填充
3. 写入生成好的script标签,实现接口的调用(返回promise对象,便于链式调用)
4. 清除script标签
*/
function jsonp(requestData) {
// 对传入参数进行处理
const { url, data, jsonp } = requestData;
let query = '';
for (let key in data) {
query += `${key}=${data[key]}&`;
}
const src = `${url}?${query}jsonp=${jsonp}`;
// 生成、填充script标签,在页面中挂载调用接口
let scriptTag = document.createElement('script');
scriptTag.src = src;
document.body.appendChild(scriptTag);
return new Promise((resolve, reject) => {
window[jsonp] = function(rest){
resolve(rest)
document.body.removeChild(scriptTag)
}
})
}
// 整理数据
const requestData = {
url: 'http://localhost:9527/jsonp',
data: {
val: 123,
},
jsonp: 'getMessage'
}
// 接口调用
btn.onclick = function () {
jsonp(requestData).then(function (response) {
console.log(response);
})
}
2.CORS
Cross-Origin Resource sharing(跨域资源共享),是一种基于HTTP头的机制,该机制允许服务器标示除了它自己以外其他origin(域名,协议和端口),既浏览器在跨域的情景下仍然能从目标服务器请求并获取资源。
而对服务器数据可能产生副作用的HTTP请求方法,都会触发CORS中的预检机制。
CORS中通过预检机制(preflight request)检查服务器是否允许浏览器发送真实请求,浏览器会先发送一个预检请求(option请求),请求中会携带真实请求的请求信息:
origin:请求的来源
Access-Control-Request-Method:
通知服务器在真正的请求中会采用哪种HTTP方法(GET,POST,DELETE...)
Access-Control-Request-Headers:通知服务器在真正的请求中会采用哪些请求头
服务器可以在预检请求中,可以根据以上三条信息,确定预检请求是否通过:
//server.js
app.use(async (ctx, next) => {
// 允许跨域资源共享的白名单
const whiteList = ['http://127.0.0.1:5500']
// 判断目标源是否通行
const pass = whiteList.includes(ctx.header.origin)
// 对于预检请求,如果没有设置正确的响应状态,浏览器会直接拦截真实请求,直接报错提示跨域
// 所以我们可以在这一部分,确定客户端的请求是否符合我们的要求
if (ctx.method === "OPTIONS") {
if (!pass) return
// 预检放行
ctx.status = 204
}
await next();
});
响应的状态码是决定预检请求是否通过的关键,返回正常的状态码(通常是204)就能通过预检请求,让浏览器发出真实的请求。
在代码中也可以看出,pass是决定预检请求的关键,那在实际的项目中,还得根据设计去决定通行的具体条件。当通过预检请求后,后台可以设置对应的响应头数据,例如是否允许目标源跨域资源共享:
//server.js
app.use(async (ctx, next) => {
console.log('middleware for cors');
// 允许跨域资源共享的白名单
const whiteList = ['http://127.0.0.1:5500']
// 判断目标源是否通行
const pass = whiteList.includes(ctx.header.origin)
// 对于预检请求,如果没有设置正确的响应状态,浏览器会直接拦截真实请求,直接报错跨域
// 所以我们可以在这一部分,确定客户端的请求是否符合我们的要求
if (ctx.method === "OPTIONS") {
if (!pass) return
// 预检放行
ctx.status = 204
}
// 允许访问的origin
ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
// cookie是否允许携带
ctx.set("Access-Control-Allow-Credentials", true);
// 允许访问的HTTP方法
ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
// 哪些请求头允许通行
ctx.set(
"Access-Control-Allow-Headers",
"X-Requested-With,Content-Type,Accept,Origin"
);
// 暴露给客户端的响应头信息,在不设置的情况下,客户端只能获取默认的响应头,如’content-type‘
ctx.set(
"Access-Control-Expose-Headers",
"With-Requested-Key"
);
// 设置对应的响应头数据
ctx.set(
"With-Requested-Key",
"HW"
);
// 预检结果的缓存时间,毫秒为单位,Firefox上限是86400-24小时,Chromium(谷歌引擎)上限是7200-2小时
ctx.set("Access-Control-Max-Age", 0);
await next();
});
其中需要注意两个点:
关于Access-Control-Expose-Header
使用CORS时,浏览器只允许获取默认的响应头,像上文代码中的标头With-Requested-Key,即便我们可以通过浏览器的调试器查看,也无法通过代码去获取,这时候就需要后台通过Access-Control-Expose-Header进行暴露(后台代码在已在上方统一贴出)。
前端代码
<body>
<button id="btn"> 请求资源 </button>
</body>
<script>
btn.onclick = function () {
axios.post('http://localhost:9527/getMessage', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
// 可以在里面查找到暴露出来的响应头数据,如’With-Requested-Key‘: "HW"
console.log(response.headers);
})
.catch(function (error) {
console.log(error);
});
}
</script>
关于Access-Control-Allow-Credentials
使用CORS时,默认不携带cookie,需要同时满足三个条件,才能在使用CORS时进行cookie的传递:
浏览器的请求中,设置withCredentials参数为true
服务端设置标头Access-Control-Allow-Credentials为true
服务端设置标头Access-Control-Allow-Origin不为*
我们可以在原生ajax请求中设置该参数,或者在axios的默认配置中设置该参数:
// 原生ajax
const xhr = new XMLHttpRequest()
xhr.withCredentials = true
// axios
axios.defaults.withCredentials = true;
Ok,明白CORS的作用,以及明白CORS中的预检机制后,接下来是了解什么时机下会触发预检机制。
CORS中归纳了一系列不会触发预检机制的请求场景,即满足所有下述条件的情况下,统称为简单请求:
使用这三种方法之一:GET HEAD POST
不得人为设置此集合外的其他首部字段:Accept Accept-Language Content-Language Content-Type
Content-type的值仅限于这三者之一:
text/plain
multipart/form-data
application/x-www/form-urlencoded
请求中,XMLHttpRequest实例没有注册任何事件监听器,即XMLHttpRequest实例对象可以使用XMLHttpRequest.upload属性进行访问
请求中没有使用ReadableStream对象
小结:CORS中主要区分了简单请求和复杂请求两种情况,复杂请求会触发CORS的预检机制。通过上方的案例,也可以清楚CORS的配置主要是在服务端,但客户端也需要知道CORS的使用注意点,例如响应头数据的获取以及cookies的携带配置,这些知识应该是前后端都需要掌握的技能点。
3.服务器代理
同源策略主要是限制浏览器和服务器之间的请求,服务器与服务器之间并不存在跨域。
我们可以通过koa2模拟和实现这种概念:
//前端代码
<body>
<button id="btn"> 请求资源 </button>
<script>
btn.onclick = function () {
let url = checkUrlProxy('http://localhost:9527/api/getMessage','api')
axios.post(url, {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
}
// 判断接口是否携带api字段,若是,则更改为代理服务器对应的域名
function checkUrlProxy(url, proxyFlag){
let proxyServer = 'http://localhost:1005'
let urlArr = [url.split('/')[1],url.split('/')[3]]
if(urlArr.includes(proxyFlag)) {
return `${proxyServer}/${proxyFlag}${url.split(proxyFlag)[1]}`
}
return url
}
//
</script>
</body>
前端的代码部分,通过checkUrlProxy函数简单地确定本次请求是否要转向代理服务器。
后端代码如下:
//proxyServer.js
let requestFlag = false
let body = ''
app.use(async (ctx, next) => {
// 全放行
if (ctx.method === "OPTIONS") {
ctx.status = 204
requestFlag = false
} else {
requestFlag = true
}
ctx.set("Access-Control-Allow-Origin", "*");
ctx.set("Access-Control-Allow-Credentials", true);
ctx.set("Access-Control-Request-Method", "*");
ctx.set(
"Access-Control-Allow-Headers",
"X-Requested-With,Content-Type,Accept,Origin"
);
ctx.set("Access-Control-Max-Age", 86400);
// 根据具体情况进行修改
ctx.set("Access-Control-Expose-Headers", "With-Requested-Key");
await next();
if(requestFlag) {
ctx.body = body
body = ''
}
});
app.use(async (ctx, next) => {
if (!requestFlag) return
await p4r(ctx)
});
function p4r(ctx) {
return new Promise((res, rej) => {
const proxyRequest = http.request({
host: '127.0.0.1',
port: 9527,
path: ctx.url,
method: ctx.method,
headers: ctx.header
},
serverResponse => {
serverResponse.on('data', chunk => {
body += chunk
})
serverResponse.on('end', () => {
res(body)
})
}
)
proxyRequest.end()
})
}
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
app.listen(1005, (err) => {
if (err) console.log('服务器启动失败');
else console.log('proxy server 1005 running --> ✨✨✨');
})
//targetServer.js
const data = {val : 123}
// 配合代理服务器的post路由
router.post('/api/getMessage', (ctx) => {
ctx.body = JSON.stringify(data)
})
// 定义好路由组件的内容后进行路由注册
app.use(router.routes())
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
app.listen(9527, (err) => {
if (err) console.log('服务器启动失败');
else console.log('服务器启动成功');
})
后端代码主要分两部分:
代理服务器(proxyServer),代理服务器设置CORS时不限制通行,在koa2框架中,通过中间件向目标服务器发送请求,当接收到对应数据后,再响应给浏览器
目标服务器(targetServer),目标服务器不需要做太复杂的配置,案例中只是将数据传递给请求方
Ok,我们通过这个案例,明确代理服务器的具体效果,浏览器向目标服务器直接请求资源,仍然会受到同源策略的影响,但通过代理服务器向目标服务器请求资源时,却没这种限制。
那在实际项目中,我们可以通过脚手架或打包工具的配置文件,简洁方便地设置代理服务器,无需自己手写服务器代码,拿vue的脚手架为例:
devServer:{
proxy:{
'api':{
target:'127.0.0.1:9527', //目标服务器地址
changeOrigin: true, // 是否允许跨域
pathRewrite: { //是否重写接口
'api':'',
}
}
}
}
在配置的时候,可以通过框架的脚手架,或者打包工具确定配置文件,例如一些熟悉的字眼:vue.config.jswebpack.config.jspackage.json(react),更准确的做法就是直接去对应工具的官方文档查阅代理服务器的配置介绍。
相关文章:
前端跨域问题解决
一、同源策略 同源策略是一个重要的安全策略,它用于限制一个Origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。 Origin:指web文档的来源,Web 内容的来源取决于访问的U…...
【前端】Js
目 录 一.前置知识第一个程序JavaScript 的书写形式注释输入输出 二.语法概览变量的使用理解 动态类型基本数据类型 三.运算符算术运算符赋值运算符 & 复合赋值运算符自增自减运算符比较运算符逻辑运算符位运算移位运算 四.条件语句if 语句三元表达式switch 五.循环语句whi…...
第四章 Istio出口流量管理
文章目录 访问外部服务Envoy 代理将请求传递给网格外服务配置服务条目以提供对外部服务的受控访问访问外部 HTTP 服务 直接访问外部服务 出口网关清理 HTTP 网关其他 访问外部服务 为了更好的做好网络访问控制,k8s结合Istio出口网络升级示意图 来自 Istio 的 pod…...
leetcode做题笔记188. 买卖股票的最佳时机 IV
给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。 注意:你不能同时参与多…...
基于springboot实现大学生社团活动平台项目【项目源码+论文说明】
摘要 21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,网络管理工作的重要性已逐渐被人们所认识,科学化的管理,使信…...
力扣--第三大的数
给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。 示例 1: 输入:[3, 2, 1] 输出:1 解释:第三大的数是 1 。 示例 2: 输入:[1, 2] 输出࿱…...
Android 11.0 禁用adb reboot recovery命令实现正常重启功能
1.前言 在11.0的系统开发中,在定制recovery模块的时候,由于产品开发需要要求禁用recovery的相关功能,比如在通过adb命令的 adb reboot recovery的方式进入recovery也需要实现禁用,所以就需要了解相关进入recovery流程来禁用该功能 2.禁用adb reboot recovery命令实现正常…...
Cesium Vue(一)— 项目初始化配置
1. 创建VUE项目工程 创建项目 vue create cesium-vue配置Vue3 2. 创建vue.config.js文件 const { defineConfig } require(vue/cli-service)// The path to the CesiumJS source code const cesiumSource node_modules/cesium/Source; const cesiumWorkers ../Build/C…...
【linux下centos7.9安装docker,docker-composed(root用户)】
一 安装docker 1.联网下载安装docker curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 2.安装成功 docker -v 3.将docker注册为service,进入/etc/systemd/system/目录,并创建docker.service文件(注意insecure-registry修改成自己的…...
腾讯云我的世界mc服务器配置怎么选择?
使用腾讯云服务器开Minecraft我的世界服务器配置怎么选择?10人以内玩2核4G就够用了,开我的世界服务器选择轻量应用服务器就够了,腾讯云轻量CPU采用至强白金处理器,大型整合包一般1.12版本的,轻量2核4G配置都差不多的&a…...
《低代码指南》——AI低代码维格云架构视图怎么用?
架构视图是一个展示信息层级关系的视图,轻轻拖拽卡片,就能搭建精巧缜密的企业组织架构视图、实现信息结构化。 利用好架构视图,可以很好地解决以下场景: 展示企业/团队的组织关系 可视化管理产品开发架构 统筹全员 OKR 完成情况 架构视图的基础知识 架构视图分为以下几个…...
ubuntu16.04安装vscode遇到的code 依赖于 libnss3 (>= 2:3.30)解决
ubuntu16.04安装vscode遇到的code 依赖于 libnss3 (> 2:3.30);然而:系统中 libnss3:amd64 的版本为 2:3.28.4-0ubuntu0.16.04.14解决方法 一开始下载了最新版本的vscode,安装时出现了上面的错误状况,最新版本的依赖库版本过低的…...
它来了,xhadmin多应用Saas框架正式发布!
这是一个很激动人心的时刻,因为这个框架从立项到现在已经一年多的时间了,先给大家欣赏下颜值吧。 上图所示是总后台的首页截图,看上去还是比较满意的,不过后续还有一些小小的调整。 应用市场的效果也是相当炸裂的,整体…...
PixMIM论文笔记
论文名称:PixMIM: Rethinking Pixel Reconstruction in Masked Image Modeling 发表时间:2023 年 3 月 4 日 作者及组织:上海人工智能实验室、西蒙菲莎大学、香港中文大学 GitHub:https://github.com/open-mmlab/mmselfsup/tree/d…...
设计师首选:最佳的5款网页设计软件
对于UI设计师来说,网页设计是一项必要的技能。如何做好网页设计?除了设计理念,网页设计和制作软件的应用也是不可或缺的。目前市场上有很多这样的软件,工人要想做好,就必须先磨利工具。如果他们想做网页设计࿰…...
ES6箭头函数
1.箭头函数的写法 如果函数内有返回值 var fn v > v 1 返回值如果不是表达式(是对象),用()括号返回 var fn v > ({ obj: v }) 返回值如果不是表达式(也不是对象)方法体内按正常写 var fu () > {var a 1;var b a1;return b; } 2.箭头函数和普通…...
毫米波雷达在环境监测中的关键作用
随着环境问题的日益凸显,精确、实时的环境监测成为了保护地球的关键一环。在这个背景下,毫米波雷达技术逐渐崭露头角,以其在环境监测中的独特优势成为不可或缺的工具。本文将探讨毫米波雷达在环境监测中的关键作用,以及它是如何应…...
print() 函数
二、print() 函数 这里先说一下 print() 函数,如果你是新手,可能对函数不太了解,没关系,在这里你只要了解它的组成部分和作用就可以了,后面函数这一块会详细说明的。 print() 函数由两部分构成 : 指令&a…...
PyQt 小程序
设备管理程序 v0.0.1.0, 终于出了一个基础版本,… … 两个字典的键值判断 辛亏用的是Python 这个编码时间大大缩短了...
大模型技术实践(五)|支持千亿参数模型训练的分布式并行框架
在上一期的大模型技术实践中,我们介绍了增加式方法、选择式方法和重新参数化式方法三种主流的参数高效微调技术(PEFT)。微调模型可以让模型更适合于我们当前的下游任务,但当模型过大或数据集规模很大时,单个加速器&…...
[正式学习java②]——数组的基本使用,java内存图与内存分配
一、数组的两种初始化方式 1.完整格式(静态初始化) 数据类型[] 数组名 new 数据类型[]{元素1,元素2…}; //范例 int[] arr new int[]{1,2,3,4}; 简化书写 一般我们会省略后面的 new 数据类型[] int[] arr {1,2,3,4}; 2.动态初始化 当不知道数组里面的初始值的时候…...
ESP32网络开发实例-TCP服务器数据传输
TCP服务器数据传输 文章目录 TCP服务器数据传输1、IP/TCP简单介绍2、软件准备3、硬件准备4、TCP服务器实现本文将详细介绍在Arduino开发环境中,实现一个ESP32 TCP服务器,从而达到与TCP客户端数据交换的目标。 1、IP/TCP简单介绍 Internet 协议(IP)是 Internet 的地址系统,…...
四川天蝶电子商务有限公司抖音电商服务引领行业标杆
随着电子商务的飞速发展,四川天蝶电子商务有限公司作为一家领先的抖音电商服务提供商,已经脱颖而出。本文将详细解析四川天蝶电子商务有限公司的抖音电商服务,让您一探究竟。 一、卓越的服务理念 四川天蝶电子商务有限公司始终坚持以客户为中…...
复制活动工作表和计数未保存工作簿进行
我给VBA下的定义:VBA是个人小型自动化处理的有效工具。可以大大提高自己的劳动效率,而且可以提高数据的准确性。我这里专注VBA,将我多年的经验汇集在VBA系列九套教程中。 作为我的学员要利用我的积木编程思想,积木编程最重要的是积木如何搭建…...
ORA-12541:TNS:no listener 无监听程序
问题截图 解决方法 1、删除Listener 新建一个新的 2、主机为服务器ip 3、设置数据库环境 只需要设置实例名不需要设置路径 4、服务命名 一样设置为ip 服务名与监听名一直 eg:orcl...
UE 多线程
详细参考:《Exploring in UE4》多线程机制详解[原理分析] - 知乎 (zhihu.com) UE4 C基础 - 多线程 - 知乎 (zhihu.com) 多线程的好处 通过为每种事件类型的处理分配单独的线程,能够简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程…...
BootStrap5基础入门
BootStrap5 项目搭建 1、引入依赖 从官网 getbootstrap.com 下载 Bootstrap 5。 或者Bootstrap 5 CDN <!-- 新 Bootstrap5 核心 CSS 文件 --> <link rel"stylesheet" href"https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.c…...
企业文件防泄密软件!好用的文件加密系统推荐
由于众多企业内部都有大量的机密数据以电子文档的形式存储着,且传播手段多样,很容易造成文件泄密的问题发生。若是员工通过网络泄密重要文件,或是有黑客入侵窃取机密数据等,造成重要文件被非法查看盗取,都会给企业业务…...
【LLM微调范式1】Prefix-Tuning: Optimizing Continuous Prompts for Generation
论文标题:Prefix-Tuning: Optimizing Continuous Prompts for Generation 论文作者:Xiang Lisa Li, Percy Liang 论文原文:https://arxiv.org/abs/2101.00190 论文出处:ACL 2021 论文被引:1588(2023/10/14&…...
实验2.1.3 交换机的远程配置
实验2.1.3 交换机的远程配置 一、任务描述二、任务分析三、实验拓扑四、具体要求五、任务实施(一) password认证1. 进入系统视图重命名交换机的名称为SWA2. 关闭干扰信息3. 设置vty为0-44. 设置认证方式为password5. 设置登录密码为:huawei6.…...
延吉做网站/网站优化及推广方案
Gcd最大公约数 python def gcd(a, b):if a % b 0:return belse:return gcd(b, a % b)C 简单原始版本: int gcd(int a,int b){return a%b 0 ? b : gcd(b,a%b); }简化一点 int gcd(int a,int b){return b 0 ? a : gcd(b,a%b); }再简化一点 int gcd(int a,i…...
专做正品的护肤品网站/专业的网页制作公司
前言 最近碰到这样一个需求,用户需要批量打包下载sharepoint文档库中的文档,所以,就需要开发一个打包下载的服务。 然后,把打包的代码分享给大家,也许会有需要的人。 static void Main(string[] args){string filesPat…...
做网站的的需求文档/seo关键词查询
前言 很久没有写博客了,这次手写spring,其实网上教程蛮多的。有朋友推荐在github上推荐给我了一个手写的spring。 这里我也仅仅是针对spring进行一个复习。 这里的手写也是简单的手写,主要是用来了解spring IOC 和AOP 的一些相关知识。所以希…...
网站一级栏目/免费网站安全软件下载
说明:为了加强线上数据库安全,避免研发人员误操作造成数据的丢失,制作本文档。一线运维人员可以参考!一、数据备份:专用数据库备份服务器,定时对数据库进行热备、冷备,即主从设置、mysqldump冷备、mysql-bin-log日志备…...
网站建设案例展示/随州今日头条新闻
要使用输入输出函数,首先要: #include <iostream> using namespace std;C使用一个预处理器,该程序在进行主编译之前对源文件进行处理,它处理以#开头的编译指令,不必执行任何特殊的操作来调用该预处理器…...
自建网站推广方式/网页怎么做
Android实现购物车及其他功能的角标发布时间:2020-09-13 07:37:05来源:脚本之家阅读:98作者:瞳瞳色丶轻烟1.先来张效果图2.自定义一个角标工具类BottomBarView 。*** Created by Administrator on 2016/12/27.* 角标工具类*/publi…...