跨域cookie携带实践

前端请求接口携带cookie报错Refused to set unsafe header

前言

在开发中后端接口和前端由于不在一个电脑/ip上开发,由于浏览器存在的同源策略参考MDN,在进行调试的时候久会出现跨域的问题

常见的解决方法有jsonp,添加跨域头等方法,实际上最简单的还是通过设置跨域浏览器来实现跨域请求

实现方法参考:跨域浏览器

存在的问题

使用跨域浏览器开发时,简单的跨域请求都不会被浏览器拦截,而携带cookie进行请求时就会报错refused to set unsafe header

解决方式1

  • 后端设置响应头:
1
2
Access-Control-Allow-Credentials: true #允许客户端携带跨域cookie
Access-Control-Allow-Origin: www.abc.com #允许访问跨域资源 不同使用通配符,只能使用单一的地址
  • 同时前端需要设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 原生
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.withCredentials = true; // 携带跨域cookie
xhr.send();

// jquery
$.ajax({
type: "GET",
url: url,
xhrFields: {
withCredentials: true // 携带跨域cookie
},
processData: false,
success: function(data) {
console.log(data);
}
});

// axios
axios.defaults.withCredentials=true; // 让ajax携带cookie
  • 通过设置后仍然不能携带cookie,继续尝试解决

解决方式2

  • 通过后端返回cookie,在后端和postman都能看到有cookie出现,但是在前端部分控制台看不到cookie的响应头

  • 查找以后发现后端未设置cookie的domain属性,仅请求的地址能显示cookie

  • domain:表示cookie所在的域,默认为请求的地址,如网址为JavaScript.exam.cn/JavaScript/read.html,那么domain默认为JavaScript.exam.cn。如域A为catagory.exam.cn,域B为JavaScript.exam.cn,那么在域A生产一个令域A和域B都能访问的cookie就要将该cookie的domain设置为.exam.com;如果要在域A生产一个令域A不能访问而域B能访问的cookie就要将该cookie的domain设置为JavaScript.test.com

解决方式3

  • 后端通过setDomain()方法设置domain属性后,在Postman发现仍然没有修改成功domain值,因为两台电脑的地址都是ip地址,设置domain为ip地址后似乎没用?

  • 通过查找,网上大多的设置的domain值都是以"."开头的域名,设置为.google.com,则所有以google.com结尾的域名都可以访问该Cookie。注意第一个字符必须为"."

解决方式4

  • 简单粗暴,自己电脑上跑一个后端的项目,这样和前端就处于同一个地址,可以获取cookie :)

以上解决供大家参考。也记录一下

小程序bug汇总

2019-01-30更新

1.关于自定义顶部导航栏Android/IOS高度不一致的问题

效果

小程序可以实现自定义顶部导航栏,在app.json中设置

1
2
3
4
5
{
"window": {
"navigationStyle": "custom"
}
}

开启之后页面会顶到屏幕最上端,需要自行完成布局

推荐一个自己写的小demo Indomi/wxNavbar

但是在Android/IOS中,导航栏的高度是不一致的,在IOS中,通过wx.getSystemInfo获得的系统信息可以得到

IOS

导航栏高度 = screenHeight - statusBarHeight - windowHeight = 44

在安卓端

Android

导航栏高度 = screenHeight - statusBarHeight - windowHeight = 48

不知道是不是系统层面设计风格不同的原因,在实际开发中使用的是以48为准

额外的tips:

大部分手机的statusBarHeight为20,但是iphoneX的statusBarHeight为44

小程序scroll-view垂直滑动宽度出现白边

bug复现

scroll-view在设置为纵向滑动时,上下滑动过程中左右滑动,其内部的元素右边会滑动出一个白边,如图

右侧白边

先留坑,等解决了再写

小程序cover-view实践踩坑

小程序使用cover-viewcover-image

近期有需求,使用video组件播放视频,在使用手机流量的情况下加一层遮罩来提醒,点击按钮关闭遮罩开始播放

类似效果

  • 最好还要加入一行标题,当前正在使用非wifi环境,观看需要使用手机流量

  • 因为前期看过cover-*的两个组件,这个场景刚好适合,video作为原生组件,层级最高,使用z-index也不能改变叠放

  • cover-view只能嵌套相同cover-*的组件和button

根据以上需求和限制大致的思路如下:

1
2
3
4
5
6
<video src="http://xxx.mp4">
<cover-view>
当前正在使用非wifi环境,观看需要使用手机流量
<button>使用流量播放</button>
</cover-view>
</video>

调整好css后发现,button并不能挡住video组件正中间的播放按钮,对比同类的实现产品,应该是增加了一层遮罩,显示视频预览图,点击播放后消失,于是增加一层cover-image

1
2
3
4
5
6
7
<video src="http://xxx.mp4">
<cover-view>
当前正在使用非wifi环境,观看需要使用手机流量
<button>使用流量播放</button>
</cover-view>
<cover-image src="http://xxxxx.jpg"></cover-image>
</video>

通过定位将遮罩的图片铺满video组件,但是产生了以下几个bug:

bug1:使用z-index将按钮和标题放在遮罩之上无效

bug2: 编辑器预览正常,但是真机调试后发现图片被cover-view内的标题文字压了下去

在网上大致一搜没有很好的解决方法,cover-view这个组件看来只能够用于一些简单实用的场景,在当前的需求下实现难度很大

解决办法

通过不断对比同类的产品,我发现了一个实现思路:

  • 通过view生成一个跟video等高等宽的块,在view内实现需要的功能,然后再使用wx:ifwx:else去控制提示和视频的显示,可以很轻松的实现想要的效果

这种方法早就应该想到,但是就想着试一试cover-view,搞得很麻烦

Vue-router

Vue-router

1.基本使用

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>

JavaScript

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
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')

// 现在,应用已经启动了!

2.动态路由匹配

2.1使用动态路径参数

1
2
3
4
5
6
7
8
9
10
const User = {
template: '<div>User</div>'
}

const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})

当匹配到参数时,参数值将被设置到this.$route.params

1
2
3
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}

多段路径匹配

模式 匹配路径 $route.params
/user/:username /user/evan {username:'evan'}
/user/:username/post/:post_id /user/evan/post/123 {username: 'evan', post_id: '123'}

复用组件(生命周期钩子不会被调用)

简单watch $route 对象:

1
2
3
4
5
6
7
8
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}

导航守卫

1
2
3
4
5
6
7
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}

2.2通配符捕获路由

匹配任意路径

1
2
3
4
5
6
7
8
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}

通配符匹配的参数,会被赋值到pathMatch

1
2
3
4
5
6
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'

2.3高级路由匹配

使用path-to-regexp

2.4匹配优先级

先匹配到优先级最高

3.嵌套路由

实际场景

1
2
3
4
5
6
7
8
/user/foo/profile                     /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+

示例

1
2
3
4
5
6
7
8
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}

在嵌套的出口渲染组件,需要使用children配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})

提供空的子路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const router = new VueRouter({
routes: [
{
path: '/user/:id', component: User,
children: [
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },

// ...其他子路由
]
}
]
})

4.编程式导航

router.push(location, onComplete?, onAbort?)

点击<router-link :to="...">等同于router.push(...)

声明式 编程式
<router-link :to="..."> router.push(...)

参数选择

1
2
3
4
5
6
7
8
9
10
11
// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意点:提供了pathparams的内容会被忽略

1
2
3
4
5
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

router.replace(location, onComplete?, onAbort?)

该方法不向history添加新记录,而是替换当前记录

声明式 编程式
<router-link :to="..." replace> router.replace(...)

router.go(n)

history记录中向前或者后退多少步,类似于window.history.go(n)

5.命名路由

使用name

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})

链接到命名路由

1
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

等同于router.push()

1
router.push({ name: 'user', params: { userId: 123 }})

6.命名视图

使用场景:同级渲染多个视图,如:

1
2
3
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

多个视图配置多个组件

1
2
3
4
5
6
7
8
9
10
11
12
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})

7.重定向和别名

重定向

普通重定向

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})

命名路由的重定向

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})

带方法的动态重定向

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})

别名

/a的别名是/b,意味着当用户访问/b时,URL保持为/b,但是路由依然匹配/a

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})

8.路由组件传参

在组件中使用$route会使之与其对应的路由形成高度耦合,从而使组件只能在某些特定URL上使用,限制了灵活性

通过props解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },

// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})

布尔模式

如果props设置为trueroute.params将会被设置为组件属性

对象模式

如果props是一个对象,它会被按照原样设置为组件属性,当props是静态的时候有用

函数模式

可以通过创建一个函数返回props,这样可以将参数转换成为另一种类型,将静态值与基于路由的值相结合等等

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})

9.HTML5 History 模式

vue-router默认采用hash模式,使用URL的hash来模拟一个完整的URL

另一种路由的history模式,充分利用history.pushState来完成URL跳转无需重新加载页面

1
2
3
4
const router = new VueRouter({
mode: 'history',
routes: [...]
})

此模式需要后端支持,URL无任何匹配时返回index.html

Node项目docker化

Node项目docker化

1.npm install安装依赖

  • npm版本大于等于5,会自动生成package-lock.json文件,一起被拷贝进docker镜像中

2.创建Dockerfile文件

  • 定义基础镜像
1
FROM node:8
  • 在镜像中创建一个文件夹存放应用程序代码,即应用程序的工作目录
1
2
#Create app directory
WORKDIR /usr/src/app
  • 下一步使用npm安装依赖,使用通配符匹配两个package
1
2
3
4
5
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm install --only=production
  • 在docker镜像中使用COPY命令绑定应用程序
1
COPY . .
  • EXPOSE命令与docker镜像做映射
1
EXPOSE 8080
  • 定义运行时的cmd命令来运行应用程序
1
CMD [ "npm", "start" ]

3.创建.dockerignore文件

在构建镜像时,docker会获取所有位于context目录下的文件。为了增加docker构建的速度,可以在context目录中添加.dockerignore文件来排除不需要的文件与目录

  • 避免本地模块和日志被拷贝入镜像中
1
2
3
.git
node_modules
npm-debug.log

4.构建镜像

  • 进入Dockerfile所在文件目录,运行
1
docker build -t <ImageName> .

注意后面还有一个点

  • 查看构建的镜像
1
docker images

5.运行镜像

  • 使用-d模式运行镜像将以分离模式运行Docker容器,使得容器在后台自助运行。开关符-p在容器中把一个公共端口映射到私有的端口
1
docker run -p 49160:8080 -d <ImageName>
  • 打印应用程序的输出
1
2
3
4
5
6
7
8
# Get container ID
docker ps

# Print app output
docker logs <container Id>

# Example
Running on http://localhost:8080
  • 如果要进入容器中,运行exec命令
1
2
# Enter the container
docker exec -it <container id> /bin/bash

6.测试

  • 测试应用程序连接
1
2
3
4
5
docker ps

# Example
ID |IMAGE
ecce33Bjlask|<ImageName>
1
2
3
4
5
6
7
8
9
10
11
curl -i localhost:49160

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 12
ETag: W/"c-M6tWOb/Y57lesdjQuHeB1P/qTV0"
Date: Mon, 13 Nov 2017 20:53:59 GMT
Connection: keep-alive

Hello world

7.使用Nodemon实现热更新

nodemon是一款很受欢迎的包,它在运行时会监视目录中的文件,当任何文件发生了改变时,nodemon将会自动重启你的node应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM node:8

# 创建 app 目录
WORKDIR /app

# 安装 nodemon 以实现热更新
RUN npm install -g nodemon

# 安装 app 依赖
# 使用通配符确保 package.json 与 package-lock.json 复制到需要的地方。(npm 版本 5 以上)
COPY package*.json ./

RUN npm install

# 打包 app 源码
COPY src /app

EXPOSE 8080
CMD [ "nodemon", "server.js" ]

8.优化

8.1 使用COPY来代替ADD

  • Dockerfile中,除非需要自动解压 tar 文件,否则最好使用 COPY 来代替 ADD。

8.2 使用node原生命令

1
2
3
4
5
#推荐使用
CMD [ "node", "app.js" ]

#来代替
CMD [ "npm", "app.js" ]
  • 绕过package.jsonstart命令,这样可以减少在容器中运行的进程数量,同时还能让Node.js进程接收到SIGTERMSIGINT等退出信号,如果是npm则会无视这些信号

8.3 使用--init标志

  • tini轻量集初始化系统来包装进Node.js进程,它们也能响应一些SIGTERM(CTRL-C)之类的内核信号,例如:
1
2
docker run -it --init -p 8080:8080 -v $(pwd):/app \
keel-docker-dev bash

在CentOS7安装Docker

在CentOS7安装Docker

1.查看内核版本

1
uname -r
  • Docker要求CentOS系统的内核版本高于3.1.0

2.使用root权限更新yum

1
sudo yum update

3.卸载旧版本

1
sudo yum remove docker  docker-common docker-selinux docker-engine

4.安装需要的软件包

  • yum-util提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
1
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

5.设置yum源

1
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

6.查看所有仓库中所有docker的版本

1
yum list docker-ce --showduplicates | sort -r

7.安装docker

1
2
sudo yum install docker-ce #由于repo中默认只开启stable仓库,所以安装稳定版
sudo yum install <version> #例如 sudo yum install docker-ce-17.12.0.ce

8.启动并加入开机启动

1
2
sudo systemctl start docker
sudo systemctl enable docker

9.验证是否安装成功

  • 有client和service两部分表示安装启动都成功了
1
docker version

ubuntu安装后的相关配置

整理一下安装一个新的ubuntu以后的相关配置

  1. 软件源更换

备份
cd /etc/apt/
sudo cp sources.list sources.list.bak
替换源
vi sources.list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# aliyun
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties
deb http://archive.canonical.com/ubuntu xenial partner
deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse

sudo apt-get update 更新源
sudo apt-get upgrade 更新软件

shell脚本运行完不退出窗口

read -n 1

adb shell 中am命令

  • am命令

am全称是activity manager,使用am去模拟各种系统的行为,例如启动一个activity,强制停止进程,发送广播进程,修改设备屏幕属性等

am <command>
也可以加adb shell逐条运行
eg:adb shell am start -a android.intent.action.VIEW

Read More