使用
xcode
来管理自动生成证书,不需要在管理后台创建
然后到后面就看到自动创建的证书了
上传到蒲公英内测 https://www.pgyer.com
最后把应用地址复制发给测试人员下载即可,需要注意的是我们要添加测试手机的设备UUID才可以安装到手机上测试
需要注意的是:设备添加超过
20个
,需要等待24小时
才能在iPhone手机上安装APP测试。添加新的额设备udid,需要重新打包ipa包才能进行安装到对应手机上
上传成功后,来到app 后台
打包后,在app store后台选择最近的构建版本
这里我们使用安卓
9.0 Google APIs
的模拟器(装最新版本也可以),记得要装Google APIs
的,否则执行adb root
获取root
权限会报错adbd cannot run as root in production builds
emulator -list-avds |
# 需要以这样的方式启动安卓模拟器才可转到包 |
adb root |
命令执行完之后,模拟器会重新启动。如果启动成功,那么手机的root
权限已开启
openssl x509 -subject_hash_old -in charles-ssl-proxying-certificate.pem |
adb push charles-ssl-proxying-certificate.pem /system/etc/security/cacerts/xxx.0 |
xxx.0
是上面的hash值
例如dfaf1.0
adb shell
,执行reboot
重启模拟器,切记:一定要重启模拟器证书才会生效
看到charles
证书安装到系统目录才算成功
Sentry
是一套开源的实时的异常收集、追踪、监控系统。这套解决方案由对应各种语言的 SDK 和一套庞大的数据后台服务组成,通过 Sentry SDK 的配置,还可以上报错误关联的版本信息、发布环境。同时 Sentry SDK 会自动捕捉异常发生前的相关操作,便于后续异常追踪。异常数据上报到数据服务之后,会通过过滤、关键信息提取、归纳展示在数据后台的 Web 界面中
支持如下语言
sentry功能架构
sentry核心架构
sentry是开源的,如果我们愿意付费的话,sentry给我们提供了方便。省去了自己搭建和维护 Python 服务的麻烦事
登录官网 https://sentry.io 注册账号后接入sdk即可使用
Sentry 的管理后台是基于
Python Django
开发的。这个管理后台由背后的 Postgres 数据库(管理后台默认的数据库)、ClickHouse
(存数据特征的数据库)、relay、kafka、redis 等一些基础服务或由 Sentry 官方维护的总共 23 个服务支撑运行。可见的是,如果独立的部署和维护这 23 个服务将是异常复杂和困难的。幸运的是,官方提供了基于 docker 镜像的一键部署实现 getsentry/onpremise
sentry 本身是基于 Django 开发的,而且也依赖到其他的如 Postgresql、 Redis 等组件,所以一般有两种途径进行安装:通过 Docker 或用 Python 搭建
前置环境
需要安装对应版本,否则安装会报错
Docker 19.03.6+
Docker-Compose 1.28.0+
4 CPU Cores
8 GB RAM
20 GB Free Disk Space
安装工具包
yum install yum-utils device-mapper-persistent-data lvm2 -y |
设置阿里镜像源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
安装docker
yum install docker-ce docker-ce-cli containerd.io -y |
启动docker
systemctl start docker |
docker 镜像加速(重要)
在后续部署的过程中,需要拉取大量镜像,官方源拉取较慢,可以修改 docker 镜像源
登录阿里云官网,打开 阿里云容器镜像服务。点击左侧菜单最下面的 镜像加速器
,选择 Centos
vi /etc/docker/daemon.json |
{ |
然后重启docker即可
# 重新加载配置 |
安装docker-compose
# 使用国内源安装 |
设置docker-compose执行权限
chmod +x /usr/local/bin/docker-compose |
创建软链
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose |
测试是否安装成功:
$ docker-compose --version |
git clone https://github.com/getsentry/onpremise |
在
onpremise
的根路径下有一个install.sh
文件,只需要执行此脚本即可完成快速部署,脚本运行的过程中,大致会经历以下步骤:
docker volume
数据卷创建(可理解为 docker
运行的应用的数据存储路径的创建)cd onpremise |
后续一步一步安装下来
设置管理员账号(如果跳过此步,可手动创建)
在执行结束后,会提示创建完毕,运行 docker-compose up -d
启动服务
docker-compose up -d |
查看服务运行状态docker-compose ps
所有服务都启动成功后,就可以访问
sentry
后台了,后台默认运行在服务器的9000
端口,这里的账户密码就是安装时让你设置
的那个
点击头像User settings - Account Details
的相应菜单设置,刷新后生效
npm i @vue/cli -g |
# Using npm |
// src/main.js |
我们手动抛出异常,在控制台可见捕获了错误
为了方便查看具体的报错内容,我们需要上传sourceMap
到sentry
平台。一般有两种方式 sentry-cli
和 sentry-webpack-plugin
方式,这里为了方便采用webpack
方式
source-map
是一个文件,可以让已编译过的代码可以映射出原始源;source-map
就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。webpack方式上传
npm i @sentry/webpack-plugin -D |
修改vue.config.js
配置文件
// vue.config.js |
获取TOKEN
获取org
在项目根目录创建.sentryclirc
url
:sentry部署的地址,默认是https://sentry.io/
org
:控制台查看组织名称project
:项目名称token
:生成token需要勾选project:write
项目写入权限# .sentryclirc |
执行项目打包命令,即可把js下的sourcemap
相关文件上传到sentry
npm run build |
上传后的sourcemap
在这里可以看到
正确上传过 source-map
的项目,可以看到很清晰的报错位置
进入本地打包的dist,
http-server -p 6002
启动一个模拟正式环境部署的服务访问看看效果
还可以通过 面包屑
功能查看,报错前发生了什么操作
记得别把sourcemap文件传到生产环境,又大又不安全 删除sourcemap
, 基于vue2演示的三种方式
// 方式1 |
Sentry.init()
中,new Integrations.BrowserTracing()
的功能是将浏览器页面加载和导航检测作为事物,并捕获请求,指标和错误。
TPM
: 每分钟事务数FCP
:首次内容绘制(浏览器第第一次开始渲染 dom 的时间点)LCP
:最大内容渲染,代表 viewpoint
中最大页面元素的加载时间FID
:用户首次输入延迟,可以衡量用户首次与网站交互的时间CLS
:累计布局偏移,一个元素初始时和消失前的数据TTFB
:首字节时间,测量用户浏览器接收页面的第一个字节的时间(可以判断缓慢来自网络请求还是页面加载问题)USER
:uv
数字USER MISERY
: 对响应时间难以忍受的用户指标,由 sentry
计算出来,阈值可以动态修改yarn create vite |
npm i @sentry/vue @sentry/tracing |
src/main.js
中修改
import { createApp } from "vue"; |
修改
vite.config.js
配置
安装npm i vite-plugin-sentry -D
插件
import { defineConfig } from 'vite' |
此时当执行
vite build
时,viteSentry
这个插件会将构建的sourcemap
文件上传到sentry
对应的项目release
之下。当次版本新捕获到错误时就可以还原出错误行,以及详细的错误信息。
使用umi项目接入演示
mkdir umi-sentry && cd umi-sentry |
# Using npm |
初始化sentry
// pages/index.ts |
手动抛出异常查看是否能正确上报到sentry
[auth] |
npm i @sentry/webpack-plugin -D |
// .umirc.ts 修改 |
# 执行打包上传sourcemap |
修改代码抛出异常,查看控制台sourcemap解析的效果
注意:npm run build之后,不要把sourcemap上传到生产环境,记得删除
在上传的 issues 里面,我们可以借助 setUser 方法,设置读取存在本地的用户信息。(此信息需要持久化存储,否则刷新会消失)
// app/main.js |
Sentry.ErrorBoundary
。加了错误边界,可以把错误定位到组件上面。npm i @sentry/rrweb rrweb -S |
import SentryRRWeb from '@sentry/rrweb'; |
在报错后,可以录屏播放错误发生的情况
issues
,performance
超过我们设置的阈值,会触发 alert
。GraphQL 是一种新的 API 的查询语言,它提供了一种更高效、强大和灵活 API 查询。它 是由 Facebook 开发和开源,目前由来自世界各地的大公司和个人维护。GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且 没有任何冗余。它弥补了 RESTful API(字段冗余,扩展性差、无法聚合 api、无法定义数据 类型、网络请求次数多)等不足
注意:GraphQL 是 api 的查询语言,而不是数据库。从这个意义上说,它是数据库无关的, 而且可以在使用 API 的任何环境中有效使用,我们可以理解为 GraphQL 是基于 API 之上的一 层封装,目的是为了更好,更灵活的适用于业务的需求变化
GraphQL 可以用在常见各种服务器端语言以及客户端语言中
客户端语言:js、React + React Native、Angular、Vue.js、Apollo Link、Native iOS、Native Android、 Scala.js
中文文档:http://graphql.cn
GraphQL 出现的历史背景
当提起API设计的时候,大家通常会想到SOAP(一种简单的基于 XML 的协议),RESTful 等设计方式,从 2000 年 RESTful 的理论被提出的时候,在业界引起了很大反响,因为这种 设计理念更易于用户的使用,所以便很快的被大家所接受。
我们知道 REST 是一种从服务 器公开数据的流行方式。当 REST 的概念被提及出来时,客户端应用程序对数据的需求相 对简单,而开发的速度并没有达到今天的水平。
因此 REST 对于许多应用程序来说是非常 适合的。然而在业务越发复杂,客户对系统的扩展性有了更高的要求时,API 环境发生了巨 大的变,RESTful 显得心有余而力不足。比如:字段冗余,扩展性差、无法聚合 api、无法 定义数据类型、网络请求次数多
GraphQL 的出现整好弥补了 RESTful APi 的不足
使用 GraphQL 的公司
目前已经有很多的公司在使用 GraphQL(https://graphql.org/users/)
在过去的十多年中,REST 已经成为设计 web api 的标准(虽然只是一个模糊的标准)。
它提供了一些很棒的想法,比如无状态服务器和结构化的资源访问。
然而 REST api 表 现得过于僵化,无法跟上访问它们的客户的快速变化的需求
客户端可以自定义 Api 聚合
如果设计的数据结构是从属的,直接就能在查询语句中指定;即使数据结构是独 立的,也可以在查询语句中指定上下文,只需要一次网络请求,就能获得资源和子 资源的数据。
代码即是文档
GraphQL 会把 schema 定义和相关的注释生成可视化的文档,从而使得代码的变更,直接就反映到最新的文档上,避免 RESTful 中手工维护可能会造成代码、 文档不一致的问题
参数类型强校验
可以将GraphQL的类型系统分为
标量类型
(ScalarTypes,标量类型)和其他高级数据类型
,标量类型即可以表示最细粒度数据结构的数据类型,可以和JavaScript的原始类型对应
GraphQL规范目前规定支持的标量类型有
32
位整数 – GraphQLInt
GraphQLFloat
UTF‐8
字符序列 – GraphQLString
true
或者false
– GraphQLBoolean
GraphQL其他高级数据类型包括
用于描述层级或者树形数据结构。对于树形数据结构来说,叶子字段的类型都是标量数据类型。几乎所有GraphQL类型都是对象类型。Object类型有一个name字段,以及一个很重要的fields字段。fields字段可以描述出一个完整的数据结构。例如一个表示地址数据结构的GraphQL对象为
const AddressType=newGraphQLObjectType({ |
Interface
:接口用于描述多个类型的通用字Union
:联合类型用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型Enum
:枚举用于表示可枚举数据结构的类型InputObject
:输入对象List
:列表列表是其他类型的封装,通常用于对象字段的描述。例如下面PersonType类型数据的parents和children字段
const PersonType=newGraphQLObjectType({ |
Non-Null
:不能为Null
Non-Null
强制类型的值不能为null
,并且在请求出错时一定会报错。可以用于必须保证值不能为null
的字段。例如数据库的行的id字段不能为null
const RowType=newGraphQLObjectType({ |
GraphQL规范支持两种操作
query
:仅获取数据(fetch
)的只读请求mutation
:获取数据后还有写操作的请求新版本的GraphQL还支持
subscription
,这是为了处理订阅更新这种比较复杂的实时数据更新场景而设计的操作
使用mongodb
做数据库演示,mac安装mongodb
,brew install mongodb-community
# 进入mongo shell |
或者导入数据库数据
下载数据库文件解压并导入mongodb即可 https://blog.poetries.top/db/koa.zip
导入mongodb数据库
mongorestore -h localhost:27017 -d koa-demo(数据库名称,不存在会自动创建) ./dump(本地数据文件路径) |
https://github.com/graphql/express-graphql
npm install express-graphql graphql--save |
引入express-graphql配置中间件
// app.js |
schema/default.js
Schema
const DB=require('../model/db.js'); /*引入DB库*/ |
/** |
打开本地调试
下载数据库文件解压并导入mongodb即可 https://blog.poetries.top/db/koa.zip
mongorestore -h localhost:27017 -d koa-demo(数据库名称,不存在会自动创建) ./dump(本地数据文件路径)
mongodump -h localhost:27017 -d test(数据库名称) -o ./dump
npm install graphql koa-graphql koa-mount --save |
实现导航列表API、文章分类API、文章列表API、文章详情API 、文章列表分页查询API、以及文章列表关联文章分类实现聚合API
// app.js |
// schema/default.js |
// model/db.js |
启动服务
聚合查询文章分类信息,分类信息的方式要放在article的schema里面,这样才能聚合查询到
聚合查询结果
查询订单,聚合查询订单关联的商品信息返回,实现类似以下效果
// schema/default.js |
查询订单详情
需要哪些字段,就返回哪些字段,编辑器会自定提示
//定义文章分类的schema |
// scehma/default.js |
可以看到必填字段不填会提示
再次查询列表
ApolloBoost是一种零配置开始使用ApolloClient的方式。它包含一些实用的默认值,例如我们推荐的InMemoryCache和HttpLink,它非常适合用于快速启动开发。将它与vue-apollo和graphql一起安装:
npm install vue-apollo graphql apollo-boost --save |
src/main.js
中引入apollo-boost
模块并实例化ApolloClient
import ApolloClient from'apollo-boost' |
可以打开 http://118.123.14.36:3002/graphql 在控制台查看查询结果
src/main.js
配置vue-apollo
插件import VueApollofrom'vue-apollo' |
Apollo provider
Provider保存了可以在接下来被所有子组件使用的Apollo客户端实例
const apolloProvider = newVueApollo({ |
使用apollo Provider
选项将它添加到你的应用程序
new Vue({ |
组件加载的时候就会去服务器请求数据,请求的数据会放在
navList
这个属性上面,在模板中可以直接使用当前属性
import gql from'graphql-tag'; |
另一种写法:
import gql from 'graphql-tag'; |
完整例子
<template> |
<div class="news"> |
逻辑
import gql from 'graphql-tag'; |
<template> |
服务器端接口
<template> |
可以看到新增成功效果
npm i vue-infinite-scroll -S |
// main.js配置 |
<template> |
https://vue-apollo.netlify.app/zh-cn/guide/apollo/pagination.html
<template> |
分页效果
项目例子完整代码下载地址 https://blog.poetries.top/assets/graphql-code.zip
CentOS
、Ubuntu
、Debian
,CentOS
现在市场占有率第一init 0
关机 init 6
重启 ls
、 ls -l
、 ll
列出出当前目录下的文件 cd
切换目录pwd
查看当前路径ctrl+c
中断当前程序ctrl+l / (clear)
清屏ifconfig/ipconfig
查看网卡信息ping 127.0.0.1
看网络是否通畅useradd zhangsan
passwd zhangsan
userdel -rf zhangsan
-r
:递归的删除目录下面文件以及子目录下文件。touch file
rm -rf file
-r
:递归的删除目录下面文件以及子目录下文件。-f
:强制删除,忽略不存在的文件,从不给出提示 mv file1 file11
cat file1
cp file2 file22
mv file1 file11
vi file1
touch file{1..10}
rm -rf file{1..10}
cat file1 | head -3
cat file1 | tail -3
find
查找文件find / -name httpd.conf
查找当前目录下的文件名为 httpd.conf
的文件find
目录 -name
文件名httpd.conf
里面有listen
cat httpd.conf | grep listen
cat httpd.conf | grep -ignore listen / cat httpd.conf | grep -i listen
忽略大小写vi httpd.conf
/Listen
搜索Listen
N
下一个mkdir dir1 dir2 dir3
rm -rf dir1 dir2
-r
:递归的删除目录下面文件以及子目录下文件。-f
:强制删除,忽略不存在的文件,从不给出提示rm -rf dir*
以dir
开头的所有文件删除mv dir1 dir11
ls / ll
mkdir -p a/b/c/d/e/f/g
创建多层级目录tree a
tree命令不存在的话需要安装 yum install tree -y
cp -rf wwwroot/ mywwwroot/
yum install -y unzip zip
zip -r public.zip public
-r
递归 表示将指定的目录下的所有子目录以及文件一起处理unzip public.zip
unzip public.zip -d dir
unzip -l public.zip
tar czvf public.tar.gz public
tar xzvf public.tar.gz
tar tf public.tar.gz
tar cvf wwwroot.tar wwwroot
仅打包,不压缩!tar xvf wwwroot.tar
- `tar cvf xxx.tar xxx` 这样创建xxx.tar文件先,- `xz xxx.tar` 将 xxx.tar压缩成为 xxx.tar.xz 删除原来的tar包- `xz -k xxx.tar` 将 xxx.tar压缩成为 xxx.tar.xz 保留原来的tar包
- `xz -d ***.tar.xz` 先解压xz 删除原来的xz包- `xz -dk ***.tar.xz` 先解压xz 保留原来的xz包- `tar -xvf ***.tar` 再解压tar
xz -l ***.tar.xz
先解压xz- `alias chttp='cat /etc/httpd/conf/httpd.conf'` - `chttp`
unalias chttp
alias
useradd lisi
passwd lisi
userdel -r lisi
-r
:递归的删除目录下面文件以及子目录下文件。- 备注:删除用户的时候用户组被删除
id user
gpasswd -a testuser root
testuser
加入到root
组,加入组后testuser
获取到user
组及root
组所有权限gpasswd -d testuser root
rwx
当前用户对mnt有读写执行权限 u
r-x
当前用户的组对mnt文件有读和执行 g
r-x
其他用户对mnt也具有读和执行 o
r
读w
写x
执行user u
group g
other o
all a
u+g+o=a
(表示所有人) r
查看目录里面的文件(4)w
在目录里创建或删除文件(2)x
切换进目录(1)r
查看文件内容w
在文件里写内容x
执行该文件(文件不是普通文件,是程序或脚本)+
增加权限 -删除权限chmod u+x my.sh
给当前用户分配执行my.sh
的权限chmod o+r,o+w file.txt
给其他用户分配对file.txt
的读写权限chmod o+r,o+w,o+x mnt/
给所有其他用户分配对mnt目录的进入、读取、写入权限chmod -R o+r,o+w,o+x mnt/
修改目录下的所有文件的权限为可读、可修改、可执行chmod 755 file
chmod -R 777 wwwroot/
修改目录下的所有文件的权限为可读、可修改、可执行mount dev/cdrom /media
挂载df
查看光盘是否挂载- 卸载`umount /media`
- `rpm -ivh` `rpm`软件包
rpm
卸载软件rpm -e net-tools
net-tools
表示要卸载的软件包rpm
软件包的安装位置 / 软件包是否安装 rpm -ql net-tools
- `yum install -y net-tools` 包括 `netstat` `ifconfig`等命令- `yum install -y unzip zip` `zip`压缩减压- `yum install -y mlocate` `updatedb`- `yum install -y wget` 下载文件包- `yum -y install psmisc` `pstree | grep httpd` 查看进程 `pstree -p` 显示进程以及子进程
yum
卸载rpm
包yum -y remove wget
yum
搜索npm
包yum search
名称yum
查看rpm
包yum list
yum list | grep httpd
yum list updates
列出所有可更新的软件包yum list installed
列出所有已安装的软件包yum info package1
yum info httpd
yum info zip
yum info unzip
- `yum -y install httpd` `service httpd start` 安装启动`apache`- 启动`apache`- 关闭防火墙 `systemctl stop firewalld`
yum
的主配置文件 etc/yum.conf
yum
的仓库配置文件 /etc/yum.repo.d/*.repo
sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
yum search nginx
看看是否已经添加源成功。如果成功则执行下列命令安装Nginx。npm info nginx
也可以看看nginx
源是否添加成功sudo yum install -y nginx
sudo systemctl start nginx.service
sudo systemctl enable nginx.service
gcc
,make
,openssl
如下:yum install -y gcc make gcc-c++ openssl-devel
rpm -qa | grep gcc / rpm -ql gcc
- 生成编译配置文件(`Makefile`)- 开始编译(`make`)- 开始安装(`make install`)
httpd-2.2.9.tar.gz
源代码:./configure --prefix=/usr/local/nodejs
安装路径设置为/usr/local/apache
make / make -j4
make install
- 结束当前源代码进程- 删除源代码 - 如:结束进程 - `pstree|grep httpd` - `pkill httpd` - 删除源代码 - `cd /usr/local/` - 直接删除源代码 `rm -rf apache/`
usr/local/nodejs
目录./configure
make / make -j4
make install
- `top` - 15:31:47 up 9:30, 3 users, load average: 0.00, 0.02, 0.05- 依次对应:系统当前时间 up 系统到目前为止i运行的时间, 当前登陆系统的用户数量, load average后面的三个数字分别表示距离现在一分钟,五分钟,十五分钟的负载情况。
who
命令: 显示当前正在系统中的所有用户名字,使用终端设备号,注册时间。 whoami
: 显示出当前终端上使用的用户。 last
: last
作用是显示近期用户或终端的登录情况 - `pstree` 查看进程树 - `pstree -ap` 显示所有信息 - `pstree | grep httpd` - `pstree -ap | grep httpd` - `ps -au` - `ps -au |grep httpd` - `ps -aux`- `ps` 中`aux`的含义: - 显示现行终端机下的所有程序,包括其他用户的程序(`a`) - 以用户为主的格式来显示程序状况。 (`x`) - 显示所有程序,不以终端机来区分(`u`)
pkill httpd
pkill
进程的名字kill 2245
kill
进程号kill -9 1234
kill -9
进程号 强制杀死kill:执行
kill`命令,系统会发送一个SIGTERM信号给对应的程序。当程序接收到该signal信号后,将会发生以下事情:kill -9
: kill -9
命令,系统给对应程序发送的信号是SIGKILL,即exit
。exit
信号不会被系统阻塞,所以kill -9
能顺利杀掉进程。netstat -tunpl |grep httpd
- `df`命令作用是列出文件系统的整体磁盘空间使用情况。可以用来查看磁盘已被使用多少空间和还剩余多少空间。- `df -h` 以人们易读的方式显示,总共多少g用了多少g- `df /home` 查看该文件夹所在磁盘的使用情况
systemctl
管理服务yum
安装httpd
yum install -y httpd
systemctl start httpd
systemctl
管理服务systemctl start httpd
systemctl stop httpd
systemctl restart httpd
systemctl status httpd
systemctl is-active httpd
systemctl list-units -t service
systemctl list-units -at service
注意顺序 systemctl enable httpd
systemctl disable httpd
systemctl list-unit-files|grep enabled`
systemctl list-unit-files|grep disabled`
systemctl list-unit-files|grep disabled | grep httpd`
systemctl reload httpd
firewalld
的基本使用:systemctl start firewalld
systemctl stop firewalld
systemctl status firewalld
systemctl disable firewalld
systemctl enable firewalld
firewall-cmd
的基本使用:firewall-cmd --zone=public --add-port=80/tcp --permanent
(–permanent
永久生效,没有此参数重启后失效)firewall-cmd --reload
修改firewall-cmd
配置后必须重启firewall-cmd --zone= public --query-port=80/tcp
firewall-cmd --zone= public --remove-port=80/tcp --permanent
firewall-cmd --zone=public --list-ports
/etc/selinux/config
文件SELINUX=enforcing
改为SELINUX=disabled
ssh,
secure shell protocol
,以更加安全的方式连接远程服务器
# root: 用户名 |
在本地电脑上配置
ssh-config
,对自己管理的服务器起别名,可以更方便地登录多台云服务器
ssh-config 的配置文件
/etc/ssh/ssh_config |
示例
# 修改~/.ssh/config配置 |
配置成功之后直接
ssh
就可以直接登录
# 登录服务器1 |
把自己的公钥放在远程服务器的 authorized_keys 中
把本地文件
~/.ssh/id_rsa.pub
中内容复制粘贴到远程服务器~/.ssh/authorized_keys
简单来说,就是 Ctrl-C 与 Ctrl-V 操作,不过还有一个更加有效率的工具: ssh-copy-id
。
# 在本地环境进行操作 |
# 登录成功服务器,可以看到authorized_keys中的公钥信息 |
我们可以通过 man ssh-config
,找到每一项的详细释义。
# 编辑 ~/.ssh/config |
通过 printenv
可获得系统的所有环境变量
$ printenv |
我们也可以通过
printenv
,来获得某个环境变量的值
$ printenv NVM_BIN |
通过
$var
或者${var}
可以取得环境变量,并通过echo`
进行打印
$ echo $path |
$HOME
当前用户目录,也就是 ~
目录$USER
当前用户名$PATH
环境变量,指向环境变量的路径$SHELL
当前用户的 shell,比如 bash
、zsh
、fish
等export
可以用来设置环境变量,比如 $ export PATH=/usr/local/bin:$PATH |
在执行命令之前置入环境变量,可以用以指定仅在该命令中有效的环境变量。
# 该环境变量仅在当前命令中有效 |
快速高效,支持断点续传、按需复制的文件拷贝工具,并支持远程服务器拷贝,建议在本地也使用
rsync
替换cp
进行文件拷贝
# 将本地的test目录拷贝到服务器的/home目录 |
拷贝目录,则需要看原目录是否以
/
结尾
/
结尾,代表将该目录连同目录名一起进行拷贝/
结尾,代表将该目录下所有内容进行拷贝官方文档:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/
配置yum源
/etc/yum.repos.d/
下创建文件mongodb-org-4.0.repo
cd /etc/yum.repos.d/ |
mongodb-org-4.0.repo
中写入如下内容(下面内容可以直接复制,也可以复制官方文档)[mongodb-org-4.0] |
yum install -y mongodb-org |
systemctl start mongod |
systemctl enable mongod |
sudo vi /etc/mongod.conf
127.0.0.1
修改为0.0.0.0
service mongod restart
27017
端口:firewall-cmd --zone=public --add-port=27017/tcp --permanent;
(--permanent
永久生效,没有此参数重启后失效)firewall-cmd --reload
service mongod stop
rpm -qa | grep mongodb-org
列出所有的包yum remove -y $(rpm -qa | grep mongodb-org)
yum remove -y mongodb-org*
rm -r /var/log/mongodb
rm -r /var/lib/mongo
找到mysql
的yum
源rpm
包 https://dev.mysql.com/downloads/repo/yum
mysql安装源地址:http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
查看机器上面是否安装过mysql
rpm -qa | grep mysql* |
mysql的安装:
rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
yum -y install mysql-server
mysql
systemctl start mysqld
systemctl enable mysqld
/var/log/mysqld.log
文件中给 root
生成了一个默认密码。通过下面的方式找到 root 默认密码,然后登录 mysql 进行修改mysql -u root -p
输入密码ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!'
;ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
/etc/my.cnf
文件添加 validate_password_policy
配置,指定密码策略0(LOW),1(MEDIUM),2(STRONG)
其中一种,选择 2
需要提供密码字典文件validate_password_policy=0
validate_password = off
systemctl restart mysqld
远程管理mysql 添加 mysql
远程登录用户
把host
改为%
mysql -u root -p |
firewall-cmd --zone=public --add-port=3306/tcp --permanent
firewall-cmd --reload
mysql
redis
yum 源yum search redis |
EPEL (Extra Packages for Enterprise Linux)是基于 Fedora 的一个项目,为“红帽系”的操作系 统提供额外的软件包,适用于 RHEL、CentOS 和 Scientific Linux.
yum install epel-release -y |
yum info redis |
systemctl start redis |
linux 上面进入 Redis 客户端
redis-cli |
/usr/local/nodejs
vi /etc/profile
export NODE_HOME=/usr/local/nodejs/bin
export PATH=$NODE_HOME:$PATH
:wq
保存,然后运行 source /etc/profile
PM2 是一款非常优秀的 Node 进程管理工具,它有着丰富的特性:能够充分利用多核 CPU 且能够负载均衡、能够帮助应用在崩溃后、指定时间(cluster model)和超出最大内存限制 等情况下实现自动重启。 PM2 是开源的基于 Nodejs 的进程管理器,包括守护进程,监控,日志的一整套完整的功能。
PM2 的主要特性:
PM2 的常见命令
npm install pm2 -g |
运行
pm2
的程序并指定name
pm2 start app.js --name appName |
pm2 list
pm2 logs
pm2 logs appName
pm2 stop all
# 停止所有进程pm2 restart all
# 重启所有进程pm2 reload all
# 0 秒停机重载进程 (用于 NETWORKED 进程)pm2 stop 0
# 停止指定的进程 pm2 restart 0
# 重启指定的进程pm2 stop appName
pm2 restart appName
pm2 delete 0
# 杀死指定的进程pm2 delete all
# 杀死全部进程pm2 delete appName
# 杀死指定名字的进程pm2 show appName
sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm |
yum search nginx
看看是否已经添加源成功。如果成功则执行下列命令安装 Nginx。 npm info nginx
也可以看看 nginx 源是否添加成功sudo yum install -y nginx |
sudo systemctl start nginx |
# 修改 SELINUX=enforcing 为 SELINUX=disabled |
firewalld
开启 80
端口firewall-cmd --zone=public --list-ports |
找到 /etc/nginx/conf.d
然后在里面新建对应网站的配置文件
server { |
重启 nginx
systemctl restart nginx |
nginx -t
看配置是否正确 systemctl stop nginx
停止nginxsystemctl start nginx
启动nginx域名测试
找到 C:\Windows\System32\drivers\etc\hosts
192.168.1.128 |
浏览器输入
www.aaa.com
nginx 转发到了127.0.0.1:3001
# 添加 |
负载均衡的种类
Nginx 的特点是占有内存少,并发能力强,事实上 nginx 的并发能力确实在同类型的网页服 务器中表现最好,中国大陆使用 nginx 网站用户有:新浪、网易、 腾讯等
nginx 的 upstream 目前支持 3 种方式的分配
weight 权重
——you can you upip_hash
ip 哈希算法/etc/nginx/conf.d
然后在里面新建对应网站的配置文件重启 nginx:
systemctl restart nginx
www_aaa_com.conf
upstream bakeaaa { |
www_bbb.com.conf
|
www_ccc_com.conf
upstream bakeccc { |
负载均衡操作演示
我们使用docker跑三个nodejs应用程序作为演示
const Koa = require('koa'); |
const Koa = require('koa'); |
const Koa = require('koa'); |
在本地启动以上三个服务
# nginx/Dockerfile |
本机
conf.d/default.conf
文件
upstream bakeaaa { |
docker build -t nginx-demo .
ddocker run --name nginx -d -p 8666:80(本机端口:容器端口) -v /Users/poetry/Download/docker/nginx/conf.d:/etc/nginx/conf.d(本机配置文件目录:容器配置文件目录) e00b36d6975b(nginx镜像ID)
docker ps
查看启动的服务nginx/conf.d
需要重启容器才生效 docker restart nginx(容器名称)
修改
upstream bakeaaa
中的权重等,可以看到不断刷新页面,可以看到不同的服务器负载均衡的效果
安装nginx源
sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm |
查看 Nginx 源是否配置成功
yum search nginx
看看是否已经添加源成功。如果成功则执行下列命令安装 Nginx。 npm info nginx
也可以看看 nginx 源是否添加成功安装nginx源
sudo yum install -y nginx |
启动 Nginx
并设置开机自动运行
sudo systemctl start nginx sudo systemctl enable nginx |
下载 nodejs 二进制代码包,然后减压到 /usr/local/nodejs
配置环境变量
vi /etc/profile
最后面添加
export NODE_HOME=/usr/local/nodejs/bin |
:wq
保存,然后运行 source /etc/profile
server { |
为什么要使用 https
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。
HTTPS 是在 HTTP 的基础上添加了安全层,从原来的明文传输变成密文传输,当然加密与解 密是需要一些时间代价与开销的,不完全统计有 10 倍的差异。在当下的网络环境下可以忽 略不计,已经成为一种必然趋势。
目前微信小程序请求 Api 必须用 https、Ios 请求 api 接口必须用 https
配置 https
证书类型
server { |
Docker 是一个跨平台的开源的应用容器引擎,诞生于 2013 年初,基于 Go 语言 并遵从 Apache2.0 协议开源
刚开始学 Docker 你可以把它理解成我们以前学过的虚拟机,但是 Docke 和传统虚拟化方式 的不同之处。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系 统上再运行所需应用进程;Docker 相比传统的虚拟化技术要更轻量级,Docker 容器内的应 用程序是直接运行在宿主内核中的,容器内没有自己的内核,也没有进行硬件虚拟
因此 Docker 容器要比传统虚拟机占用资源更小、系统支持量更大、启动速度更快、更容易 维护和扩展。 目前 Docker 是全栈开发者必备的技能之一。 官网:https://hub.docker.com
本地docker环境搭建
mac下安装docker: brew install docker
,或者下载安装 https://docs.docker.com/docker-for-mac/install
https://hub.docker.com 拉取镜像速度比较慢,我们推荐使用国内的镜像源访问速度较快 https://hub.daocloud.io
设置国内镜像源
{ |
进入该网站 https://hub.daocloud.io
获取镜像的下载地址
docker命令基础
docker images
查看镜像docker ps
查看启动的容器 (-a
查看全部)docker rmi 镜像ID
删除镜像docker rm 容器ID
删除容器docker exec -it 1a8eca716169(容器ID:docker ps获取) sh
进入容器内部docker inspect bf70019da487(容器ID)
查看容器内的信息 删除none的镜像,要先删除镜像中的容器。要删除镜像中的容器,必须先停止容器。
$ docker rmi $(docker images | grep "none" | awk '{print $3}') |
$ docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') //停止容器 |
docker info
命令可以查看 Docker 容器的配置信息,包括镜像源、网络、磁盘、内存、系统等。
$ docker info |
安装工具包
yum install yum-utils device-mapper-persistent-data lvm2 -y |
设置阿里镜像源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
安装docker
yum install docker-ce docker-ce-cli containerd.io -y |
启动docker
systemctl start docker |
设置docker镜像源
vi /etc/docker/daemon.json |
{ |
后续拉取镜像直接从 https://hub.docker.com 网站拉取速度更快
重启docker
systemctl restart docker |
yum list docker-ce --showduplicates | sort -r |
sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.i o |
$ sudo yum remove docker-ce docker-ce-cli containerd.io
$ sudo rm -rf /var/lib/docker
$ sudo rm -rf /var/lib/containerd
访问 https://www.aliyun.com/ 搜索 “容器镜像服务”
您可以通过修改daemon配置文件/etc/docker/daemon.json
来使用加速器
sudo mkdir -p /etc/docker |
Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运 行在 Linux 内核的程序以及相应的数据
类似 linux 系统环境,运行和隔离应用。容器从镜像启动的时候,docker 会在镜像的最上一 层创建一个可写层,镜像本身是只读的,保持不变。容器与镜像的关系,就如同面向编程中 对象与类之间的关系。
仓库(Repository)是集中存储镜像的地方,这里有个概念要区分一下,那就是仓库与仓库 服务器(Registry)是两回事,像我们上面说的 Docker Hub,就是 Docker 官方提供的一个仓库 服务器,不过其实有时候我们不太需要太过区分这两个概念。
公共仓库
私有仓库
有时候自己部门内部有一些镜像要共享时,如果直接导出镜像拿给别人又比较麻烦,使用像 Docker Hub 这样的公共仓库又不是很方便,这时候我们可以自己搭建属于自己的私有仓库服 务,用于存储和分布我们的镜像。
Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运 行在 Linux 内核的程序以及相应的数据
镜像结构: registryname/repositoryname/imagename:tagname
例如:docker.io/library/centos:8.3.2011
docker search
搜索镜像 docker search centos
docker pull
下载镜像 docker pull centos
docker pull centos:8.3.2011
docker images
查看本地镜像docker tag
给镜像打标签registryname/repositoryname/imagename:tagname
docker.io/library/centos:8.3.2011
docker rmi 镜像ID -f(强制删除)
删除镜像docker images | grep centos
dockerHub
仓库https://hub.docker.com/
docker login
本地登录dockerHub
docker login
cat .docker/config.json
docker push
镜像名称 把本地镜像推送到远程类似 linux 系统环境,运行和隔离应用。容器从镜像启动的时候,docker 会在镜像的最上一 层创建一个可写层,镜像本身是只读的,保持不变。容器与镜像的关系,就如同面向编程中 对象与类之间的关系。
因为容器是通过镜像来创建的,所以必须先有镜像才能创建容器,而生成的容器是一个独立 于宿主机的隔离进程,并且有属于容器自己的网络和命名空间
docker ps
docker ps -a
查看所有容器docker run
参数docker run
:创建一个新的容器并运行一个命令docker run
是日常用的最频繁用的命令之一,同样也是较为复杂的命令之一 命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-i
:表示启动一个可交互的容器,并持续打开标准输入-t
: 表示使用终端关联到容器的标准输入输出上-d
:表示容器放置后台运行--rm
:退出后即删除容器--name
:表示定义容器唯一名称-p
映射端口-v
指定路径挂载数据卷-e
运行容器传递环境变量IMAGE
:表示要运行的镜像COMMAND
:表示启动容器时要运行的命令-it
启动一个交互式容器docker run
启动一个交互式容器在容器内执行/bin/bash
命令docker run -it nginx(镜像ID或名称) /bin/bash
docker rm 容器ID -f
docker stop 容器ID
docker start 容器ID
docker restart 容器ID
docker exec -it 容器ID /bin/bash
docker exec
:进入容器开启一个新的终端(常用) 执行 exit
退出的时候不会停止容器docker attach
:进入容器正在执行的终端 exit
退出会停止容器 docker logs 容器ID
docker logs [OPTIONS] CONTAINER
-f
: 跟踪日志输出--since
:显示某个开始时间的所有日志 -t
: 显示时间戳 --tail
:仅列出最新 N
条容器日志docker commit
容器转换为镜像docker exec -it 容器ID /bin/bash
进入容器内部,写入内容 echo test > 1.txt
docker commit 容器ID 自定义镜像名称
将容器转换为镜像进入https://hub.daocloud.io
搜索node,切换到版本获取下载地址
docker pull daocloud.io/library/node:12.18
docker tag 28faf336034d node
重命名镜像重命名镜像后IMAGE ID都是一样的
也可以导出镜像到本地备份 docker save -o node.image(导出镜像要起的名称) 28faf336034d(要导出的镜像的ID)
我们先删除之前的镜像 docker rmi 28faf336034d -f
强制删除
再次导入本地镜像
docker load -i node.image(导入的镜像名称)
然后再次重命名镜像即可
docker tag 28faf336034d node:v1.0(版本v1.0)
docker pull daocloud.io/library/nginx:1.13.0-alpine |
启动Nginx镜像
服务器上启动
docker run --name nginx(起一个容器名称) -d(后台运行) -p 80:80(本机:容器) -v(映射Nginx容器的运行目录本机) /root/nginx/log:/var/log/nginx(本机目录:容器目录) -v /root/nginx/conf/nginx.conf:/etc/nginx/nginx.conf(本机目录:容器内nginx配置所在目录) -v /root/nginx/conf.d:/etc/nginx/conf.d -v /root/nginx/html:/usr/share/nginx/html f00ab1b3ac6d(nginx镜像ID) |
本地电脑启动
docker run --name nginx -d -p 8666:80 -v /Users/poetry/Downloads/docker/nginx/log:/var/log/nginx -v /Users/poetry/Downloads/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v /Users/poetry/Downloads/docker/nginx/conf.d:/etc/nginx/conf.d -v /Users/poetry/Downloads/docker/nginx/html:/usr/share/nginx/html f00ab1b3ac6d |
把docker容器中的Nginx服务配置映射本地方便管理
访问docker暴露的8666端口即可
当我们修改了html中的文件,无需重启容器即可看到效果
进入https://hub.daocloud.io
搜索mysql,切换到版本获取下载地址
docker pull daocloud.io/library/mysql:8.0.20
启动MySQL镜像
docker run -d(后台运行) -p 3307:3306(本机端口:MySQL运行端口) --name mysql(容器名称) -e MYSQL_ROOT_PASSWORD=123456(设置mysql密码) be0dbf01a0f3(mysql镜像ID) |
查看当前正在运行的镜像
docker ps -a(正在运行和停止的镜像-a都可见) |
删除容器
删除之前需要stop:docker stop bac2692e2b9a(容器ID)
docker rm bac2692e2b9a(容器ID:docker ps获取) |
进入容器内部
docker exec -it bac2692e2b9a(容器ID) sh(指定进入方式) |
我们使用Navicat新建一个连接测试一下
说明我们使用docker安装MySQL的方式是没问题的
查看MySQL容器日志
docker logs -f(查看最后几条) bac2692e2b9a(容器ID) |
重启容器
如果修改了容器配置,我们需要重新启动容器
docker restart bac2692e2b9a(容器ID) |
设置MySQL权限
mysql8.0后,需要设置,否则node连接不上
docker exec -it bac2692e2b9a sh |
# 远程连接权限 |
挂载配置文件目录
默认数据库的数据是放在容器里面的,这样的话当容器删除会导致数据丢失。我们想的是删 除容器的时候不删除容器里面的 mysql 数据,这个时候启动容器的时候就可以把 mysql 数据 挂载到外部。
docker run -d(后台运行) -p 3307:3306(本机端口:MySQL运行端口) -v v /mysql/conf.d:/etc/mysql/conf.d -v /mysql/data:/var/lib/mysql --name mysql(容器名称) -e MYSQL_ROOT_PASSWORD=123456(设置mysql密码) be0dbf01a0f3(mysql镜像ID) |
docker inspect bac2692e2b9a(容器ID)
docker inspect bac2692e2b9a | grep mysql |
docker pull daocloud.io/library/redis:6.0.3-alpine3.11 |
启动Redis镜像
docker run -d -p 6380:6379 --name redis 29c713657d31(镜像ID) --requirepass 123456(redis登录密码) |
或进入redis镜像后在输入密码
交互式进入redis容器
docker exec -it 9751cbc96861(容器ID) sh |
docker pull mongo |
启动容器 映射端口 挂载目录
docker run --name mongoTest -p 27018:27017 -v ~/Downloads/docker/mongo:/data/db -d mongo(镜像ID或名称) |
可以看到通过 -v
挂载到本地的数据
进入容器内部 docker exec -it mongoTest(镜像ID或名称) sh
输入mongo,可以看到mongo已经安装成功了,我们从容器外连接容器的mongo
连接需要密码
docker run -d --name authMongo -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=123456 -p 27019:27017 -v ~/Downloads/docker/authMongo:/data/db mongo(镜像名称或者ID) --auth |
进入容器内部
远程连接
Dockerfile 构建一个 nginx 镜像,构建好的镜像内会有一个
/usr/share/nginx/html/index.html
文件
新建一个名为 Dockerfile 文件,并在文件内添加以下内容
FROM nginx |
docker build -t nginx:v1 .
docker run -it -d -p 8900:80 容器ID
curl 127.0.0.1
你好 dockerDockerfile
文件的文件名建议使用 Dockerfile
,如果是其他文件构建的时候需要指定文件 名FROM # 基础境像,一切从这里开始构建 |
FROM
指定哪种镜像作为新镜像的基础镜像,如:FROM ubuntu:14.04
MAINTAINER
指明该镜像的作者和其电子邮件,如:MAINTAINER "xxxxxxx@qq.com"
LABEL
给镜像添加信息。使用 docker inspect
可查看镜像的相关信息,如:LABEL maintainer="xx@qq.com"
LABEL version="1.0"
LABEL description="This is description"
RUN
在新镜像内部执行的命令\
来换行,如: RUN apt-get update && apt-get install -y vim
exec
格式 RUN ["executable", "param1", "param2"]
的命令,如RUN ["apt-get","install","-y","nginx"]
RUN ["yum","install","-y","nginx"]
COPY
COPY ./src/ /usr/share/nginx/html/
ADD
ADD ./src.tar.gz /usr/share/nginx/html/
WORKDIR
WORKDIR /usr/share/nginx/html
CMD
CMD ["/bin/bash"]
ENTRYPOINT
ENTRYPOINT ["/bin/bash"]
CMD
和 ENTRYPOINT
同样作为容器启动时执行的命令,区别有以下几点CMD
的命令会被 docker run
的命令覆盖而 ENTRYPOINT
不会CMD ["/bin/bash"]
或 ENTRYPOINT ["/bin/bash"]
后,再使用 docker run -it image
启动容 器,它会自动进入容器内部的交互终端,如同使用 docker run -it image /bin/bash
docker run -it image /bin/ps
,使用 CMD
后面的命令就会被覆盖 转而执行 bin/ps
命令,而 ENTRYPOINT
的则不会,而是会把 docker run
后面的命令当做 ENTRYPOINT
执行命令的参数EXPOSE 暴露端口
EXPOSE 8080
docker run -P
时,会自动随机映射 EXPOSE
的端口。VOLUME
VOLUME /usr/share/nginx/html
docker run -v
时,会自动随机映射 VOLUME
的卷。docker inspect
查看通过该 dockerfile
创建的镜像生成的容器ENV
ENV PATH=/usr/bin
docker run -e
时,会自动随机映射 ENV
的环境变量。# Dockerfile_centos |
编译 编译的时候注意最后面的.
docker build -f Dockerfile_centos -t centos:v1.0 .
查看执行的历史 docker history 镜像名称或者id
项目目录中新建 Dockerfile
COPY . /root/wwwroot/
表示把项目目录中的代码复制到容器里面的/root/wwwroot
目录
FROM node |
构建 docker build -t nodeimg:v1.0.1 .
运行镜像并且进入容器 docker run -tid --name nodeDemo -p 3000:3000 nodeimg:v1.0.1
多个容器之间如何通信,是否可以直接连接
首先看看网卡信息
:默认情况同一台主机上面的容器是可以互相通信的,默认情况同一台主机上面的容器 和主机之间是可以互相通信的
通信原理
我们每启动一个Docker容器,Docker就会给Docker容器分配一个ip,我们只要安装了Docker, 就会有一个网卡
Docker0
,Docker0
使用的是桥接模式,使用的技术是 evth-pair 技术
docker network --help
docker network ls
查看网络 docker network inspect 网络ID(docker network ls获取)
查看网络详情Docker 网络模式 | 配置 | 说明 |
---|---|---|
host 模式 | --net=host | 容器和宿主机共享 Network namespace |
container 模式 | --net=container:NAMEorID | 容器和另外一个容器共享 Network namespace 。 kubernetes 中的 pod 就是多个容器共享一个 Network namespace 。 |
none 模式 | --net=none | 容器有独立的 Network namespace ,但并没有对其 进行任何网络设置,如分配 veth pair 和网桥连 接,配置 IP 等。 |
bridge 模式 | --net=bridge | (默认为该模式) |
host 模式
如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡, 配置自己的 IP 等,而是使用宿主机的 IP 和端口。但是,容器的其他方面,如文件系统、进 程列表等还是和宿主机隔离的
使用 host 模式的容器可以直接使用宿主机的 IP 地址与外界通信,容器内部的服务端口也可 以使用宿主机的端口,不需要进行 NAT,host 最大的优势就是网络性能比较好,但是 docker host 上已经使用的端口就不能再用了,网络的隔离性不好
container 模式
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和 宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器 共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还 是隔离的。两个容器的进程可以通过 lo 网卡设备通信
none 模式
使用 none 模式,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker 容器进 行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己 为 Docker 容器添加网卡、配置 IP 等。
这种网络模式下容器只有 lo 回环网络,没有其他网卡。none 模式可以在容器创建时通过 --network=none
来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安 全性
bridge 模式
当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有 容器就通过交换机连在了一个二层网络中
docker network create 创建网络以及启动容器指定网络
docker network create --help |
docker pull centos |
创建一个 mysqlNet网络
--driver bridge
配置网络类型 bridge
桥接--subnet 192.168.1.0/24
配置子网 建议每个网络的范围尽量小--gateway 192.168.1.1
配置网关docker network create --driver bridge --subnet 192.168.1.0/24 --gateway 192.168.1.1 mysql Net |
启动容器指定网络
我们启动容器的时候可以加上 --net
参数可以指定启动容器的时候使用的网络,如果不加表 示默认使用 docker0
网络
--net bridge
表示使用docker0
网络
docker run -tid --name centos01 centos /bin/bash |
--net mysqlNet
表示使用我们自定义网络
docker run -tid --name centos04 --net mysqlNet centos /bin/bash |
使用主机名称可以 ping 通
# 进入centos05 |
结果
$ docker exec -it centos05 /bin/bash |
不同网络的容器默认没法通信
我们在centos05
容器内ping centos01
容器,结果是不成功的
# 进入centos05 |
这样我们就把 centos04 和 centos05 加入了我们自定义的 mysqlNet 网络,这样的话 centos04 和 centos05 是互通的,但是 mysqlNet 网络和 docker0 网络默认是不互通的
docker network connect 实现不同网络之间的连通
如上图,我们想的是 centos01 可以 访问 mysqlNet 里面的 centos04 和 centos05,这个时候 我们就需要使用 docker network connect 实现网络连通
docker network connect mysqlNet centos01 |
# 查看本地网络 |
查看网络详情
$ docker network inspect mysqlNet |
可以看到是ping成功的
$ docker exec centos01 ping centos05 |
云开发与serverless的区别
Serverless Framework
是无服务器应用框架,提供将云函数SCF
、API
网关、对象存储 COS
、云数据库 DB
等资源组合的业务框架,开发者可以直接基于框架编写业务逻辑,而无需关注底层资源的配置和管理。Tencent CloudBase,TCB
)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用、自动弹性扩缩的后端云服务,包含计算、存储、托管等 serverless
化能力,可用于云端一体化开发多种端应用(小程序、公众号、Web
应用、Flutter
客户端等),帮助开发者统一构建和管理后端服务和云资源,避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。在产品中选择云开发产品
创建一个项目, 这里要选择好区域,下次创建了项目,区域不一样,可能项目就看不到
全局安装脚手架包官方地址
npm i -g @cloudbase/cli |
为了简化输入,cloudbase 命令可以简写成 tcb
测试安装是否成功
tcb -v |
查看命令
tcb -h |
# CloudBase CLI 会自动打开云开发控制台获取授权,您需要点击同意授权按钮允许 CloudBase CLI 获取授权。如您没有登录,您需要登录后才能进行此操作。 |
也可以使用下面的方式通过 API 秘钥直接登录,避免交互式输入
tcb login --apiKeyId xxx --apiKey xxx |
本地创建项目
tcb new [options] [appName] [templateUri] |
云开发项目是和云开发环境资源关联的实体,云开发项目聚合了云函数、数据库、文件存储等服务,您可以在云开发项目中编写函数,存储文件,并通过 CloudBase 快速的操作您的云函数、文件存储、数据库等资源。
云开发项目文件结构:
. |
选择自己已经创建的环境,如果没有就 创建新环境,这时候会打开浏览器
本地打开项目并且安装依赖包
npm install |
部署到线上
# 调用 tcb framework deploy |
部署完成后可以使用 tcb fn list
命令查看已经部署完成的函数列表
查看所有环境
tcb env list |
安全域名
当您需要在网页应用中使用云开发的身份验证服务时,您需要将您的网站的域名(发起请求的页面的域名)加入安全域名名单中。安全域名是云开发服务认可的用户请求来源域名,所有来自非安全域名名单中的请求都不会被响应。
使用下面的命令查看所有配置的安全域名
tcb env domain list |
新增安全域名
# 添加一个域名 |
删除安全域名
tcb env domain delete |
登录方式
当您需要使用云开发的身份验证服务时,您需要配置您想使用的登录方式。目前云开发支持自定义登录、微信公众平台、微信开放平台登录等多种登录方式。
# 您可以使用下面的命令列出环境配置的登录方式列表,查看环境配置的登录方式,以及相关的状态。 |
您可以使用下面的命令新建登录方式:
tcb env login create |
您需要选择配置的平台,登录方式状态,以及对应的 AppId 和 AppSecret,登录方式请选择启用。在添加方式时不会校验 AppId 和 AppSecret 的有效性,只有在请求时,AppId 和 AppSecret 才会被校验,所以请确保您添加的 AppId 和 AppSecret 是有效的。
修改登录方式
您也可以使用
tcb env login update
修改已经配置的登录方式,如切换启用状态,修改 AppId 和 AppSecret。
在
cloudbaserc.json
中声明"version": "2.0"
即可启用新的特性,新版配置文件只支持 JSON 格式
// 动态变量特性允许在 `cloudbaserc.json` 配置文件中使用动态变量,从环境变量或其他数据源获取动态的数据。使用 `{{}}` 包围的值定义为动态变量,可以引用数据源中的值。如下所示 |
CloudBase 支持使用 .env
类型文件作为主要数据源,使用不同的后缀区分不同的阶段、场景,如 .env.development
可以表示开发阶段的配置,.env.production
可以表示生产环境的配置
当指定
--mode [mode]
时,会再加载.env.[mode]
文件,并按照如下的顺序合并覆盖同名变量:.env.[mode] > .env.local > .env
即.env.[mode]
中的同名变量会覆盖.env.local
和.env
文件中的同名变量
当使用
tcb framework deploy --mode test
命令时,会自动加载.env
,.env.local
以及.env.test
等三个文件中的环境变量合并使用。
我们建议你将秘钥等私密配置放在 .env.local
文件中,并将 .env.local
加入 .gitignore
配置中
如 .env.local
文件中存在以下变量
DB_HOST = localhost |
则可以在配置文件中使用
{ |
参考配置
// https://docs.cloudbase.net/cli/functions/configs |
下面为目前所有支持的配置项
配置项 | 是否必填 | 类型 | 描述 |
---|---|---|---|
name | 是 | String | 云函数名称,即为函数部署后的名称 |
params | 否 | Object/JSONObject | CIL 调用云函数时的函数入参 |
triggers | 否 | Array | 触发器配置 |
handler | 否 | String | 函数处理方法名称,名称格式支持“文件名称.函数名称”形式 |
ignore | 否 | String/Array<String> | 部署/更新云函数代码时的忽略文件,支持 glob 匹配规则 |
timeout | 否 | Number | 函数超时时间(1 - 60S) |
envVariables | 否 | Object | 包含环境变量的键值对对象 |
vpc | 否 | VPC | 私有网络配置 |
runtime | 否 | String | 运行时环境配置,可选值: Nodejs8.9, Nodejs10.15 Php7, Java8 |
memorySize | 否 | Number | 函数内存,默认值为 256,可选 128、256、512、1024、2048 |
installDependency | 否 | Boolean | 是否云端安装依赖,目前仅支持 Node.js |
codeSecret | 否 | String | 代码加密秘钥,格式为 36 位大小写字母、数字 |
runtime
默认为 Nodejs10.15
,使用 Node.js 运行时可不填,使用 Php 和 Java 则必填。CloudFunctionTrigger
名称 | 是否必填 | 类型 | 描述 |
---|---|---|---|
name | 是 | String | 触发器名称 |
type | 是 | String | 触发器类型,可选值:timer |
config | 是 | String | 触发器配置,在定时触发器下,config 格式为 cron 表达式 |
VPC
名称 | 是否必填 | 类型 | 描述 |
---|---|---|---|
vpcId | 是 | String | VPC Id |
subnetId | 是 | String | VPC 子网 Id |
更新函数运行时配置
创建函数式,Cloudbase CLI 会为函数提供一些默认的配置,所以您不需要添加配置信息也可以直接部署函数。您也可以通过下面的命令修改函数的运行时配置
# 更新 app 函数的配置 |
目前支持修改的函数配置包含超时时间 timeout
、环境变量 envVariables
、运行时 runtime
,vpc网络以及 installDependency
等选项。
CloudBase CLI 会从配置文件中读取函数的配置信息并更新,CloudBase CLI 会更新配置文件中存在的函数的所有配置,暂不支持指定更新单个配置选项。
fn deploy
命令部署函数的文件大小总计不能超过50 M
,否则可能会部署失败。
在一个包含 cloudbaserc.json
配置文件的项目下,您可以直接使用下面的命令部署云函数:
tcb fn deploy <functionName> |
使用 fn deploy
时,functionName
选项是可以省略的,当 functionName
省略时,Cloudbase CLI 会部署配置文件中的全部函数:
# 部署配置文件中的全部函数 |
全部参数
Usage: tcb fn deploy [options] [name] |
默认选项
Cloudbase CLI 为 Node.js 云函数提供了默认选项,您在部署 Node.js 云函数时可以不用指定云函数的配置,使用默认配置即可部署云函数。
云函数默认配置:
{ |
deploy 命令做了啥
fn deploy
会读取 cloudbaserc.json
文件中指定函数的配置,并完成以下几项工作
tcb fn list |
默认情况下,
fn list
命令只会列出前20
个函数,如果您的函数较多,需要列出其他的函数,您可以通过下面的选项指定命令返回的数据长度以及数据的偏移量:
-l, --limit <limit> 返回数据长度,默认值为 20 |
# 返回前 10 个函数的信息 |
tcb fn code download <functionName> [destPath] |
默认情况下,函数代码会下载到 functionRoot
下,以函数名称作为存储文件夹,您可以指定函数存放的文件夹地址,函数的所有代码文件会直接下载到指定的文件夹中。
# 查看 vue-echo 函数的详情 |
# 删除 app 函数 |
# 复制 app 函数为 app2 函数 |
触发器是按照一定规则触发函数的模块的抽象,CloudBase 云函数目前仅支持定时触发器。
如果云函数需要定时/定期执行,即定时触发,您可以使用云函数定时触发器。已配置定时触发器的云函数,会在相应时间点被自动触发,函数的返回结果不会返回给调用方。
{ |
创建函数触发器
# 创建 app 函数配置的触发器 |
Cloudbase CLI 会自动读取
cloudbaserc.json
文件中指定函数配置的定时触发器,并创建云函数触发器。如果配置文件中没有包含触发器配置,则会创建失败。
一个函数可以包含多个触发器,一个触发器包含了以下 3 个主要信息:name
, type
, config
{ |
当没有指定函数名时,Cloudbase CLI 会创建 cloudbaserc.json
文件包含的所有函数的所有触发器
删除函数触发器
# 删除 app 函数的名为 trigger 的触发器 |
当没有指定函数名时,Cloudbase CLI 会删除
cloudbaserc.json
文件包含的所有函数的所有触发器。当只指定了函数名时,Cloudbase CLI 会删除指定函数的所有触发器,当同时指定了函数名称和触发器名称时,Cloudbase CLI 只会删除指定的触发器。
# 删除 cloudbaserc.json 文件中所有函数的所有触发器 |
触发器规则
60
个字符,支持 a-z
, A-Z
, 0-9
, -
和 _
。必须以字母开头,且一个函数下不支持同名的多个定时触发器。Cron 表达式有七个必需字段,按空格分隔。其中,每个字段都有相应的取值范围:
排序 | 字段 | 值 | 通配符 |
---|---|---|---|
第一位 | 秒 | 0 - 59 的整数 | , - * / |
第二位 | 分钟 | 0 - 59 的整数 | , - * / |
第三位 | 小时 | 0 - 23 的整数 | , - * / |
第四位 | 日 | 1 - 31 的整数(需要考虑月的天数) | , - * / |
第五位 | 月 | 1 - 12 的整数或 JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV 和 DEC | , - * / |
第六位 | 星期 | 0 - 6 的整数或 MON、TUE、WED、THU、FRI、SAT 和 SUN,其中 0 指周日,1 指星期一,以此类推 | , - * / |
第七位 | 年 | 1970 - 2099 的整数 | , - * / |
通配符
通配符 | 含义 |
---|---|
, (逗号) | 代表取用逗号隔开的字符的并集。例如:在“小时”字段中 1,2,3 表示 1 点、2 点和 3 点 |
- (短横线) | 包含指定范围的所有值。例如:在“日”字段中,1 - 15 包含指定月份的 1 号到 15 号 |
* (星号) | 表示所有值。在“小时”字段中,* 表示每个小时 |
/ (正斜杠) | 指定增量。在“分钟”字段中,输入 1/10 以指定从第一分钟开始的每隔十分钟重复。例如,第 11 分钟、第 21 分钟和第 31 分钟,以此类推 |
!在 Cron 表达式中的“日”和“星期”字段同时指定值时,两者为“或”关系,即两者的条件均生效。
下面列举一些 Cron
表达式和相关含义:
*/5 * * * * * *
表示每 5 秒触发一次0 0 2 1 * * *
表示在每月的 1 日的凌晨 2 点触发0 15 10 * * MON-FRI *
表示在周一到周五每天上午 10:15 触发0 0 10,14,16 * * * *
表示在每天上午 10 点,下午 2 点,下午 4 点触发0 */30 9-17 * * * *
表示在每天上午 9 点到下午 5 点内每半小时触发0 0 12 * * WED *
表示在每个星期三中午 12 点触发当您的函数代码发生改变时,您可以使用下面的命令更新您的云函数的代码:
# 更新 app 函数的代码 |
fn code update
命令和fn deploy
命令的主要区别是:fn code update
命令只会更新函数的代码以及执行入口,不会修改函数的其他配置,而fn deploy
命令则会修改函数的代码、配置以及触发器等。
您可以通过下面的命令查看函数版本:
tcb fn list-function-versions <name> |
您可以通过下面的命令打印云函数的运行日志,使用此命令时必须指定函数的名称:
# 查看 vue-echo 函数的调用日志 |
默认情况下,Cloudbase CLI 会打印最近的 20 条日志,您可以通过在命令后附加下面的可用选项指定返回日志的筛选条件:
-i, --reqId <reqId> 函数请求 Id |
如:tcb fn log app -l 2
打印 app
函数的最新 2 条日志信息
cloudbaserc.json文件
{ |
tcb fn deploy vue-echo |
tcb fn list |
云开发为开发者提供静态网页托管的能力,静态资源(HTML、CSS、JavaScript、字体等)的分发由对象存储 COS 和拥有多个边缘网点的 CDN 提供支持。您可在腾讯云控制台进行静态网站的部署,提供给您的用户访问。目前云开发静态网页托管能力仅在腾讯云云开发控制台支持,小程序 IDE 侧控制台暂不支持。
仅有付费方式为按量付费的环境可开通静态网页托管能力,预付费方式环境不可开通。
使用 CLI 操作静态网站服务前请先到云开发控制台开通静态网站服务。
全量部署
云开发 CLI 提供了直接部署网站文件的命令,在您需要部署的文件夹目录下,直接运行
tcb hosting deploy
命令即可将当前目前下所有的文件部署静态网站中。
# dist 构建目录 |
您可以使用下面的命令展示静态网站的状态,访问域名等信息
删除文件
您可以使用下面的命令删除静态网站的存储空间中的文件或文件夹
# cloudPath 为文件或文件夹的相对根目录的路径,为 目录/文件名 的形式,如 index.js、static/css/index.js 等 |
删除全部文件
云端路径为空时,表示删除全部文件
tcb hosting delete -e envId |
查看文件列表
您可以使用下面的命令部署展示静态网站存储空间中文件
tcb hosting list -e envId |
CloudBase 提供跨平台的登录鉴权功能,您可以基于此为自己的应用构建用户体系,包括但不限于:
同时,CloudBase 登录鉴权还是保护您的服务资源的重要手段,CloudBase 对用户端发来的每一个请求,都会进行身份和权限的检查,避免您的资源被恶意攻击者消耗或者盗用。
每个登录 CloudBase 的用户,都有一个对应的 CloudBase 账号,用户通过此账号访问调用 CloudBase 的数据与资源。
登录状态的持久化
您可以指定登录状态如何持久保留。默认为 local
,相关选项包括
例如,对于网页应用,最佳选择是 local
,即在用户关闭浏览器之后仍保留该用户的会话。这样,用户不需要每次访问该网页时重复登录,避免给用户带来诸多不便体验。
访问令牌与刷新令牌
用户登录 CloudBase 之后,会获得访问令牌(Access Token
) 作为访问 CloudBase
的凭证,访问令牌默认具有两小时有效期。
登录时还会获得刷新令牌(Refresh Token),默认有效期 30 天,用于访问令牌过期后,获取新的访问令牌。
CloudBase 用户端 SDK 会自动维护令牌的刷新和有效期,开发者无需特别关注此流程。
匿名登录 的刷新令牌(
Refresh Token
)会在到期后自动续期,以实现长期的匿名登录状态。
获取当前登录的用户
获取当前用户,推荐在
Auth
对象上设置一个回调函数,每当用户登录状态转变时,会触发这个回调函数,并且获得当前的LoginState
:
import cloudbase from "@cloudbase/js-sdk"; |
您还可以使用
Auth.currentUser
属性来获取当前登录的用户。如果用户未登录,则currentUser
为 null
您还可以使用 Auth.currentUser
属性来获取当前登录的用户。如果用户未登录,则 currentUser
为 null
const user = auth.currentUser; |
获取用户个人资料
您可以通过
User
对象的各个属性来获取用户的个人资料信息:
const user = auth.currentUser; |
更新用户个人资料
您可以使用 User.update
方法来更新用户的个人资料信息。例如:
const user = auth.currentUser; |
刷新用户资料信息
对于一个多端应用,用户可能在其中某个端上更新过自己的个人资料信息,此时其它端上可能需要刷新信息:
const user = auth.currentUser; |
关联微信登录
const auth = app.auth(); |
auth.currentUser.linkWithRedirect(provider); |
用户在微信的页面登录之后,会被重定向回您的页面。然后,可以在页面加载时通过调用 Provider.getLinkRedirectResult()
来获取关联结果:
const provider = auth.weixinAuthProvider(); |
关联自定义登录
User.linkWithTicket
,获取自定义登录 Ticket
后,关联自定义用户:const auth = app.auth(); |
关联邮箱密码登录
const auth = app.auth(); |
auth.currentUser.updateEmail(email).then(() => { |
关联用户名密码登录
// 以邮箱登录为例 |
await app.auth().currentUser.updateUsername(username); // 绑定用户名 |
const loginState = await app.auth().signInWithUsernameAndPassword(username, password); // 用户名密码登录 |
避免重复登录
执行登录流程之前,我们非常建议您先判断用户端是否已经登录 CloudBase,如已经登录,那么不需要执行登录流程,以避免无意义的重复登录。
const auth = app.auth(); |
登录状态的持久保留
对于网页应用,最佳选择是
local
,即在用户关闭浏览器之后仍保留该用户的会话。这样,用户不需要每次访问该网页时重复登录,避免给用户带来诸多不便体验
const auth = app.auth({ |
登录 腾讯云 CloudBase 控制台,在 登录授权页面中,将匿名登录一栏打开或关闭。
添加安全域名(可选)
Web 应用需要将域名添加到 CloudBase 控制台的Web 安全域名列表中,否则将被识别为非法来源:
import cloudbase from '@cloudbase/js-sdk'; |
可以看到token缓存到了本地
每个
CloudBase
环境的匿名用户数量不超过1000
万个
匿名用户在安全规则中的auth.loginType
值为ANONYMOUS
,配合安全规则可以限制匿名用户的 云数据库 和 云存储 的访问权限。比如下述代码展示的安全规则为:
转化为正式用户
问题
CloudBase 限制每个环境的匿名用户数量不超过 1000 万个,如果达到上限可以在 CloudBase 控制台 的“用户管理”页面查看匿名用户的活跃情况,针对长期不登录的匿名用户可以考虑将其删除以释放空间。
CloudBase 对匿名用户的有效期限策略是:每个设备同时只存在一个匿名用户,并且此用户永不过期。当然,如果用户手动清除了设备或浏览器的本地数据,那么匿名用户的数据便会被同步清除,再次调用 CloudBase 匿名登录 API 会产生一个新的匿名用户。
CloudBase 允许客户端在未登录的情况下调用 CloudBase 的资源,开发者可以配合安全规则限制未登录对资源的访问权限。
登录 云开发 CloudBase 控制台,在 登录授权 中,将未登录一栏打开或关闭。
添加安全域名(可选)
Web 应用需要将域名添加到 CloudBase 控制台的Web 安全域名列表中,否则将被识别为非法来源:
使用流程
您需要使用自定义安全规则,来放通未登录模式下的资源访问。
基于安全性的考虑,基础的四种权限设置下,均不允许未登录进行访问。
如,您可以这样设置云数据库的权限:
{ |
在原始私有读 doc._openid==auth.openid
的基础上,允许了所有未登录用户进行读资源。详细可查看 编写安全规则。
import cloudbase from '@cloudbase/js-sdk'; |
SDK 初始化完成后可以正常发起云开发资源的调用。
使用邮箱登录,您可以让您的用户使用自己的邮箱和密码注册、登录 CloudBase,并且还可以更新登录使用的邮箱和密码。
开通邮箱登录
进入 云开发 CloudBase 控制台,在 登录授权 设置页面中,开启邮箱登录:
填入您邮箱的 SMTP 账号信息
打开右侧「应用配置」页面,设置您的应用名称和自动跳转链接。
登录流程
import cloudbase from "@cloudbase/js-sdk"; |
首先需要用户填入自己的邮箱和密码,然后调用 SDK 的注册接口:
app |
调用注册接口之后,CloudBase
会使用您预先设置的邮箱,发送一封验证邮件到用户的邮箱。邮件中包含一个激活链接,用户在点击激活链接后,账号才会正式注册成功。
密码强度要求:密码长度不小于 8 位,不大于 32 位,需要包含字母和数字。
可以看到注册成功的账户
app |
经微信授权的网页应用可以直接使用微信登录 CloudBase,包括两种授权类型:
开通流程
首先需要一个微信公众平台 / 开放平台的注册账号,如果没有,请前往 微信公众平台 或 微信开放平台申请
然后在微信公众平台/开放平台的管理后台中,查看开发者 ID(AppId)和开发者密码(AppSecret)。
以微信公众平台为例,在“开发 - 基本配置”中有以下内容:
开发者密码(AppSecret)是非常私密的信息,每次点击上图中的「重置」按钮都会获取一个新的 AppSecret。
点击启用按钮后在弹窗的对应位置填入 AppId 和 AppSecret。
Web 应用需要将域名添加到 CloudBase 控制台的Web 安全域名列表中,否则将被识别为非法来源:
微信登录流程
在使用微信登录 CloudBase 前,请先在控制台中 启用微信登录。
import cloudbase from "@cloudbase/js-sdk"; |
创建 Provider:首先我们创建一个 Provider 实例,并且填入参数:
const auth = app.auth(); |
snsapi_userinfo
或 snsapi_login
登录,并且是首次登录,那么 CloudBase 将会自动拉取、同步微信的用户基本信息。使用 Provider 进行登录
首先调用
Provider.signInWithRedirect()
,用户将会跳转到微信 OAuth 授权页面:
provider.signInWithRedirect(); |
在授权页面内,需要用户对登录行为进行授权,成功后,会返回至当前页面。
然后调用 Provider.getRedirectResult()
,获取登录结果:
provider.getRedirectResult().then((loginState) => { |
开发者可以使用自定义登录,在自己的服务器或者云函数内,为用户签发带有自定义身份 ID 的自定义登录凭证 Ticket,随后用户端 SDK 便可以使用 Ticket 登录 CloudBase。
自定义登录一般用于下面几种场景:
自定义登录需要以下几个步骤:
登录 CloudBase 控制台,在 环境 -> 登录授权下的自定义登录栏中,点击「私钥下载」或者「私钥复制」:
私钥是一份携带有 JSON 数据的文件,请将下载或复制的私钥文件保存到您的服务器或者云函数中,假设路径为
/path/to/your/tcb_custom_login.json
。
调用 CloudBase 服务端 SDK,在初始化时传入自定义登录私钥,随后便可以签发出 Ticket,并返回至用户端
const cloudbase = require("@cloudbase/node-sdk"); |
开发者也可以编写一个云函数用于生成 Ticket,并为其设置 HTTP 访问服务,随后用户端便可以通过 HTTP 请求的形式获取 Ticket,详细的方案请参阅 使用 HTTP 访问云函数。
customUserId 必须满足以下需求:
_-#@(){}[]:.,<>+#~
中的字符。用户端应用获取到 Ticket 之后,便可以调用客户端 SDK 提供的
auth.signInWithTicket()
登录 CloudBase:
import cloudbase from '@cloudbase/js-sdk'; |
自定义登录一定需要自己假设用于创建 Ticket 的服务器吗?
进入 云开发 CloudBase 控制台,在 登录授权 设置页面中,开启用户名密码登录:
绑定用户名流程
import cloudbase from "@cloudbase/js-sdk"; |
绑定用户名之前,用户需要先使用其他方式进行登录,例如邮箱登录、微信公众号登录等,但不包括匿名登录
下面以邮箱登录为例:
const auth = app.auth(); |
绑定用户名时,可以检查在当前云开发环境下,此用户名是否存在。然后再调用绑定用户名的接口
const auth = app.auth(); |
使用用户名+密码登录 CloudBase
const auth = app.auth(); |
注意:用户名登录和邮箱登录的密码是相同的
使用短信验证码登录,您可以让用户使用自己的手机号,结合短信验证码或密码注册、登录 CloudBase,并且还可以更新或者解绑登录使用的手机号。
开通短信验证码登录
登录流程
import cloudbase from "@cloudbase/js-sdk"; |
首先需要用户填入自己的手机号,然后调用 SDK 的发送短信验证码接口:
app |
调用发送短信接口后,手机将会收到云开发的短信验证码。用户填入短信验证码,以及自定义密码后,调用注册账号接口
app |
app |
CloudBase Framework 是云开发官方出品的云原生一体化部署工具,可以帮助开发者将静态网站、后端服务和小程序等应用,一键部署到云开发 Serverless 架构的云平台上,自动伸缩且无需关心运维,聚焦应用本身,无需关心底层配置和资源。
云开发应用可以理解为运行在云开发环境的应用,例如一个包含前后端、数据库等能力等服务,可以通过一键部署,直接部署在云开发环境中,使用云开发底层的各项 Serverless 资源,享受弹性免运维的优势。
一个云开发应用可以拆解为三个部分,包括代码、声明式配置和环境变量信息。
如何开发一个云开发应用
在使用 CloudBase Framework 之前,您需要需要创建一个
cloudbaserc.json
配置文件,cloudbaserc.json
文件是 CloudBase CLI 、CloudBase VSCode 插件和 CloudBase 应用的配置文件,配置文件会关系到云开发如何构建和部署您的应用。
默认情况下,使用 cloudbase init
初始化项目时,会生成 cloudbaserc.json
文件作为配置文件,您也可以使用 --config-file
指定其他文件作为配置文件,文件必须满足格式要求。
CloudBase Framework 配置文件包含以下几类配置信息:
目前支持的插件名称请参阅 https://github.com/Tencent/cloudbase-framework#目前支持的插件列表
示例
{ |
配置在构建部署生命周期前后,需要执行的自定义动作
{ |
在云端一键部署场景下,您需要完善应用依赖配置来声明应用依赖的扩展资源和环境变量参数
"framework": { |
配置文件支持动态变量的特性。在 cloudbaserc.json
中声明 "version": "2.0"
即可启用。
动态变量特性允许
cloudbaserc.json
配置文件中使用动态变量,从环境变量中获取动态的数据。使用{}
包围的值定义为动态变量,可以引用数据源中的值。例如`{env.ENV_ID}:
cloudbaserc.json
和 .env
文件. |
ENV_ID=pro-123 |
cloudbaserc.json
文件内通过 env
注入模板变量{ |
tcb framework deploy |
假设你已经完成了以上模板变量的配置
.env.dev
文件. |
.env.dev
文件添加变量ENV_ID=dev-123 |
cloudbase framework deploy --mode dev |
完整示例
// https://docs.cloudbase.net/framework/config |
其他项目配置文件示例
// https://docs.cloudbase.net/framework/config |
插件可以处理应用中的一些独立单元的构建、部署、开发、调试等流程。例如 website 插件可以处理静态网站等单元,node 插件可以处理 koa 、express 等 node 应用。
插件的配置写在 cloudbaserc.json 文件中,具体请参考配置说明中的 插件配置可以手动填写,也可以自动生成。
自动检测生成插件配置
可以在项目目录下直接运行 cloudbase
命令进行自动检测生成插件配置文件并部署
cloudbase |
手动填写插件配置
{ |
官方插件列表
常用插件介绍
云开发 CloudBase Framework 框架「Function」插件: 通过云开发 CloudBase Framework 框架将静态网站一键部署云开发环境,提供生产环境可用的 CDN 加速、自动弹性伸缩的高性能网站服务。可以搭配其他插件如 Node 插件、函数插件实现云端一体开发。
如果想全新开始一个项目,可以直接执行 init 来从模板开始一个网站项目
# 部署 |
cloudbase init 之后会创建云开发的配置文件 cloudbaserc.json,可在配置文件的 plugins 里修改和写入插件配置
{ |
功能特性
{ |
云开发 CloudBase Framework 框架「登录配置」插件: 通过云开发 CloudBase Framework 框架一键设置环境下的登录配置。
// https://docs.cloudbase.net/framework/plugins/framework-plugin-auth |
云开发 CloudBase Framework 框架「Database」插件: 通过云开发 CloudBase Framework 框架一键配置云开发数据库集合、索引,使用高性能的 Serverless 化的 NoSQL 数据库服务。可以搭配其他插件如 Website 插件、Node 插件实现云端一体开发。
// https://docs.cloudbase.net/framework/plugins/framework-plugin-database |
云开发 CloudBase Framework 框架「小程序」插件: 通过云开发 CloudBase Framework 框架一键部署微信小程序应用。
// https://docs.cloudbase.net/framework/plugins/framework-plugin-mp |
默认模板的 appid 和 privateKeyPath 为空,需要开发者填入
云开发 CloudBase Framework 框架「Node.js App」插件: 通过云开发 CloudBase Framework 框架将 Node 应用一键部署云开发环境,提供自动弹性伸缩的高性能 Node 应用服务,支持底层部署为函数或者 云托管,可以搭配其他插件如 Website 插件、函数插件实现云端一体开发。
功能特性
如果目前已有 Node 应用项目
cloudbase |
如果想全新开始一个项目,可以直接执行 init 来从模板开始一个项目
cloudbase init |
// https://docs.cloudbase.net/framework/plugins/framework-plugin-node |
entry
默认 app.js
Node 服务入口文件,相对于projectPath,需要导出 app 或者 server 的实例,同时也支持导出异步获取 app 的 tcbGetApp 方法,方法的返回值为 app 或者 server 的实例。
如 koa 服务的 app.js
const Koa = require('koa'); |
nest 服务的 app.js
const express = require('express'); |
功能特性
// https://docs.cloudbase.net/framework/plugins/framework-plugin-container |
云开发部署模板参考:https://github.com/TencentCloudBase/cloudbase-templates
node插件文档 https://docs.cloudbase.net/framework/plugins/framework-plugin-node
# 初始化egg项目 |
编写cloudbaserc.json
{ |
添加启动入口文件
; |
部署
tcb framework deploy |
node插件文档 https://docs.cloudbase.net/framework/plugins/framework-plugin-node
# 初始化koa项目 |
编写cloudbaserc.json
{ |
修改
app.js
,导出module.exports = app
部署
tcb framework deploy |
# 初始化react项目 |
编写cloudbaserc.json
{ |
部署
tcb framework deploy |
# 初始化vue项目 |
编写cloudbaserc.json
{ |
部署
tcb framework deploy |
# 初始化hexo项目 |
编写cloudbaserc.json
{ |
部署
tcb framework deploy |
使用 hexo 命令行初始化一个项目
npx hexo init hexo-hello-world |
部署项目
# tcb env list 查看环境列表 |
初始化项目
npx vue create vue-hello-world |
发布项目
# tcb env list 查看环境列表 |
在面板上创建一个
NoSQL
的数据库,参考地址
在项目中安装连接数据库的
SDK
参考文档
npm install @cloudbase/node-sdk |
初始化数据库连接参考地址
import cloudbase from '@cloudbase/node-sdk'; |
env
的获取地址
secretId
和secretKey
获取:https://console.cloud.tencent.com/cam/capi
Tencent CloudBase Toolkit 是腾讯云 - 云开发发布的 VS Code(Visual Studio Code)插件。该插件可以让您更好地在本地进行云开发项目开发和代码调试,并且轻松将项目部署到云端。
通过 Tencent CloudBase Toolkit 插件,您可以:
同时,VS Code 插件也支持了云函数本地调试与云端调试,帮助你快速定位代码问题
登录
创建新项目
注意:
CloudBase Toolkit
插件依赖于cloudbaserc.json
配置文件,只有当前项目的根目录下存在 cloudbaserc.json 配置文件时
,才能使用CloudBase Toolkit
插件进行相关操作。
如果您还没有云开发项目,可以使用初始化操作创建一个全新的云开发项目,CloudBase Toolkit 提供了部分模板项目供选择。
打开一个空的文件夹作为根目录
,点击侧边栏的云开发图标,点击下图示例中的条目
VS Code 插件会默认使用当前窗口打开文件夹的根目录下的
cloudbaserc.json
配置文件,如果你使用了 VS Code 工作区,则会使用工作区中的第一个项目文件夹根目录下的配置文件
关于 cloudbaserc.json
配置文件的详情可以参考这里 🔗
tcb framework deploy
一键部署# 函数和静态网站一起部署 |
对云函数进行
部署/删除/下载
代码等操作时,必须选中云函数文件夹
,否则会因为无法解析到准确的函数名称,而导致操作失败。
右键选中函数文件夹,点击部署云函数即可。CloudBase Toolkit 支持同时选择多个云函数进行部署。
CloudBase Toolkit 支持两种部署云函数的方式:
node_modules
目录node_modules
目录,云函数会自动在线安装依赖下载函数代码
使用下载函数代码功能,可以将云端函数代码下载到本地,进行操作时,需要选择云函数名称对应的目录。CloudBase Toolkit 支持同时选择多个云函数,下载云函数代码。
增量更新
CloudBase Toolkit 支持上传单个文件或文件夹到云函数中,而无需重新上传整个云函数
CloudBase Toolkit 支持上传文件/文件夹到静态网站存储中,同时支持文件多选,既可以同时选择多个文件上传。
CloudBase Toolkit 提供了两种上传方法:
使用 CMS 扩展时将在当前环境创建云函数、云数据库等资源
工作原理
环境需要使用按量付费
安装完成可以看到已经部署好的云函数、静态资源、云数据库
登录部署的CMS界面操作演示
npm install -g @cloudbase/cli@latest
下面是基本的目录结构,采用了 Monorepo 的组织规范,并使用 lerna 进行管理
admin
: 前端管理界面cms-api
:RESTful API
服务cms-init
:CMS 部署初始化相关脚本service
:后端服务,提供系统管理相关的服务. |
复制项目根目录下的
.env.example
为.env.local
,并填写相关的配置
# 您的云开发环境 Id |
在项目根目录下运行下面的命令:
npm install && npm run setup |
如果你使用
npm run setup
命令出现异常,你可以分别到packages
目录下的文件内,手动执行npm install
命令。
在项目根目录下运行下面的命令,会将 CloudBase CMS
的管理控制台部署到静态网站,Node 服务部署到云函数中
npm run deploy |
控制台管理
tcb-ext-cms-servic
:该服务提供登录鉴权功能,用户在 CMS 管理界面通过通过用户名和密码来进行登录时,会通过 HTTP 来请求该函数;提供 API 接口功能,所有对内容的操作和管理都会经过此函数调用,内容操作会根据用户权限来进行数据库操作。
tcb-ext-cms-init
:提供初始化应用功能,安装扩展后,会通过该函数来进行静态资源的部署和密码的生成和设置,修改账号密码或者部署路径等扩展参数都会再次执行该函数来进行更新tcb-ext-cms-api
:提供CMS RESTful API
访问能力,所有RESTful API
请求都会经过此函数调用
存储图片、文件等 CMS 系统上传的文件。
tcb-ext-cms-projects
集合:CMS 系统项目数据tcb-ext-cms-schemas
集合:CMS 系统内容配置数据,CMS 所有的系统内容类型配置、字段配置等信息都存储在该集合内tcb-ext-cms-users
集合:CMS 系统用户数据,存储 CMS 的用户信息,包括管理员和运营者的账号信息,包括角色信息等tcb-ext-cms-webhooks
集合: CMS 系统 webhook 集合,存储 CMS 系统的回调接口配置,CMS 系统数据的变更可以通过回调来进行同步。tcb-ext-cms-user-roles
集合:CMS 系统用户角色配置集合,存储 CMS 系统的自定义用户角色信息tcb-ext-cms-settings
集合:CMS 系统配置集合,存储 CMS 系统的设置请查看环境下云函数
tcb-ext-cms-init
的执行日志,获取失败原因。CloudBase CMS 安装时需要使用tcb-ext-cms-init
函数执行初始化的工作,当出现异常时,会导致安装失败。
.env.example
为 .env.local
,并根据文件中的内容进行配置# 您的云开发环境 Id |
packages/service/.env.example
为 packages/service/.env.local
,并根据文件中的内容进行配置TCB_ENVID=test-xx # 环境ID |
packages/admin/public/config.example.js
为 packages/admin/public/config.js
,并根据文件中的内容进行配置window.TcbCmsConfig = { |
安装依赖
# 安装 lerna 依赖 |
初始部署
通过部署动作,触发初始化操作
npm run deploy |
启动开发
运行下面的命令,成功后,可以访问 http://localhost:8000/
打开 CMS 管理界面
cd packages/admin && npm run dev |
1. vue微应用接入
# 新建 Vue 微应用项目 |
npm run build
新建
上传成功后,可以在管理后台中查看
2. react微应用接入
# 新建 React 微应用项目 |
在系统设置中开启API访问
在项目设置中的 API 访问 Tab 设置允许通过 RESTful API 访问
然后复制访问连接,在postman中访问查看效果
API鉴权访问
在系统设置中开启API鉴权访问,并创建token
提示需要接口授权才可以访问
在请求头加入创建好的token即可
当使用 API Token 调用 RESTful API 时,需要在 HTTP 请求
Header
中添加下面的配置
Authorization: Bearer API_TOKEN |
API_Token
为在系统设置中生成的 Token
,Bearer
为固有字段,两者通过空格连接。
控制台 https://console.cloud.tencent.com/lowcode/overview/index
微信云托管以容器服务为核心,提供方便易用的存储体系、微信生态、安全鉴权等服务能力;搭配简单易懂的操作面板,集成资源监控,资源告警,流水线等自动化功能,是一站式的后端云服务。
微信云托管使用目前主流的容器平台Docker以及容器编排技术Kubernetes(简称K8S),来管理你的项目
代替服务器部署小程序/公众号后端。
必须先有微信小程序/公众号才可以开通微信云托管,但部署在微信云托管上的服务可以通过公网访问,因此可以被APP/网站/其他平台小程序的前端调用,只是无法享受 callcontainer 内部链路带来的安全防DDoS/请求加速等优势。
微信云托管和微信云开发是两套独立体系,微信云托管的环境只能在微信云托管控制台看到,在微信开发者工具的云开发控制台中不能看到
微信云托管是整合了腾讯云底层资源和微信生态链路的综合解决方案。原云开发中的云托管独立出来,升级为微信云托管,补充数据库、ci/cd、灰度发布等更多完整后端功能和企业级devops能力。
容器系统时间默认为 UTC 协调世界时间 (Universal Time Coordinated),与本地所属时区 CST (上海时间)相差 8 个小时:
在构建基础镜像或在基础镜像的基础上制作自定义镜像时,在 Dockerfile
中创建时区文件即可解决单一容器内时区不一致问题,且后续使用该镜像时,将不再受时区问题困扰。
Dockerfile
文件。FROM centos as centos COPY --from=centos /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" > /etc/timezone |
小程序/公众号中调用
// 在小程序 app.js中初始化云托管 |
// 在小程序中调用云托管服务 |
初始化您的 Nest.js 项目
npm i -g @nestjs/cli |
在根目录下,执行以下命令在本地直接启动服务。
cd nest-app && npm run start |
打开浏览器访问 http://localhost:3000,即可在本地完成 Nest.js 示例项目的访问。
新建服务
点击发布后,云托管会执行Dockerfile
构建流水线,到日志可以查看构建进度
微信云托管部署成功后,可以在实例列表,点击进入容器看到代码,这里里面的内容不能修改,在容器启动后会覆盖
调试接口
npm install -g @wxcloud/cli |
获取 CLI 密钥
CLI工具的登录采用了密钥形式,在使用前需要前往微信云托管控制台 - 设置 -CLI 密钥生成,生成时需要账号管理员扫码,可以新建多个密钥,用于在不同地方使用。
传入微信 APPID 和CLI密钥,操作登录
wxcloud login [OPTIONS] |
查看登录账号下所有的环境列表
wxcloud env:list [OPTIONS] |
查看指定环境下的所有服务列表
wxcloud service:list [OPTIONS] |
以koa作为后端演示
全局安装 koa-generator
脚手架.
npm install -g koa-generator |
创建项目
# 使用ejs引擎 |
cd wxcloud-debug-koa // 进入项目根目录 |
www/bin
中的端口为9000
routes/index
中代码为router.get('/', async (ctx, next) => { |
打开浏览器访问 http://localhost:9000,即可在本地完成 koa
示例项目的访问。
编写dockerfile
FROM daocloud.io/library/node:14.7.0 |
如只在 VSCode 中同时编辑调试一个服务,
可直接打开服务代码目录作为根目录
(暂不支持 VSCode Workspace 工作区),保证根目录下有Dockerfile
文件,插件面板中会显示该服务的名字
调试过程中因需要获取微信信息,会使用云托管 CLI Key,因此需在 VSCode 插件配置填入小程序 appid 和 cli key,点击插件面板的 ⚙ 图标打开配置:
构建镜像,启动容器
右键服务名,选择 start,将构建镜像并启动容器
可以看到构建过程
启动容器需要相应的容器配置信息(.cloudbase/container/debug.json
),如果没有会提示创建,配置文件字段和含义如下:
其中需特别注意端口号
containerPort
、Dockerfile
路径dockerfilePath
、自定义环境变量envParams
此时出现异常,我们修改.cloudbase/container/debug.json
中的containerPort
为koa服务中定义的9000端口,重新构建即可
容器构建和启动成功后,在插件面板状态 icon 会相应更新:
也可以通过docker ps
查看已启动的服务
我们在云托管后台可以看到此时默认启动了一个调试服务,我们不要去修改它
此时可以请求容器了,在插件面板旁会展示两个端口号,通过第一个端口访问容器会带有微信相关信息(header 中包含 appid 等),通过第二个端口访问容器不会带有微信相关信息而是直接请求到容器内部,右键服务选择 Open in browser (via WX server) 和 Open in browser (no WX auth) 可以在浏览器中打开,分别对应这两种情况,也可以写代码或通过 POSTMAN 等工具请求
请求不经过微信服务器返回:http://127.0.0.1:27081/
不带微信信息的端口,直接访问即可,适合在浏览器调试
请求经过微信服务器返回:http://127.0.0.1:27082/
微信端口,请求时会模拟微信用户信息的 Header,如 x-wx-openid,适合微信开发者工具中使用
在微信开发者工具中,可以选择连接到 VSCode 启动的容器,从而在小程序模拟器中访问本地云托管容器
此能力需要使用微信开发者工具 v1.05.2202242 及以上版本,并更新 VSCode 插件到 v1.0.12 以上。
创建一个小程序测试项目
在 微信开发者工具 的
Docker
面板中,找到 「Running Containers
」,右击容器名称,选择Attach Weixin Devtools
,即可在小程序代码中,使用wx.cloud.callContainer
访问容器。
需要退出再次Detach Weixin Devtools
调用时,需要注意
Header
中的X-WX-SERVICE
需要与容器名保持一致
修改小程序app.js
代码
// app.js |
查看请求日志
或者通过docker logs
查看
进入终端
如果需要进入到容器内部终端调试定位问题,可以右键服务名选择 Attach Shell 进入容器内终端
通过微信云托管 VSCode 插件,可以实现实时开发,即代码变动时,不需要重新构建和启动容器,即可查看变动后的效果。
选择 Live Coding
右键点击需要调试的容器,选择
Live Coding
,将自动生成Dockerfile.development
和docker-compose.yml
2 个文件并启动容器。
如果生成失败,我们需要自行配置
开发模式的 docker-compose.yml
# 开发模式的 docker-compose.yml |
修改端口 9000
ports: |
实时开发使用项目目录下的 Dockerfile.development
# Dockerfile.development |
修改koa启动入口
node www/bin
启动实时服务
修改本地代码,不用重启容器即可查看效果
weixin-cloudbase
然后安装Docker
面板内,右击 Proxy nodes for VPC access
中的 api.weixin.qq.com
,点击启动(Start
)Start
),容器内即可访问本地云调用填入环境ID
启动api.weixin.qq.com
服务
启动自己的业务服务,在业务服务运行过程中,启动 vpc 中的 api.weixin.qq.com
服务
插件将会在你的云托管环境中开启一个代理服务,用于和本地
api.weixin.qq.com
服务,同时和业务服务共享同一个网络,就实现了本地的「开放接口服务」,需要注意,本地调试中只是模拟了业务服务的所处环境,不是真实的线上部署情况。
云调用是具有「免鉴权调用微信开放服务接口」特性的能力,是云开发/云托管中微信生态的一部分。
在云调用出现之前,微信开放服务接口的正常调用,需要开发者使用密钥信息获取access_token
,并自己维护 token
的有效期和安全。而获取access_token
,涉及到密钥交互请求,容易暴漏密钥导致被盗用,对开发者和微信服务都有消极的影响。
云调用主要打造免鉴权,也就是免密钥,全程不暴漏任何信息,开发者无需维护access_token
,那对于接口请求的合法性判定,完全由与微信同链路的微信云托管参与实施。
前往控制台 - 云调用 - 云调用权限配置,按照自己的业务需要配置接口。
比如你要在服务中调用文字安全检测接口
此接口的调用地址如下:https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN
在配置时,只需要 api.weixin.qq.com
之后,?
参数之前的部分,所以应该在配置输入框里填写如下
/wxa/msg_sec_check |
在云托管服务中,微信后台周期性的将开放接口所必须要的
access_token
,推送到服务的容器实例中。在使用时只需要从容器本地读取令牌,就可以包装请求去调用了:
access_token
推送的时间间隔为10
分钟,令牌的有效期为30
分钟; 挂载路径为:/.tencentcloudbase/wx/cloudbase_access_token
; 在同一个环境中所有的容器实例,推送的access_token
相同
查看容器内access_token
如果需要获取容器内的access_token
调试接口,需要在接口中填入cloudbase_access_token=容器内的access_token
// https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/guide/weixin/token.html |
npm i request request-promise -S |
在代码routes/home
中添加
router.get('/msg_sec_check', async (ctx, next) => { |
在云托管权限控制台添加接口权限
开启 api.poetries.top
本地调试服务
通过微信服务器模拟小程序请求
也可以在小程序中访问
在代码routes/home
中添加
// 云托管内调用云函数 |
小程序云函数banner代码
// 云函数入口文件 |
在云托管权限控制台添加接口权限
开启 api.poetries.top
本地调试服务
通过微信服务器模拟小程序请求
也可以在小程序中访问
在代码routes/home
中添加
// 云托管内获取小程序码 |
在云托管权限控制台添加接口权限
开启 api.poetries.top
本地调试服务
通过微信服务器模拟小程序请求
也可以在小程序中访问
Serverless
又名无服务器,所谓无服务器并非是说不需要依赖和依靠服务器等资源,而是开发者再也不用过多考虑服务器的问题,可以更专注在产品代码上。Serverless
是一种软件系统架构的思想和方法,它不是软件框架、类库或者工具。它与传统架构的不同之处在于,完全由第三方管理,由事件触发,存在于无状态(Stateless
)、 暂存(可能只存在于一次调用的过程中)计算容器内。构建无服务器应用程序意味着开发者可以专注在产品代码上,而无须管理和操作云端或本地的服务器或运行时(运行时通俗的讲 就是运行环境,比如 nodejs
环境,java
环境,php
环境)。Serverless
真正做到了部署应用 无需涉及基础设施的建设,自动构建、部署和启动服务。通俗的讲:Serverless 是构建和运行软件时不需要关心服务器的一种架构思想
虚拟主机已经是快被淘汰掉的上一代产物了。云计算涌现出很多改变传统 IT 架构和运维方 式的新技术,比如虚拟机、容器、微服务,无论这些技术应用在哪些场景,降低成本、提升 效率是云服务永恒的主题。Serverless 的出现真正的解决了降低成本、提升效率的问题。它真正做到了弹性伸缩
、高并发
、按需收费
、备份容灾
、日志监控
等。
传统的开发模式
新型的serverless开发模式
Serverless 正在改变未来软件开发的模式和流程
ServerFul 架构就是 n 台 Server 通过 网络通信 的 方式 协作在一起,也可以说 ServerFul 架构是基于 Server 和 网络通信(分布式计算) 的 软件实现架构 , Server 可 以是 虚拟机 物理机 ,以及基于硬件实现的云的云服务器
Serverless 的核心特点就是实现自动弹性伸缩和按量付费
Serverless
架构中,你不用关心应用运行的资源(比如服务配置、磁盘大小)只提供一份代码就行。Serverless
架构中,计费方式按实际使用量计费(比如函数调用次数、运 行时长),不按传统的执行代码所需的资源计费(比如固定 CPU
)。计费粒度也精确到了毫 秒级,而不是传统的小时级别。个别云厂商推出了每个月的免费额度,比如腾讯云提供了每 个月 40 万 GBs 的资源使用额度和 100 万次调用次数的免费额度。中小企业的网站访问量不 是特别大的话完全可以免费使用。Serverless
架构的弹性伸缩更自动化、更精确,可以快速根据业务并发扩容更 多的实例,甚至允许缩容到零实例状态来实现零费用,对用户来说是完全无感知的。而传统 架构对服务器(虚拟机)进行扩容,虚拟机的启动速度也比较慢,需要几分钟甚至更久。FaaS
和 BaaS
组成先看看招聘信息
看看最近 2 年 Github 的 start 数量和周下载量
目前已经使用了 serverless 的大公司
Serverless 中云函数被第一次调用会执行冷启动,Serverless 中云函数被多次连续调用会 执行热启动
通过前面的介绍,我们认识到了云函数和
serverless
,但是可能会有一个很迷惑云函数和serverless
到底有什么区别,他们之间有什么联系,为什么我在创建云函数的时候选择模板方式创建最后创建的是serverless
,而不是云函数呢。下面我们将解答云函数和serverless
的区别
Serverless Framework
是Serverless
公司推出的一个开源的Serverless
应用开发框架Serverless Framework
是由 Serverless Framework Plugin
和 Serverless Framework Components
组成Serverless Framework Plugin
实际上是一个函数的管理工具,使用这个工具,可以很轻松的 部署函数、删除函数、触发函数、查看函数信息、查看函数日志、回滚函数、查看函数 数据等。简单的概括就是serverless
其实就云函数的集合体,使用serverless
后我们创建的云函数不需要手动去创建触发器等操作官方地址
serverless
控制面板上创建,然后在vscode
中使用插件的方式下载到本地(注意: 编辑器上要选择和创建serverless
地区相同,才能看到项目,否则是看不到项目代码的)serverless cli
命令方式创建,个人也更推荐使用这种方式创建,修改代码,然后部署到后台腾讯云服务上npm i serverless -g |
查看支持的框架部署
sls registry |
# 例如初始化egg项目 |
部署
sls deploy |
通过该 VS Code 插件,您可以
界面上创建应用
vscode
上安装插件vscode
安装后插件登录并且拉取应用密钥地址 https://console.cloud.tencent.com/cam/capi 填入appID、secretID、secretKey即可拉取云函数到本地
创建一个云函数
给云函数创建触发器来访问
创建了触发器后,就可以通过触发器里面的访问路径来访问云函数
我们可以在控制台修改代码,然后重新部署云函数,或者开启自动安装依赖等
Serverless Framework Components 可以看作是一个组件集,这里面包括了很多的 Components,有基础的 Components,例如 cos、scf、apigateway 等,也有一些拓展的 Components,例如在 cos 上拓展出来的 website,可以直接部署静态网站等,还有一些框 架级的,例如 Koa,Express等
上面既然介绍了云函数和
serverless
的区别,现在我们介绍下什么场景下需要使用serverless
,而不是使用云函数,其实在实际开发过程中,我们都是使用serverless
而不去使用云函数,毕竟云函数的使用场景受限,或者说比较基础。打一个简单的比方,在写js
操作dom
的时候,你会选择用原生js
还是会使用jquery
一样的比喻
基于云函数的命令行开发工具
通过
Serverless Framework
,开发者可以在命令行完成函数的开发、部署、调试。还可以结合前端服务、 API 网关、数据库等其它云上资源,实现全栈应用的快速部署。
传统应用框架的快速迁移
Serverless Framework
提供了一套通用的框架迁移方案,通过使用Serverless Framework
提供的框架组件(Egg/Koa/Express
等,更多的框架支持可以参考),原有应用仅需几行代码简单改造, 即可快速迁移到函数平台。同时支持命令行与控制台的开发方式。
https://github.com/serverless/serverless/tree/master/docs/providers
egg 框架中默认已经配置好了静态资源,我们可以直接访问。要注意的是 serverless 服务器 上面只有根目录的 tmp 目录有写入权限,所以需要配置 egg 日志存储的目录。默认创建好 项目以后有如下配置。
// config/config.default.js |
如果除了代码部署外,您还需要更多能力或资源创建,如自动创建层托管依赖、一键实现静态资源分离、支持代码仓库直接拉取等,可以通过应用控制台,完成 Web 应用的创建工作
初始化项目
mkdir egg-example && cd egg-example |
部署上云
接下来执行以下步骤,对本地已创建完成的项目进行简单修改,使其可以通过 Web Function 快速部署,对于 Egg 框架,具体改造说明如下:
0.0.0.0:9000
。/tmp
目录可读写。scf_bootstrap
启动文件。1. (可选)配置 scf_bootstrap 启动文件
您也可以在控制台完成该模块配置。
在项目根目录下新建
scf_bootstrap
启动文件,在该文件添加如下内容(用于配置环境变量和启动服务,此处仅为示例,具体操作请以您实际业务场景来调整):
|
新建完成后,还需执行以下命令修改文件可执行权限,默认需要 777 或 755 权限才可正常启动。示例如下:
chmod 777 scf_bootstrap |
2. 控制台上传
您可以在控制台完成启动文件 scf_bootstrap 内容配置,配置完成后,控制台将为您自动生成 启动文件,和项目代码一起打包部署
启动文件以项目内文件为准,如果您的项目里已经包含 scf_bootstrap
文件,将不会覆盖该内容。
查看函数,修改代码查看日志等
高级配置管理
您可在“高级配置”里进行更多应用管理操作,如创建层、绑定自定义域名、配置环境变量等。
目前推荐使用 web 函数,也就是
HTTP 组件
,现在所有的serverless web 应用都是基于component: http
组件的。
通过
Serverless Framework
的HTTP 组件
,完成 Web 应用的本地部署
前提条件
已开通服务并完成 Serverless Framework 的 权限配置
# 初始化egg项目 |
# 编写完善配置文件 serverless.yml |
全部配置详细查看 https://github.com/serverless-components/tencent-http/blob/master/docs/configure.md
部署
创建完成后,在根目录下执行
sls deploy
进行部署,组件会根据选择的框架类型,自动生成scf_bootstrap
启动文件进行部署
我们也可以在项目跟目录自己创建启动文件scf_bootstrap
,然后chmod 777 scf_bootstrap
// scf_bootstrap |
# 微信扫码即可 |
注意:由于启动文件逻辑与用户业务逻辑强关联,默认生成的启动文件可能导致框架无法正常启动,建议您根据实际业务需求,手动配置启动文件,详情参考各框架的部署指引文档。
如果部署过程遇到问题不好排除,如以下问题:
来到控制台创建项目
在控制台安装依赖包
我们在
sls deploy
忽略了node_modules
,因此需要在控制台安装依赖
访问应用
到控制台查看
删除应用
sls remove |
随着项目复杂度的增加,deploy
上传会变慢。所以,让我们再优化一下,在项目根目录创建 layer/serverless.yml
# layer/serverless.yml |
创建后可见层对应信息
我们也可以在控制台新建层绑定到对应的函数即可
控制台上传层有大小限制
文件夹支持250M
修改以上项目下的serverless.yml加入层配置
回到项目根目录,调整一下根目录的 serverless.yml
# 编写完善配置文件 serverless.yml |
排除node_modules
exclude: # 被排除的文件或目录 |
配置层
layers: |
通过配置层layer,代码和node_modules
分离,sls deploy
更快
接着执行命令
sls deploy --target=./layer
部署层,然后再次部署sls deploy
看看速度应该更快了
每次node_modules
改变都需要
sls deploy --target=./layer
部署层, 更新 node_modules
层sls deploy
重新部署layer 的加载与访问
layer 会在函数运行时,将内容解压到
/opt
目录下,如果存在多个layer
,那么会按时间循序进行解压。如果需要访问layer
内的文件,可以直接通过/opt/xxx
访问。如果是访问node_module
则可以直接import
,因为scf
的NODE_PATH
环境变量默认已包含/opt/node_modules
路径。
目前推荐使用 web 函数,也就是
HTTP 组件
,现在所有的serverless web 应用都是基于component: http
组件的。
serverless.yml
文件npm i serverless -g |
# https://github.com/serverless-components/tencent-egg/blob/master/docs/configure.md |
sls deploy # sls deploy --debug可以查看日志 |
.env
的serverless
的登录信息浏览器打开提示缺少模块
我们在控制台上点击
打开自动安装依赖后重新部署即可看到node_modules
,这时再次访问浏览器地址
初始化您的 Nest.js 项目
npm i -g @nestjs/cli |
在根目录下,执行以下命令在本地直接启动服务。
cd nest-app && npm run start |
打开浏览器访问 http://localhost:3000,即可在本地完成 Nest.js 示例项目的访问。
部署上云
接下来执行以下步骤,对已初始化的项目进行简单修改,使其可以通过 Web Function 快速部署,此处项目改造通常分为以下两步:
scf_bootstrap
启动文件。0.0.0.0:9000
。main.ts
,监听端口改为9000
:scf_bootstrap
启动文件,在该文件添加如下内容(用于启动服务):您也可以在控制台完成该模块配置。
# scf_bootstrap |
新建完成后,还需执行以下命令修改文件可执行权限,默认需要 777 或 755 权限才可正常启动。示例如下:
chmod 777 scf_bootstrap |
本地配置完成后,执行启动文件,确保您的服务可以本地正常启动,接下来,登录 Serverless 应用控制台,选择Web 应用>Nest.js 框架,上传方式可以选择本地上传或代码仓库拉取
注意:启动文件以项目内文件为准,如果您的项目里已经包含 scf_bootstrap 文件,将不会覆盖该内容。
目前推荐使用 web 函数,也就是
HTTP 组件
,现在所有的serverless web 应用都是基于component: http
组件的。
# 编写完善配置文件 serverless.yml |
在项目根目录下新建 scf_bootstrap
启动文件,在该文件添加如下内容(用于启动服务):
|
忽略node_modules
文件上传
# serverless.yml |
执行 sls deploy
(sls deploy --debug
查看部署日志)
查看部署信息
sls info |
实时开发并上传
每次改动文件,都实时部署
sls dev |
删除部署项目
sls remove |
随着项目复杂度的增加,deploy
上传会变慢。所以,让我们再优化一下,在项目根目录创建 layer/serverless.yml
# layer/serverless.yml |
修改以上项目下的serverless.yml加入层配置
回到项目根目录,调整一下根目录的 serverless.yml
下的layers
# 编写完善配置文件 serverless.yml |
排除node_modules
exclude: # 被排除的文件或目录 |
配置层
layers: # 配置对应的 layer |
通过配置层layer,代码和node_modules
分离,sls deploy
更快
接着执行命令
sls deploy --target=./layer
部署层,然后再次部署sls deploy
看看速度应该更快了
每次node_modules
改变都需要
sls deploy --target=./layer
先部署层, 更新 node_modules
层sls deploy
重新部署layer 的加载与访问
layer 会在函数运行时,将内容解压到
/opt
目录下,如果存在多个layer
,那么会按时间循序进行解压。如果需要访问layer
内的文件,可以直接通过/opt/xxx
访问。如果是访问node_module
则可以直接import
,因为scf
的NODE_PATH
环境变量默认已包含/opt/node_modules
路径。
目前推荐使用 web 函数,也就是
HTTP 组件
,现在所有的serverless web 应用都是基于component: http
组件的。
初始化项目
npm i -g @nestjs/cli |
在根目录下,执行以下命令在本地直接启动服务。
cd nest-app && npm run start |
编写项目根目录下serverless.yml文件
全部配置详情
https://github.com/serverless-components/tencent-nestjs/blob/master/docs/configure.md
# serverless.yml |
全局安装 koa-generator
脚手架.
npm install -g koa-generator |
创建项目
# 使用ejs引擎 |
cd koa-demo // 进入项目根目录 |
npm start |
部署上云
接下来执行以下步骤,对已初始化的项目进行简单修改,使其可以通过 Web Function 快速部署,此处项目改造通常分为以下两步:
0.0.0.0:9000
。具体步骤如下:
在 Koa 示例项目中,修改监听端口到 9000
编写serverless.yml
# 文档 https://github.com/serverless-components/tencent-http/blob/master/docs/configure.md |
项目根目录新增 scf_bootstrap 启动文件
touch scf_bootstrap |
scf_bootstrap
|
部署
sls deploy |
查看部署信息
sls info |
移除
sls remove |
部署的静态资源会存储到COS中
初始化项目
vue init webpack vue-demo |
编写serverless.yml
# https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md |
执行部署
sls deploy |
如果希望查看更多部署过程的信息,可以通过
sls deploy --debug
命令查看部署过程中的实时日志信息
开发调试
部署了静态网站应用后,可以通过开发调试能力对该项目进行二次开发,从而开发一个生产应用。在本地修改和更新代码后,不需要每次都运行
serverless deploy
命令来反复部署。您可以直接通过serverless dev
命令对本地代码的改动进行检测和自动上传。
serverless.yml
文件所在的目录下运行 serverless dev
命令开启开发调试能力。serverless dev
同时支持实时输出云端日志,每次部署完毕后,对项目进行访问,即可在命令行中实时输出调用日志,便于查看业务情况和排障。查看状态
在serverless.yml
文件所在的目录下,通过如下命令查看部署状态:
serverless info |
移除
在serverless.yml
文件所在的目录下,通过以下命令移除部署的静态网站 Website
服务。移除后该组件会对应删除云上部署时所创建的所有相关资源。
$ serverless remove |
和部署类似,支持通过
sls remove --debug
命令查看移除过程中的实时日志信息
初始化项目
npm i create-umi -g |
编写serverless.yml
# https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md |
实时监控项目部署
sls dev |
编写serverless.yml配置
# https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md |
执行部署
sls deploy |
移除
sls remove |
注意:配置私有网络的服务器需要在同一个地区
购买云数据库mysql
新建mysql云函数
如果没有需要新建私有网络,需要和msyql实例同一个地区,选择了新建的私有网络,mysql实例那边网络需要修改一致
新建test数据库
创建user表
/************************************************** |
重新部署
浏览器中访问查看效果
购买MongoDB数据库
创建云函数
const {promisify} = require('util') |
创建触发器
狭义的 Serverless 是指现阶段主流的技术实现:狭义的 Serverless 是 FaaS
和 BaaS
组成
对象存储(Cloud Object Storage,COS)是一种存储海量文件的分布式存储服务,具有高扩 展性、低成本、可靠安全等优点。通过控制台、API、SDK 和工具等多样化方式,用户可简 单、快速地接入 COS,进行多格式文件的上传、下载和管理,实现海量数据存储和管理。
cnpm i cos-nodejs-sdk-v5 --save |
const COS = require('cos-nodejs-sdk-v5'); |
安装模块 multer https://github.com/expressjs/multer
npm install --save multer |
配置 form 表单
<!-- views/index.html --> |
配置内存存储引擎 (
MemoryStorage
),内存存储引擎将文件存储在内存中的Buffer
对象,它没有任何选项
var storage = multer.memoryStorage() |
接收文件上传文件到云存储
// app.js |
// services/tools.js |
上传文件需要注意
https://github.com/serverless-components/tencent-koa/blob/master/docs/upload.md
修改serverless.yml
app: appDemo |
完整serverless.yml
app: expressdemo |
部署,然后在webIDE开启自动安装依赖
sls deploy |
找到云函数对应的 api 网关
编辑 api 网关 点击域名管理
新建域名
解析域名
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。
HTTPS 是在 HTTP 的基础上添加了安全层,从原来的明文传输变成密文传输,当然加密与解 密是需要一些时间代价与开销的,不完全统计有 10 倍的差异。
在当下的网络环境下可以忽 略不计,已经成为一种必然趋势。 目前微信小程序请求 Api 必须用 https、Ios 请求 api 接口必须用 https
https 证书类型
创建证书
选择证书
配置域名
域名解析
scf_bootstrap
文件是针对 web
函数的,sls.js
入口文件是针对事件函数,主要是 serverless
封装了一些开源框架,改造的入口文件。目前推荐使用 web 函数,也就是
HTTP 组件
,现在所有的serverless web 应用都是基于component: http
组件的。
云函数 scf 针对每个用户帐号,均有一定的配额限制:
其中需要重点关注的就是单个函数代码体积
500mb
的上限。在实际操作中,云函数虽然提供了500mb
。但也存在着一个deploy
解压上限。
关于绕过配额问题:
npm install --production
就能解决问题cfs
文件系统来进行规避(参考文章)yum install yum-utils device-mapper-persistent-data lvm2 -y |
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
yum install docker-ce docker-ce-cli containerd.io -y |
systemctl start docker |
vi /etc/docker/daemon.json |
{ |
后续拉取镜像直接从 https://hub.docker.com 网站拉取速度更快
重启docker
systemctl restart docker |
docker pull daocloud.io/library/mysql:8.0.20 |
运行mysql镜像
docker run -d -p 3307:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456(设置登录密码) be0dbf01a0f3(镜像ID) |
进入mysql容器内部
至此mysql镜像搭建成功,下面我们使用
docker-compose
来管理docker容器,不在单独一个个安装MySQL、redis、nginx
# 使用国内源安装 |
设置docker-compose执行权限
chmod +x /usr/local/bin/docker-compose |
创建软链
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose |
测试是否安装成功:
$ docker-compose --version |
version: "3.0" |
FROM daocloud.io/library/node:14.7.0 |
开放端口9000、6380、3307
docker-compose -h
查看命令
docker-compose up
启动服务,控制台可见日志docker-compose up -d
后台启动服务docker-compose build --no-cache
重新构建镜像不使用缓存(最后docker-compose up -d
启动)docker-compose down
docker-compose pull
docker-compose restart
后台启动服务 docker-compose up -d
测试
云托管流水线部署更方便
这里我们上面部署使用的自建服务器上docker搭建的redis服务作为演示
这里我们上面部署使用的自建服务器上docker搭建的mysql服务作为演示
然后上传代码到github,通过云托管流水线构建
点击发布后,云托管会执行Dockerfile构建流水线,到日志可以查看构建进度
微信云托管部署成功后,可以在实例列表,点击进入容器看到代码,这里里面的内容不能修改,在容器启动后会覆盖
测试redis
需要注意,云函数的代码包不能超过500M
初始化您的 Nest.js 项目
npm i -g @nestjs/cli |
在根目录下,执行以下命令在本地直接启动服务。
cd nest-app && npm run start |
打开浏览器访问 http://localhost:3000,即可在本地完成 Nest.js 示例项目的访问。
部署上云
接下来执行以下步骤,对已初始化的项目进行简单修改,使其可以通过 Web Function 快速部署,此处项目改造通常分为以下两步:
scf_bootstrap
启动文件。0.0.0.0:9000
。main.ts
,监听端口改为9000
:scf_bootstrap
启动文件,在该文件添加如下内容(用于启动服务):您也可以在控制台完成该模块配置。
# scf_bootstrap |
新建完成后,还需执行以下命令修改文件可执行权限,默认需要 777 或 755 权限才可正常启动。示例如下:
chmod 777 scf_bootstrap |
本地配置完成后,执行启动文件,确保您的服务可以本地正常启动,接下来,登录 Serverless 应用控制台,选择Web 应用>Nest.js 框架,上传方式可以选择本地上传或代码仓库拉取
注意:启动文件以项目内文件为准,如果您的项目里已经包含 scf_bootstrap 文件,将不会覆盖该内容。
单个函数代码体积 500mb 的上限。在实际操作中,云函数虽然提供了 500mb
关于绕过配额问题:
如果超的不多,那么使用 npm install --production
就能解决问题
mac下安装docker: brew install docker
https://hub.docker.com 拉取镜像速度比较慢,我们推荐使用国内的镜像源访问速度较快 https://hub.daocloud.io
{ |
进入该网站https://hub.daocloud.io
获取镜像的下载地址
docker images
查看镜像docker ps
查看启动的容器 (-a
查看全部)docker rmi 镜像ID
删除镜像docker rm 容器ID
删除容器docker exec -it 1a8eca716169(容器ID:docker ps获取) sh
进入容器内部docker inspect bf70019da487(容器ID)
查看容器内的信息 删除none的镜像,要先删除镜像中的容器。要删除镜像中的容器,必须先停止容器。
$ docker rmi $(docker images | grep "none" | awk '{print $3}') |
$ docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') //停止容器 |
这里拉取nginx
、node
、redis
、mysql
镜像
进入https://hub.daocloud.io
搜索node,切换到版本获取下载地址
docker pull daocloud.io/library/node:12.18
docker tag 28faf336034d node
重命名镜像重命名镜像后IMAGE ID都是一样的
也可以导出镜像到本地备份 docker save -o node.image(导出镜像要起的名称) 28faf336034d(要导出的镜像的ID)
我们先删除之前的镜像 docker rmi 28faf336034d -f
强制删除
再次导入本地镜像
docker load -i node.image(导入的镜像名称)
然后再次重命名镜像即可
docker tag 28faf336034d node:v1.0(版本v1.0)
进入https://hub.daocloud.io
搜索mysql,切换到版本获取下载地址
docker pull daocloud.io/library/mysql:8.0.20
启动MySQL镜像
docker run -d(后台运行) -p 3307:3306(本机端口:MySQL运行端口) --name mysql(容器名称) -e MYSQL_ROOT_PASSWORD=123456(设置mysql密码) be0dbf01a0f3(mysql镜像ID) |
查看当前正在运行的镜像
docker ps -a(正在运行和停止的镜像-a都可见) |
删除容器
删除之前需要stop:docker stop bac2692e2b9a(容器ID)
docker rm bac2692e2b9a(容器ID:docker ps获取) |
进入容器内部
docker exec -it bac2692e2b9a(容器ID) sh(指定进入方式) |
我们使用Navicat新建一个连接测试一下
说明我们使用docker安装MySQL的方式是没问题的
查看MySQL容器日志
docker logs -f(查看最后几条) bac2692e2b9a(容器ID) |
重启容器
如果修改了容器配置,我们需要重新启动容器
docker restart bac2692e2b9a(容器ID) |
设置MySQL权限
mysql8.0后,需要设置,否则node连接不上
docker exec -it bac2692e2b9a sh |
# 远程连接权限 |
docker pull daocloud.io/library/redis:6.0.3-alpine3.11 |
启动Redis镜像
docker run -d -p 6380:6379 --name redis 29c713657d31(镜像ID) --requirepass 123456(redis登录密码) |
或进入redis镜像后在输入密码
交互式进入redis容器
docker exec -it 9751cbc96861(容器ID) sh |
docker pull daocloud.io/library/nginx:1.13.0-alpine |
启动Nginx镜像
服务器上启动
docker run --name nginx(起一个容器名称) -d(后台运行) -p 80:80(本机:容器) -v(映射Nginx容器的运行目录本机) /root/nginx/log:/var/log/nginx(本机目录:容器目录) -v /root/nginx/conf/nginx.conf:/etc/nginx/nginx.conf(本机目录:容器内nginx配置所在目录) -v /root/nginx/conf.d:/etc/nginx/conf.d -v /root/nginx/html:/usr/share/nginx/html f00ab1b3ac6d(nginx镜像ID) |
本地电脑启动
docker run --name nginx -d -p 8666:80 -v /Users/poetry/Downloads/docker/nginx/log:/var/log/nginx -v /Users/poetry/Downloads/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v /Users/poetry/Downloads/docker/nginx/conf.d:/etc/nginx/conf.d -v /Users/poetry/Downloads/docker/nginx/html:/usr/share/nginx/html f00ab1b3ac6d |
把docker容器中的Nginx服务配置映射本地方便管理
访问docker暴露的8666端口即可
当我们修改了html中的文件,无需重启容器即可看到效果
构建egg镜像,进入到egg目录
# 构建egg镜像,版本v1.0 |
Dockerfile文件如下
# 使用node镜像 |
docker run -d(后台启动) -p 7001:7001(本机:容器) --name server(容器名称) af9360186a24(镜像ID) |
version: "3.0" |
修改egg服务代码
常用命令
docker-compose -h
查看命令
docker-compose up
启动服务,控制台可见日志docker-compose up -d
后台启动服务docker-compose build --no-cache
重新构建镜像不使用缓存(最后docker-compose up -d
启动)docker-compose down
docker-compose pull
docker-compose restart
后台启动服务 docker-compose up -d
查看应用状态 docker-compose ps
停止服务 docker-compose down
把前端打包的文件放到Nginx目录下访问
yum install yum-utils device-mapper-persistent-data lvm2 -y |
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
yum install docker-ce docker-ce-cli containerd.io -y |
systemctl start docker |
vi /etc/docker/daemon.json |
{ |
后续拉取镜像直接从 https://hub.docker.com 网站拉取速度更快
重启docker
systemctl restart docker |
docker pull daocloud.io/library/mysql:8.0.20 |
运行mysql镜像
docker run -d -p 3307:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456(设置登录密码) be0dbf01a0f3(镜像ID) |
进入mysql容器内部
至此mysql镜像搭建成功,下面我们使用
docker-compose
来管理docker容器,不在单独一个个安装MySQL、redis、nginx
# 使用国内源安装 |
设置docker-compose执行权限
chmod +x /usr/local/bin/docker-compose |
创建软链
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose |
测试是否安装成功:
$ docker-compose --version |
登录服务器后台放行对应端口
修改Nginx配置
修改config/config.prod.js
docker-compose.yml
version: "3.0" |
egg Dockerfile
# 使用node镜像 |
./deploy/redis/conf/redis.conf
需要设置的地方
#指定日志级别,notice适用于生产环境 |
全部配置
# Redis configuration file example. |
scp -rp egg.zip root@43.138.12.18:/home |
解压
unzip -u -d server egg.zip |
# cd egg |
vscode本地连接线上数据库测试
redis服务连接测试
无需密码登录
redis-cli -h 43.23.121.12 -p 6380 |
设置密码后的登录方式
redis-cli -h 43.31.121.12 -p 6380 |
缓存服务测试
测试egg接口
访问前端项目测试接口
云托管流水线部署更方便
这里我们上面部署使用的自建服务器上docker搭建的redis服务作为演示
这里我们上面部署使用的自建服务器上docker搭建的mysql服务作为演示
然后上传代码到github,通过云托管流水线构建
点击发布后,云托管会执行Dockerfile构建流水线,到日志可以查看构建进度
微信云托管部署成功后,可以在实例列表,点击进入容器看到代码,这里里面的内容不能修改,在容器启动后会覆盖
postman测试
测试redis服务
至此部署到微信云托管完成,后续修改代码提交到github会自动触发云托管部署
需要注意,云函数的代码包不能超过500M
由于云函数在执行时,只有
/tmp
可读写的,所以我们需要将 egg.js 框架运行尝试的日志写到该目录下,为此需要修改config/config.default.js
中的配置如下:
const config = (exports = { |
// 安装Serverless 框架 |
在 egg 项目根目录,新建 Serverless 配置文件 serverless.yml
app: appDemo |
sls deploy
(意: sls
是 serverless
命令的简写。)通过以下命令移除部署的 Egg 服务资源,包括云函数和 API 网关。
$ sls remove |
当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以在项目根目录
serverless-egg
中创建.env
文件:
# .env |
如果已有腾讯云账号,可以在 API 密钥管理 中获取 SecretId 和SecretKey.
通常初始化的 egg 项目,会自动创建
app/public
目录。但是在打包压缩时,如果该目录为空,则部署后,该目录不会存在。所以 egg 项目启动时会自动创建,但是云函数是没有操作权限的,建议可以在app/public
目录下创建一个空文件.gitkeep
,来解决此问题。
如果除了代码部署外,您还需要更多能力或资源创建,如自动创建层托管依赖、一键实现静态资源分离、支持代码仓库直接拉取等,可以通过应用控制台,完成 Web 应用的创建工作
mkdir egg-example && cd egg-example |
接下来执行以下步骤,对本地已创建完成的项目进行简单修改,使其可以通过 Web Function 快速部署,对于 Egg 框架,具体改造说明如下:
0.0.0.0:9000
。/tmp
目录可读写。scf_bootstrap
启动文件。1. (可选)配置 scf_bootstrap 启动文件
您也可以在控制台完成该模块配置。
在项目根目录下新建
scf_bootstrap
启动文件,在该文件添加如下内容(用于配置环境变量和启动服务,此处仅为示例,具体操作请以您实际业务场景来调整):
|
新建完成后,还需执行以下命令修改文件可执行权限,默认需要 777 或 755 权限才可正常启动。示例如下:
chmod 777 scf_bootstrap |
2. 控制台上传
您可以在控制台完成启动文件 scf_bootstrap 内容配置,配置完成后,控制台将为您自动生成 启动文件,和项目代码一起打包部署
启动文件以项目内文件为准,如果您的项目里已经包含 scf_bootstrap
文件,将不会覆盖该内容。
查看函数,修改代码查看日志等
高级配置管理
您可在“高级配置”里进行更多应用管理操作,如创建层、绑定自定义域名、配置环境变量等。
Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。
本文基于nest8演示
$ npm i -g @nestjs/cli |
nest new project-name
创建一个项目
$ tree |
以下是这些核心文件的简要概述:
app.controller.ts
带有单个路由的基本控制器示例。app.module.ts
应用程序的根模块。main.ts
应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。
main.ts
包含一个异步函数,它负责引导我们的应用程序:
import { NestFactory } from '@nestjs/core'; |
NestFactory
暴露了一些静态方法用于创建应用实例create()
方法返回一个实现 INestApplication
接口的对象, 并提供一组可用的方法
nest
有两个支持开箱即用的 HTTP 平台:express
和fastify
。 您可以选择最适合您需求的产品
platform-express
Express 是一个众所周知的 node.js 简约 Web 框架。 这是一个经过实战考验,适用于生产的库,拥有大量社区资源。 默认情况下使用 @nestjs/platform-express
包。 许多用户都可以使用 Express
,并且无需采取任何操作即可启用它。platform-fastify
Fastify
是一个高性能,低开销的框架,专注于提供最高的效率和速度。 Nest中的控制器层负责处理传入的请求, 并返回对客户端的响应。
控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作
通过NestCLi创建控制器:
nest -h
可以看到nest
支持的命令
常用命令:
nest g co user module
nest g s user module
nest g mo user module
nest g controller posts |
表示创建posts的控制器,这个时候会在src目录下面生成一个posts的文件夹,这个里面就是posts的控制器,代码如下
import { Controller } from '@nestjs/common'; |
创建好控制器后,nestjs
会自动的在 app.module.ts
中引入PostsController
,代码如下
// src/app.module.ts |
Nestjs提供了其他HTTP请求方法的装饰器
@Get()
@Post()
@Put()
、@Delete()
、@Patch()
、@Options()
、@Head()
和@All()
在Nestjs中获取Get
传值或者Post提
交的数据的话我们可以使用Nestjs中的装饰器来获取。
@Request() req |
示例
@Controller('posts') |
注意
关于nest的return
: 当请求处理程序返回 JavaScript 对象或数组时,它将自动序列化为 JSON。但是,当它返回一个字符串时,Nest 将只发送一个字符串而不是序列化它Nestjs中的服务可以是
service
也可以是provider
。他们都可以通过 constructor 注入依赖关系
。服务本质上就是通过@Injectable()
装饰器注解的类。在Nestjs中服务相当于MVC
的Model
创建服务
nest g service posts |
创建好服务后就可以在服务中定义对应的方法
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; |
模块是具有
@Module()
装饰器的类。@Module()
装饰器提供了元数据,Nest 用它来组织应用程序结构
每个 Nest 应用程序至少有一个模块,即根模块。根模块是 Nest 开始安排应用程序树的地方。事实上,根模块可能是应用程序中唯一的模块,特别是当应用程序很小时,但是对于大型程序来说这是没有意义的。在大多数情况下,您将拥有多个模块,每个模块都有一组紧密相关的功能。
@module() 装饰器接受一个描述模块属性的对象:
providers
由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享controllers
必须创建的一组控制器imports
导入模块的列表,这些模块导出了此模块中所需提供者exports
由本模块提供并应在其他模块中可用的提供者的子集// 创建模块 posts |
Nestjs中的共享模块
每个模块都是一个共享模块。一旦创建就能被任意模块重复使用。假设我们将在几个模块之间共享 PostsService 实例。 我们需要把 PostsService 放到 exports 数组中:
// posts.modules.ts |
可以使用
nest g res posts
一键创建以上需要的各个模块
NestJS中配置静态资源目录完整代码
npm i @nestjs/platform-express -S |
import { NestExpressApplication } from '@nestjs/platform-express'; |
npm i ejs --save |
配置模板引擎
// main.ts |
项目根目录新建views
目录然后新建根目录 -> views -> default -> index.ejs
|
渲染页面
Nestjs中 Render
装饰器可以渲染模板,使用路由匹配渲染引擎
mport { Controller, Get, Render } from '@nestjs/common'; |
cookie和session的使用依赖于当前使用的平台,如:express和fastify
两种的使用方式不同,这里主要记录基于express平台的用法
cookie可以用来存储用户信息,存储购物车等信息,在实际项目中用的非常多
npm instlal cookie-parser --save |
引入注册
// main.ts |
接口中设置cookie 使用response
请求该接口,响应一个cookie
@Get() |
cookie相关配置参数
domain
String 指定域名下有效expires
Date 过期时间(秒),设置在某个时间点后会在该cookoe
后失效httpOnly
Boolean 默认为false
如果为true
表示不允许客户端(通过js
来获取cookie
)maxAge
String 最大失效时间(毫秒),设置在多少时间后失效path
String 表示cookie
影响到的路径,如:path=/
如果路径不能匹配的时候,浏览器则不发送这个cookie
secure
Boolean 当 secure
值为 true
时,cookie
在 HTTP 中是无效,在 HTTPS
中才有效signed
Boolean 表示是否签名cookie
,如果设置为true
的时候表示对这个cookie
签名了,这样就需要用res.signedCookies()
获取值cookie
不是使用res.cookies()
了获取cookie
@Get() |
Cookie加密
// 配置中间件的时候需要传参 |
session
是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session
保存在服务器上安装 express-session
npm i express-session --save |
// main.ts |
session相关配置参数
secret
String 生成session
签名的密钥name
String 客户端的cookie
的名称,默认为connect.sid
, 可自己设置resave
Boolean 强制保存 session
即使它并没有变化, 默认为true
, 建议设置成false
saveUninitalized
Boolean 强制将未初始化的 session
存储。当新建了一个 session
且未设定属性或值时,它就处于 未初始化状态。在设定一个 cookie
前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。默认:true
, 建议手动添加cookie
Object 设置返回到前端cookie
属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }
。rolling
Boolean 在每次请求时强行设置 cookie
,这将重置 cookie
过期时间, 默认为false
接口中设置session
@Get() |
获取session
@Get('/session') |
跨域,路径前缀,网络安全
yarn add helmet csurf |
// main.ts |
限速:限制客户端在一定时间内的请求次数
yarn add @nestjs/throttler |
在需要使用的模块引入使用,这里是全局使用,在
app.module.ts
中引入。这里设置的是:1分钟内只能请求10次,超过则报status为429的错误
app.module.ts |
执行顺序(时机)
从客户端发送一个post请求,路径为:/user/login
,请求参数为:{userinfo: ‘xx’,password: ‘xx’}
,到服务器接收请求内容,触发绑定的函数并且执行相关逻辑完毕,然后返回内容给客户端的整个过程大体上要经过如下几个步骤:
全局使用: 管道 - 守卫 - 拦截器 - 过滤器 - 中间件。统一在main.ts文件中使用,全局生效
import { NestFactory } from '@nestjs/core'; |
常用内置管道,从@nestjs/common
导出
ParseIntPipe
:将字符串数字转数字ValidationPipe
:验证管道局部使用管道
匹配整个路径,使用UsePipes
只匹配某个接口,使用UsePipes
在获取参数时匹配,一般使用内置管道
import { |
自定义管道
使用快捷命令生成:nest g pi myPipe common/pipes
import { |
自定义守卫
使用快捷命令生成:nest g gu myGuard common/guards
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; |
局部使用守卫
import { |
自定义守卫中使用到了自定义装饰器
nest g d role common/decorator |
//这是快捷生成的代码 |
使用快捷命令生成:nest g in auth common/intercepters
import { |
局部使用过滤器
import { |
自定义过滤器
使用快捷命令生成:nest g f myFilter common/filters
import { |
局部使用中间件
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common'; |
自定义中间件
nest g mi logger common/middleware |
import { Injectable, NestMiddleware } from '@nestjs/common'; |
函数式中间件
// 函数式中间件-应用于全局 |
从客户端发送一个post请求,路径为:
/user/login
,请求参数为:{userinfo: ‘xx’,password: ‘xx’}
,到服务器接收请求内容,触发绑定的函数并且执行相关逻辑完毕,然后返回内容给客户端的整个过程大体上要经过如下几个步骤:`
项目需要包支持:
npm install --save rxjs xml2js class-validator class-transformer |
rxjs
针对JavaScript的反应式扩展,支持更多的转换运算
xml2js
转换xml内容变成json格式
class-validator
、class-transformer
管道验证包和转换器建立user模块:模块内容结构:
nest g res user
user.controller.ts文件
import { |
user.module.ts文件
import { Module } from '@nestjs/common'; |
user.service.ts文件
import { Injectable } from '@nestjs/common'; |
user.login.dto.ts文件
// user / dto / user.login.dto.ts |
app.module.ts文件
import { Module } from '@nestjs/common'; |
新建common文件夹里面分别建立对应的文件夹以及文件:
中间件(middleware) — xml.middleware.ts
守卫(guard) — auth.guard.ts
管道(pipe) — validation.pipe.ts
异常过滤器(filters) — http-exception.filter.ts
拦截器(interceptor) — response.interceptor.ts
// main.ts |
本例中:使用中间件让express支持xml请求并且将xml内容转换为json数组
// common/middleware/xml.middleware.ts |
注册方式
全局注册:在main.ts
中导入需要的中间件模块如:XMLMiddleware然后使用 app.use(new XMLMiddleware().use)
即可
模块注册:在对应的模块中注册如:user.module.ts
同一路由注册多个中间件的执行顺序为,先是全局中间件执行,然后是模块中间件执行,模块中的中间件顺序按照
.apply
中注册的顺序执行
守卫控制一些权限内容,如:一些接口需要带上token标记,才能够调用,守卫则是对这个标记进行验证操作的。
本例中代码如下:
// common/guard/auth.guard.ts |
注册方式
main.ts
中导入需要的守卫模块如:AuthGuard
。然后使用 app.useGlobalGuards(new AuthGuard())
即可controller
控制器中导入AuthGuard
。然后从@nestjs/common
中导UseGuards
装饰器。最后直接放置在对应的@Controller()
或者@Post/@Get…
等装饰器之下即可同一路由注册多个守卫的执行顺序为,先是全局守卫执行,然后是模块中守卫执行
想到自定义返回内容如
{ |
这个时候就可以使用拦截器来做一下处理了。
拦截器作用:
拦截器的执行顺序分为两个部分:
// common/interceptor/response.interceptor.ts |
中间多了个全局管道以及自定义逻辑,即只有路由绑定的函数有正确的返回值之后才会有
next.handle()
之后的内容
注册方式
main.ts
中导入需要的模块如:ResponseInterceptor
。然后使用 app.useGlobalInterceptors(new ResponseInterceptor())
即可controller
控制器中导入ResponseInterceptor
。然后从@nestjs/common
中导入UseInterceptors
装饰器。最后直接放置在对应的@Controller()
或者@Post/@Get
…等装饰器之下即可同一路由注册多个拦截器时候,优先执行模块中绑定的拦截器,然后其拦截器转换的内容将作为全局拦截器的内容,即包裹两次返回内容如:
{ // 全局拦截器效果 |
管道是请求过程中的第四个内容,主要用于对请求参数的验证和转换操作。
项目中使用class-validator
class-transformer
进行配合验证相关的输入操作内容
认识官方的三个内置管道
ValidationPipe
:基于class-validator
和class-transformer
这两个npm包编写的一个常规的验证管道,可以从class-validator
导入配置规则,然后直接使用验证(当前不需要了解ValidationPipe
的原理,只需要知道从class-validator
引规则,设定到对应字段,然后使用ValidationPipe
即可)ParseIntPipe
:转换传入的参数为数字如:传递过来的是/test?id=‘123’”这里会将字符串‘123’转换成数字123
如:传递过来的是/test?id=‘xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx’”这里会验证格式是否正确,不正确则抛出错误,否则调用findOne方法
本例中管道使用如下:
// common/pipe/validation.pipe.ts |
注册方式
main.ts
中导入需要的模块如:ValidationPipe
;然后使用 app.useGlobalPipes(new ValidationPipe())
即可controller
控制器中导入ValidationPipe
;然后从@nestjs/common
中导入UsePipes
装饰器;最后直接放置在对应的@Controller()
或者@Post/@Get…
等装饰器之下即可,管道还允许注册在相关的参数上如:@Body/@Query…
等注意:同一路由注册多个管道的时候,优先执行全局管道,然后再执行模块管道:
内置异常类
系统提供了不少内置的系统异常类,需要的时候直接使用throw new XXX(描述,状态)这样的方式即可抛出对应的异常,一旦抛出异常,当前请求将会终止。
注意每个异常抛出的状态码有所不同。如:
BadRequestException — 400 |
本例中使用的是自定义的异常类,代码如下:
// common/filters/http-exception.filter.ts |
注册方式
main.ts
中导入需要的模块如:HttpExceptionFilter
然后使用 app.useGlobalFilters(new HttpExceptionFilter())
即可controller
控制器中导入HttpExceptionFilter
然后从@nestjs/common
中导入UseFilters
装饰器;最后直接放置在对应的@Controller()
或者@Post/@Get…
等装饰器之下即可注意: 同一路由注册多个管道的时候,只会执行一个异常过滤器,优先执行模块中绑定的异常过滤器,如果模块中无绑定异常过滤则执行全局异常过滤器
如何 限制 和 验证 前端传递过来的数据?
常用:dto
(data transfer object数据传输对象) + class-validator
,自定义提示内容,还能集成swagger
class-validator的验证项装饰器
https://github.com/typestack/class-validator#usage
@IsOptional() //可选的 |
@IsEmail({}, { message: ‘邮箱格式错误’ }) //邮箱 |
@ValidateIf(o => o.username === ‘admin’) //条件判断,条件满足才验证,如:这里是传入的username是admin才验证 |
yarn add class-validator class-transformer |
全局使用内置管道ValidationPipe
,不然会报错,无法起作用
import { NestFactory } from '@nestjs/core'; |
编写dto
,使用class-validator
的校验项验证
创建DTO:只需要用户名,密码即可,两种都不能为空
可以使用
nest g res user
一键创建带有dto的接口模块
import { IsNotEmpty, MinLength, MaxLength } from 'class-validator'; |
修改DTO:用户名,密码,手机号码,邮箱,性别,状态,都是可选的
import { |
controller
和service
一起使用
// user.controller.ts |
// user.service.ts |
yarn add nestjs-config |
app.module.ts |
配置数据库
src -> config -> database |
yarn add cross-env |
cross-env的作用是兼容window系统和mac系统来设置环境变量
在package.json中配置
"scripts": { |
dotenv的使用
yarn add dotenv |
根目录创建 env.parse.ts
import * as fs from 'fs'; |
导入环境
// main.ts |
.env.local
PORT=9000 |
.env.prod
PORT=9000 |
读取环境变量 process.env.MYSQL_HOST
形式
yarn add @nestjs/platform-express compressing |
配置文件的目录地址,以及文件的名字格式
|
// app.module.ts |
// upload.controller.ts |
// upload.service.ts |
// upload.module.ts |
nest如何实现图片随机验证码?
这里使用的是svg-captcha这个库,你也可以使用其他的库
yarn add svg-captcha |
封装,以便多次调用
src -> utils -> tools.service.ts |
在使用的module中引入
import { Module } from '@nestjs/common'; |
使用
import { Controller, Get, Post,Body } from '@nestjs/common'; |
前端简单代码
|
// 邮件服务配置 |
邮件服务使用
// email.services.ts |
方式与逻辑
yarn add @nestjs/jwt @nestjs/passport passport-jwt passport-local passport |
jwt策略 jwt.strategy.ts
// src/modules/auth/jwt.strategy.ts |
本地策略 local.strategy.ts
// src/modules/auth/local.strategy.ts |
constants.ts
// src/modules/auth/constants.ts |
使用守卫 auth.controller.ts
// src/modules/auth/auth.controller.ts |
在module引入jwt配置和数据库查询的实体 auth.module.ts
// src/modules/auth/auth.module.ts |
auth.service.ts
// src/modules/auth/auth.service.ts |
最后在app.module.ts中导入即可测试
// app.modules.ts |
使用postman测试
密码加密
一般开发中,是不会有人直接将密码明文直接放到数据库当中的。因为这种做法是非常不安全的,需要对密码进行加密处理。
好处:
- 预防内部网站运营人员知道用户的密码
- 预防外部的攻击,尽可能保护用户的隐私
加密方式
md5
:每次生成的值是一样的,一些网站可以破解,因为每次存储的都是一样的值bcryptjs
:每次生成的值是不一样的yarn add md5 |
加密
import * as md5 from 'md5'; |
给密码加点”盐”:目的是混淆密码,其实还是得到固定的值
const passwrod = '123456'; |
验证密码:先加密,再验证
const passwrod = '123456'; |
使用bcryptjs
yarn add bcryptjs |
同一密码,每次生成不一样的值
import { compareSync, hashSync } from 'bcryptjs'; |
验证密码:使用不同的值 匹配 密码123456,都能通过
const password = '123456'; |
推荐使用bcryptjs
,算法要比md5
高级
RBAC是基于角色的权限访问控制(Role-Based Access Control)一种数据库设计思想,根据设计数据库设计方案,完成项目的权限管理
在RBAC中,有3个基础组成部分,分别是:
用户
、角色
和权限
,权限与角色相关联,用户通过成为适当角色而得到这些角色的权限
权限:具备操作某个事务的能力
如:一般的管理系统中:
销售人员:仅仅可以查看商品信息
运营人员:可以查看,修改商品信息
管理人员:可以查看,修改,删除,以及修改员工权限等等
管理人员只要为每个员工账号分配对应的角色,登陆操作时就只能执行对应的权限或看到对应的页面
权限类型
数据库设计
数据库设计:可简单,可复杂,几个人使用的系统和几千人使用的系统是不一样的
小型项目:用户表,权限表
中型项目:用户表,角色表,权限表
大型项目:用户表,用户分组表,角色表,权限表,菜单表…
没有角色的设计
只有用户表,菜单表,两者是多对多关系,有一个关联表
缺点:
用户表和角色表的关系设计:
如果你希望一个用户可以有多个角色,如:一个人即是销售总监,也是人事管理,就设计多对多关系
如果你希望一个用户只能有一个角色,就设计一对多,多对一关系
角色表和权限表的关系设计:
一个角色可以拥有多个权限,一个权限被多个角色使用,设计多对多关系
多对多关系设计
用户表与角色表是多对多关系,角色表与菜单表是多对多关系
更加复杂的设计
实现流程
代码实现
这里将实现一个用户,部门,角色,权限的例子:
用户通过成为部门的一员,则拥有部门普通角色的权限,还可以单独给用户设置角色,通过角色,获取权限。
权限模块包括,模块,菜单,操作,通过type区分类型,这里就不再拆分。
关系总览:
用户
import { |
角色
import { |
部门
import { |
权限
import { |
由于要实现很多接口,这里只说明一部分,其实都是数据库的操作,所有接口如下:
根据用户的id获取信息:id,用户名,部门名,角色,这些信息在做用户登陆时传递到token中。
这里设计的是:创建用户时,添加部门,就会成为部门的普通角色,也可单独设置角色,但不是每个用户都有单独的角色。
async getUserinfoByUid(uid: number) { |
结合possport + jwt 做用户登陆授权验证
在验证账户密码通过后,possport 返回用户,然后根据用户id获取用户信息,存储token,用于路由守卫,还可以使用redis存储,以作他用。
async login(user: any): Promise<any> { |
使用守卫,装饰器,结合token,验证访问权限
逻辑:
controller
使用自定义守卫装饰接口路径,在请求该接口路径时,全部进入守卫逻辑如下:findOne
接口使用了自定义装饰器装饰接口,意思是只能admin
来访问
import { |
装饰器
import { SetMetadata } from '@nestjs/common'; |
自定义守卫
返回
true
则有访问权限,返回false
则直接报403
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; |
简单测试
两个用户,分别对应不同的角色,分别请求user的findOne接口
用户1:销售部员工和admin
用户2:人事部员工
用户1:销售部员工和admin |
前端的权限访问则是通过权限表url和type来处理
nest如何开启定时任务?
定时任务场景
每天定时更新,定时发送邮件
没有controller,因为定时任务是自动完成的
yarn add @nestjs/schedule |
// src/tasks/task.module.ts |
在这里编写你的定时任务
// src/tasks/task.service.ts |
自定义定时时间
* * * * * * 分别对应的意思: |
挂载-使用
// app.module.ts |
dto
yarn add @nestjs/swagger swagger-ui-express -D |
// main.ts |
swagger装饰器
@ApiTags('user') // 设置模块接口的分类,不设置默认分配到default |
在controller
引入@nestjs/swagger
, 并配置@ApiBody()
和 @ApiParam()
不写也是可以的
user.controller.ts |
编写dto,引入@nestjs/swagger
创建
import { IsNotEmpty, MinLength, MaxLength } from 'class-validator'; |
更新
import { |
打开:localhost:3000/api-docs,开始测试接口
mac中,直接使用brew install mongodb-community
安装MongoDB,然后启动服务brew services start mongodb-community
查看服务已经启动ps aux | grep mongo
Nestjs中操作Mongodb数据库可以使用Nodejs封装的DB库,也可以使用Mongoose。
// https://docs.nestjs.com/techniques/mongodb |
在app.module.ts中配置数据库连接
// app.module.ts |
// mongodb配置 |
配置Schema
// article.schema |
在控制器对应的Module中配置Model
// mongodb.module.ts |
在服务里面使用@InjectModel 获取数据库Model实现操作数据库
// mongodb.service.ts |
浏览器测试 http://localhost:9000/api/mongodb/list
mac中,直接使用brew install mysql
安装mysql,然后启动服务brew services start mysql
查看服务已经启动ps aux | grep mysql
Nest 操作Mysql官方文档:https://docs.nestjs.com/techniques/database
npm install --save @nestjs/typeorm typeorm mysql |
配置数据库连接地址
// src/config/typeorm.ts |
// app.module.ts中配置 |
配置实体entity
// photo.entity.ts |
参数校验
Nest 与 class-validator 配合得很好。这个优秀的库允许您使用基于装饰器的验证。装饰器的功能非常强大,尤其是与 Nest 的 Pipe 功能相结合使用时,因为我们可以通过访问 metatype
信息做很多事情,在开始之前需要安装一些依赖。
npm i --save class-validator class-transformer |
// posts.dto.ts |
在控制器对应的Module中配置Model
import { Module } from '@nestjs/common'; |
在服务里面使用@InjectRepository获取数据库Model实现操作数据库
// posts.services.ts |
操作数据库时,如何做异常处异常? 比如id不存在,用户名已经存在?如何统一处理请求失败和请求成功?
处理方式:
controller
处理// user.controller.ts |
// user.service.ts |
可以将
HttpException
再简单封装一下,或者使用继承,这样代码更简洁一些
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; |
简洁代码
// user.service.ts |
全局使用filter过滤器
// src/common/filters/http-execption.ts |
全局使用interceptor拦截器
// src/common/inteptors/transform.interceptor.ts |
// main.ts |
失败
成功
typeorm的数据库实体如何编写?
数据库实体的监听装饰器如何使用?
简单例子:下面讲解
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn} from "typeorm"; |
装饰器说明
Entity
实体声明,程序运行时,自动创建的数据库表,@Entity({ name: 'users' })
, name
则是给该表命名,否则自动命名PrimaryColumn
设置主键,没有自增PrimaryGeneratedColumn
设置主键和自增,一般是id
Column
设置数据库列字段,在下面说明CreateDateColumn
创建时间,自动填写UpdateDateColumn
更新时间,自动填写DeleteDateColumn
删除时间,自动填写列字段参数
// 写法: |
完整例子
import { |
baseEntity
:将id,创建时间,更新时间,删除时间抽离成BaseEntity
import { |
users
表继承自baseEntity
,就不需要写创建时间,修改时间,自增ID
等重复字段了。其他的表也可以继承自baseEntity
,减少重复代码
import { Column,Entity } from 'typeorm'; |
@AfterLoad
@BeforeInsert
@AfterInsert
@BeforeUpdate
@AfterUpdate
@BeforeRemove
AfterLoad
例子:其他的装饰器是一样的用法
import { |
访问数据库的方式有哪些?
typeorm增删改查操作的方式有哪些?
第一种:Connection
import { Injectable } from '@nestjs/common'; |
第二种:Repository
,需要@nestjs/typeorm
的InjectRepository
来注入实体
import { Injectable } from '@nestjs/common'; |
第三种:getConnection()
:语法糖,是Connection
类型
import { Injectable } from '@nestjs/common'; |
第四种:getRepository
:语法糖
import { Injectable } from '@nestjs/common'; |
第五种:getManager
import { Injectable } from '@nestjs/common'; |
简单总结
使用的方式太多,建议使用:2,4
,比较方便
Connection核心类:
connection
等于getConnection
connection.manager
等于getManager
, 等于getConnection.manager
connection.getRepository
等于getRepository
, 等于getManager.getRepository
connection.createQueryBuilder
使用QueryBuilder
connection.createQueryRunner
开启事务EntityManager
和 Repository
都封装了操作数据的方法,注意:两者的使用方式是不一样的,(实在不明白搞这么多方法做什么,学得头大)getManager
是EntityManager
的类型,getRepository
是Repository
的类型createQueryBuilder
,但使用的方式略有不同typeorm
封装好的方法,增删改 + 简单查询QueryBuilder
查询生成器,适用于关系查询,多表查询,复杂查询sql
语句,只是封装了几种方式而已,方便人们使用。第一种:sql语句
export class UserService { |
第二种:typeorm封装好的api方法
这里使用第二种访问数据库的方式
export class UserService { |
api方法
增 |
find更多参数
this.userRepository.find({ |
find进阶选项
TypeORM 提供了许多内置运算符,可用于创建更复杂的查询
import { Not, Between, In } from "typeorm"; |
第三种:QueryBuilder
查询生成器
使用链式操作
QueryBuilder增,删,改
// 增加 |
查询
简单例子
export class UserService { |
QueryBuilder查询生成器说明
查询单表
访问数据库的方式不同: |
获取结果
.getSql(); 获取实际执行的sql语句,用于开发时检查问题 |
查询部分字段
.select(["user.id", "user.name"]) |
where
条件
.where("user.name = :name", { name: "joy" }) |
having
筛选
.having("user.firstName = :firstName", { firstName: "Timber" }) |
orderBy
排序
.orderBy("user.name", "DESC") |
group
分组
.groupBy("user.name") |
关系查询(多表)
1参:你要加载的关系,2参:可选,你为此表分配的别名,3参:可选,查询条件 |
typeorm
使用事务的方式有哪些?如何使用?
事务
- 在操作多个表时,或者多个操作时,如果有一个操作失败,所有的操作都失败,要么全部成功,要么全部失
- 解决问题:在多表操作时,因为各种异常导致一个成功,一个失败的数据错误。
例子:银行转账
如果用户1向用户2转了100元,但因为各种原因,用户2没有收到,如果没有事务处理,用户1扣除的100元就凭空消失了
如果有事务处理,只有用户2收到100元,用户1才会扣除100元,如果没有收到,则不会扣除。
应用场景
多表的增,删,改操作
nest-typrorm事务的使用方式
controller
中编写,传递给service
使用getManager
或 getConnection
,在service
中编写与使用connection
或 getConnection
,开启queryRunner
,在service
中编写与使用方式一:使用装饰器
controller
import { |
service
avatar
表,同时关联保存用户的id
import { Injectable } from '@nestjs/common'; |
方式二:使用getManager 或 getConnection
service
import { Injectable } from '@nestjs/common'; |
方式三:使用 connection 或 getConnection
service
import { Injectable } from '@nestjs/common'; |
实体如何设计一对一关系?如何增删改查?
一对一关系
- 定义:一对一是一种 A 只包含一个 B ,而 B 只包含一个 A 的关系
- 其实就是要设计两个表:一张是主表,一张是副表,查找主表时,关联查找副表
- 有外键的表称之为副表,不带外键的表称之为主表
- 如:一个账户对应一个用户信息,主表是账户,副表是用户信息
- 如:一个用户对应一张用户头像图片,主表是用户信息,副表是头像地址
一对一实体设计
主表:
- 使用
@OneToOne()
来建立关系- 第一个参数:
() => AvatarEntity
, 和谁建立关系? 和AvatarEntity
建立关系- 第二个参数:
(avatar) => avatar.user)
,和哪个字段联立关系?avatar
就是AvatarEntity
的别名,可随便写,和AvatarEntity
的userinfo
字段建立关系- 第三个参数:
RelationOptions
关系选项
import { |
副表
参数:同主表一样
主要:根据@JoinColumn({ name: ‘user_id’ })
来分辨副表,name
是设置数据库的外键名字,如果不设置是userId
import { |
一对一增删改查
- 注意:只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
- 在这里:创建,修改,删除都开启了事务
- 注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
// user.controller.ts |
// user.service.ts |
实体如何设计一对多与多对一关系,如何关联查询
一对多关系,多对一关系
定义:一对多是一种一个 A 包含多个 B ,而多个B只属于一个 A 的关系
其实就是要设计两个表:一张是主表(一对多),一张是副表(多对一),查找主表时,关联查找副表
有外键的表称之为副表,不带外键的表称之为主表
如:一个用户拥有多个宠物,多个宠物只属于一个用户的(每个宠物只能有一个主人)
如:一个用户拥有多张照片,多张照片只属于一个用户的
如:一个角色拥有多个用户,多个用户只属于一个角色的(每个用户只能有一个角色)
一对多和多对一实体设计
一对多
使用
@OneToMany()
来建立一对多关系
第一个参数:() => PhotoEntity
, 和谁建立关系? 和PhotoEntity
建立关系
第二个参数:(user) => user.photo
,和哪个字段联立关系?user
就是PhotoEntity
的别名,可随便写,和PhotoEntity
的userinfo
字段建立关系
第三个参数:RelationOptions
关系选项
import { |
多对一
使用
@ManyToOne()
来建立多对一关系,参数如同上
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; |
一对多和多对一增删改查
只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
比较复杂的是更新操作
user.controller.ts
import { |
user.service.ts
令人头大的地方:建立关系和查找使用实体,删除使用实体的id,感觉设计得不是很合理,违背人的常识
import { Injectable } from '@nestjs/common'; |
实体如何设计多对多关系?如何增删改查?
多对多关系
定义:多对多是一种 A 包含多个 B,而 B 包含多个 A 的关系
如:一个粉丝可以关注多个主播,一个主播可以有多个粉丝
如:一篇文章属于多个分类,一个分类下有多篇文章
比如这篇文章,可以放在nest目录,也可以放在typeorm目录或者mysql目录
实现方式
第一种:建立两张表,使用装饰器
@ManyToMany
建立关系,typeorm
会自动生成三张表
第二种:手动建立3张表
这里使用第一种
这里将设计一个用户(粉丝) 与 明星的 多对多关系
用户(粉丝)可以主动关注明星,让users
变为主表,加入@JoinTable()
使用
@ManyToMany()
来建立多对多关系
第一个参数:() => StarEntity
, 和谁建立关系? 和StarEntity
建立关系
第二个参数:(star) => star.photo
,和哪个字段联立关系?star
就是StarEntity
的别名,可随便写,和PhotoEntity
的followers
字段建立关系
用户(粉丝)表:follows关注/跟随
import { |
明星表:followers跟随者
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; |
注意:
程序运行后,将会默认在数据库中生成三张表,users,star,users_follows_star,users_follows_star是中间表,用于记录users和star之间的多对多关系,它是自动生成的。
为了测试方便,你可以在users表和star表创建一些数据:这些属于单表操作
只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
user.controller.ts
import { |
user.service.ts
import { Injectable } from '@nestjs/common'; |
Redis 字符串数据类型的相关命令用于管理 redis 字符串值
keys *
set key value
set key value EX 30
表示30秒后过期get key
del key
flushall
type key
expire key 20
表示指定的key5
秒后过期Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
rpush key value
lpush key value
rpop key
lpop key
lrange key
del key
flushall
type key
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。它和列表的最主要区别就是没法增加重复值
sadd key value
srem key value
smembers key
del key
flushall
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
hmset zhangsan name "张三" age 20 sex “男”
hset zhangsan name "张三"
hgetall key
del key
flushall
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
// 发布 |
Nestjs中使用redis
Nestjs Redis 官方文档:https://github.com/kyknow/nestjs-redis
npm install nestjs-redis --save |
如果是nest8需要注意该问题:https://github.com/skunight/nestjs-redis/issues/82
// app.modules.ts |
// src/config/redis.ts 配置 |
创建一个cache.service.ts 服务 封装操作redis的方法
// src/common/cache.service.ts |
使用redis服务
redis-test.controller
import { Body, Controller, Get, Post, Query } from '@nestjs/common'; |
redis-test.module.ts
import { Module } from '@nestjs/common'; |
redis-test.service.ts
import { Injectable } from '@nestjs/common'; |
在要使用的controller或service中使用redis
这里以实现token
存储在redis
为例子,实现单点登陆
需要在passport
的login
中,存储token
,如果不会passport
验证
单点登陆原理
一个账户在第一个地方登陆,登陆时,JWT生成token,保存token到redis,同时返回token给前端保存到本地
同一账户在第二个地方登陆,登陆时,JWT生成新的token,保存新的token到redis。(token已经改变)
此时,第一个地方登陆的账户在请求时,使用的本地token就会和redis里面的新token不一致(注意:都是有效的token)
import { Injectable } from '@nestjs/common'; |
验证token
import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt'; |
A模块的Service需要调用B模块的service中一个方法,则需要在A的Service导入B的service
场景如下:
// A.Service |
我的理解
// A.module.ts |
A
为啥”为什么还需要在A的module.ts中导入B模块呢”?
因为 BService
的作用域只在 BModule
里,所以你要在 AController
里直接用,就会报错拿不到实例。
再来说,”有什么办法可以让 BService
随处直接用么?”,参考如下手段:
B 的module 声明时,加上@Global
,如下:
import { Module, Global } from '@nestjs/common'; |
这样,你就不用在 AModule
的声明里引入 BModule
了。
关于『你的理解』部分,貌似你把@Inject
和 @Injectable
搞混了,建议再读一读这个部分的文档,多做些练习/尝试,自己感受下每个api的特点。
最后,官网文档里其实有介绍 ,看依赖注入
:https://docs.nestjs.com/modules#dependency-injection
Error: EMFILE, too many open files
也就是nodejs打开文件过多会导致错误,一次次排查,最后找到了一个有效的方法,总结记录一下当我尝试去操作大量文件的时候
for(var i=0; i<200000; i++) { |
以上导致
Error: EMFILE: too many open files
错误。我不必关闭文件,因为显然可以fs.readFile
对文件进行操作并为我关闭文件。我在Mac OS
上,我的ulimit
设置为8192
$ ulimit -a |
可以通过修改系统配置,但是不太推荐
$ echo kern.maxfiles=65536 | sudo tee -a /etc/sysctl.conf |
- 由于
node.js
的异步特性,因此会发生此错误。进程试图打开的文件超出允许的数量,因此会产生错误。- 可以通过创建打开
文件队列
来解决此问题,以使它们永远不会超过限制
,以下是一些可以为您完成此操作的库:
我们可以使用文件队列来限制每次打开的文件数量
Instantiate Filequeue with a maximum number of files to be opened at once (default is 200)
how to use
var FileQueue = require('filequeue'); |
filequeue支持以下方法
readFile |
使用filequeue
就可以正常运行了
var FileQueue = require('filequeue'); |
vercel
是一个站点托管平台,提供CDN加速,同类的平台有Netlify
和Github Pages
,相比之下,vercel
国内的访问速度更快,并且提供Production
环境和development
环境,对于项目开发非常的有用的,并且支持持续集成,一次push
或者一次PR
会自动化构建发布,发布在development
环境,都会生成不一样的链接可供预览。
但是vercel
只是针对个人用户免费,teams
是收费的
首先
vercel
零配置部署,第二访问速度比github-page
好很多,并且构建很快,还是免费使用的,对于部署个人前端项目路、接口服务非常方便
vercel
类似于github page
,但远比github page
强大,速度也快得多得多,而且将Github授权给vercel
后,可以达到最优雅的发布体验,只需将代码轻轻一推,项目就自动更新部署了。vercel
还支持部署serverless接口
。那代表着,其不仅仅可以部署静态网站,甚至可以部署动态网站,而这些功能,统统都是免费的vercel
还支持自动配置https
,不用自己去FreeSSL
申请证书,更是省去了一大堆证书的配置vercel
目前的部署模板有31种之多打开vercel
主页https://vercel.com/signup
使用GitHub
账号去关联vercel
,后续代码提交到vercel
可以自动触发部署
出现授权页面,点击Authorize Vercel
。
vercel是最好用的静态站点托管平台,借助vercel平台,我们可以把博客静态文件部署到vercel上,不在使用GitHub pages托管,vercel比GitHub pages快多了。
选择一个vercel提供的模板部署,当然你也可以把代码提交到GitHub上,再去vercel选择即可
创建一个GitHub项目,代码会自动在GitHub账号上创建
创建完成后,等待vercel构建
创建成功后自动跳到主页
点击visit即可访问创建好的服务 https://hexo-seven-blush.vercel.app ,vercel会分配给我们一个默认的域名,当然你也可以自定义修改。
我们可以查看打包日志,如果构建过程出现问题,在这里看即可
点击
view domain
绑定自定义域名
然后我们去域名解析处理解析CNAME
到cname.vercel-dns.com
最后解析完成,访问
hexo.poetries.com
自定义域名即可。到此我们把博客hexo项目部署到vercel上,后期当你在GitHub提交代码会自动触发vercel打包构建
你也可以从Github选择代码来创建项目
导入GitHub账号上的项目
部署vue、react等前端项目过程也类似,这里不再演示
用
vercel
部署Serverless Api
,不购买云服务器也能拥有自己的动态网站
简单演示部署api接口服务
配置vercel.json
,更多配置在vercel官网查 https://vercel.com/docs
{ |
创建接口,
vercel约定在api下创建接口路径
,最后我们可以通过域名/api/json
域名/api/query-all-users
来访问接口服务,我们这里创建了两个接口
// api/json.js |
我们这里使用腾讯云数据库,把一些数据存到云数据库上
// utils/db.js |
访问该链接获取secretId、secretKey填入即可 https://console.cloud.tencent.com/cam/capi
来到腾讯云控制台,创建环境获取环境ID
选择数据库-创建新的集合users
// api/query-all-users.js |
]]>这样我们就写好了两个接口服务,提交代码到GitHub上,然后在vercel上创建项目导入GitHub上的代码部署即可,最后部署的服务通过
https://域名/api/query-all-users?name=小月&size=100
访问即可
代码库应该有一个、且仅有一个主分支:master。所有提供给用户使用的正式版本,都在这个主分支上发布。
每次发布 打一个tag
,例如tag v1.0.0、tag v2.0.0
主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做develop。
这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在master分支上,对develop分支进行”合并”(merge)。
Git创建Develop分支的命令:
git checkout -b develop master
将Develop分支发布到Master分支的命令:
# 切换到Master分支
git checkout master
# 对Develop分支进行合并
git merge --no-ff develop
==这里稍微解释一下,上一条命令的
--no-ff
参数是什么意思。默认情况下,Git执行”快进式合并”(fast-farward merge
),会直接将Master分支指向Develop分支。==使用
--no-ff
参数后,会执行正常合并,在Master分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。
版本库的两条主要分支:master和develop。前者用于正式发布,后者用于日常开发。
其实,常设分支只需要这两条就够了,不需要其他了。
但是,除了常设分支以外,还有一些临时性分支,用于应对一些特定目的的版本开发。临时性分支主要有三种:
这三种分支都属于临时性需要,使用完以后,应该删除,使得代码库的常设分支始终只有Master和Develop。
==接下来,一个个来看这三种”临时性分支”。==
功能分支,它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。
功能分支的名字,可以采用feature-*的形式命名。
# 创建一个功能分支:
git checkout -b feature-开发一个新功能 develop
# 开发完成后,将功能分支合并到develop分支:
git checkout develop
git merge --no-ff feature-开发一个新功能
# 删除feature分支:
git branch -d feature-开发一个新功能
预发布分支,它是指发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试。
预发布分支是从Develop分支上面分出来的
,预发布结束以后,必须==合并进Develop和Master分支
==。它的命名,可以采用release-*的形式。
# 创建一个预发布分支:
git checkout -b release-1.2.0 develop
# 确认没有问题后,合并到master分支:
git checkout master
git merge --no-ff release-1.2.0
# 对合并生成的新节点,做一个标签
git tag -a 1.2
# 再合并到develop分支:
git checkout develop
git merge --no-ff release-1.2.0
# 最后,删除预发布分支:
git branch -d release-1.2.0
最后一种是修补bug分支。软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。
]]>修补bug分支是==
从Master分支上面分出来的
==。修补结束以后,再==合并进Master和Develop分支
==。它的命名,可以采用fixbug-*的形式。
创建一个修补bug分支:
git checkout -b fixbug-0.1 master
修补结束后,合并到master分支:
git checkout master
git merge --no-ff fixbug-0.1
git tag -a 0.1.1
再合并到develop分支:
git checkout develop
git merge --no-ff fixbug-0.1
最后,删除"修补bug分支":
git branch -d fixbug-0.1
集成工具 jenkins 的基本介绍和自动化部署微前端项目的几个简单方案
Jenkins 是国际上流行的免费开源软件项目,是基于 Java 开发持续集成工具,用于监控持续重复的工作,旨在提供一个开放的易用的软件平台,使软件的持续集成自动化,大大节约人力和时效。
Jenkins 功能包括:
安装好的 jenkins 可以在系统管理进行配置系统信息等
系统设置
凭据配置
凭据管理
插件管理
Jenkins 提供了两种不同的方法来在主服务器上安装插件:
在管理平台界面中使用插件管理器
通过“系统管理” >“插件管理”视图,Jenkins 环境的管理员可以使用该视图。在“ 可选插件 ”选项卡下,可以搜索用户需要的插件,搜索到需要的插件后勾选插件列表的选中框,之后点击左下角的下载并且重启后安装,等待插件下载完成后服务自动重启,重新进入系统即安装成功。
使用 Jenkins CLI install-plugin 命令
管理员还可以使用 Jenkins CLI,它提供了安装插件的命令。用于管理 Jenkins 环境的脚本或配置管理代码可能需要安装插件,而无需在 Web UI 中直接进行用户交互。Jenkins CLI 允许命令行用户或自动化工具下载插件及其依赖项
管理用户
Jenkins 默认使用自带数据库模式存储用户,jenkins 默认创建 admin 账号,默认密码位于 /var/lib/jenkins/secrets/initialAdminPassword
,登录之后可在管理用户修改用户默认密码
视图功能主要用于管理不同项目之间的任务,每个项目创建一个视图并在视图下管理整个项目的模块。
任务配置主要将自动化构建部署从项目的获取到部署成功的一个过程需要做的工作做分解配置。
这一步主要是在执行构建前对 jenkins 配置进行了简单的设置
描述
丢弃旧的构建
参数化构建过程
Extended Choice Parameter 插件,该插件可以使用多选框,利用该插件可以指定需要打包的应用,而不需要打包所有项目,减少打包时间
Check Boxes
(值使用的类型)8
checkbox 参数值个数(项目子包和主包个数),
各个值的分割符号main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system
参数值(主包和子包相对项目路径main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system
参数默认选中的值(主包和子包相对项目路径布尔值参数,true/false 值的参数,当前应用于构建过程中判断是否需要构建 npm install
Git plugin
GIT 仓库管理插件,用于同步 git 库,通过该插件 jenkins 任务可以在构建过程中获取配置好的 git 远程仓库代码,任务执行时代码会被拉取到/var/lib/jenkins/workspace/{任务名称}
目录下
执行 shell
开始执行构建任务之前源码管理插件已经将代码从远程库中获取,执行 shell 任务主要通过获取参数化构建时设置的参数去对整个项目中的各个应用进行打包并将打包完成的部署文件统一放在根目录的发布文件夹publish
,执行详细代码如下:
!/bin/bash |
Send build artifacts over SSH,使用该插件需要在系统管理->插件管理
中安装,该插件主要功能为将构建好的部署包按照一定规则发送到部署服务器,并且在这之后可在部署服务器执行一定的 shell 操作。安装插件后还需要在系统管理->系统配置->Publish over SSH
添加 SSH Services。
Name:选择部署服务器,所选服务器就是系统配置中所添加,构建时就会连接该服务器
Transfers
publish/**
/home/jenkinsC
|
按照上一步的配置规则执行自动化构建和部署
路径:工程->Build With Parameters->开始构建
点击开始构建前需要配置构建所需的参数,构建过程中在左下角的构建历史可以查看构建进度条。
在左侧的构建历史可以查看构建记录的状态,并且每个构建记录还能通过构建编号左侧的小球颜色判断状态,一般有三个状态分别分为 SUCCESS(蓝色)、UNSTABLE(黄色)、FAILURE(红色),点击对应构建记录可查看详细信息
状态描述:
构建记录:
Extended Choice Parameter
插件,该插件可以使用多选框
Git plugin
/var/lib/jenkins/workspace/{任务名称}
目录下Send build artifacts over SSH,使用该插件需要在系统管理->插件管理
中安装,该插件主要功能为将构建好的部署包按照一定规则发送到部署服务器,并且在这之后可在部署服务器执行一定的 shell 操作。安装插件后还需要在系统管理->系统配置->Publish over SSH
添加 SSH Services。
在系统管理->系统配置->Publish over SSH
添加
效果演示
配置流程
构建的shell代码
!/bin/bash -ilex |
构建后操作shell代码
#!/bin/bash -ilex |
最后配置一下Nginx指向
/home/docker/nginx/html/web-test
部署目录即可访问
介绍阿里云对象存储部署步骤。
https://oss.console.aliyun.com/
其他保持默认
https://cdn.console.aliyun.com/
路径:CDN > 域名管理 > 添加域名
加速域名:xxx.test.com
其他保持默认
路径:CDN > 域名管理 > 找到域名
路径:CDN > 域名管理 > 域名名称 > HTTPS 配置 > HTTPS 证书 > 修改配置
HTTPS 安全加速:开启
其他保持默认
路径:CDN > 域名管理 > 找到域名
CNAME:xxx.test.com.w.kunlunpi.com
https://dns.console.aliyun.com/
路径:云解析 DNS > 域名解析 > 找到域名
路径:云解析 DNS > 域名解析 > 解析设置 > 添加记录
记录类型:CNAME
其他保持默认
路径:对象存储 > Bucket 列表 > 找到存储桶
路径:对象存储 > 存储桶名称 > 文件管理 > 找到 index.html 文件 > 更多 > 设置 HTTP 头
Cache-Control:no-cache(Object 允许被缓存在客户端或代理服务器的浏览器中,但每次访问时需要向 OSS 验证缓存是否可用。缓存可用时直接访问缓存,缓存不可用时重新向 OSS 请求。)
其他保持默认
路径:对象存储 > 基础设置 > 静态页面
默认首页:index.html
默认 404 页:index.html
路径:对象存储 > 传输管理 > 域名管理 > 绑定域名
域名:xxx.test.com
其他保持默认
我们使用函数的时候不用关心后端IP、域名,只需要调用函数,后端的服务是一个函数。serverless并不是没有服务器,只是服务器部署在云上面的,比我们去自己维护更方便多
Serverless
又名无服务器,所谓无服务器并非是说不需要依赖和依靠服务器等资源,而是开发者再也不用过多考虑服务器的问题,可以更专注在产品代码上。Serverless
是一种软件系统架构的思想和方法,它不是软件框架、类库或者工具。它与传统架构的不同之处在于,完全由第三方管理,由事件触发,存在于无状态(Stateless
)、 暂存(可能只存在于一次调用的过程中)计算容器内。构建无服务器应用程序意味着开发者可以专注在产品代码上,而无须管理和操作云端或本地的服务器或运行时(运行时通俗的讲 就是运行环境,比如 nodejs
环境,java
环境,php
环境)。Serverless
真正做到了部署应用 无需涉及基础设施的建设,自动构建、部署和启动服务。
- 第一种:
狭义 Serverless
(最常见)=Serverless computing 架构
=FaaS 架构
=Trigger(事件驱动)+ FaaS(函数即服务)+ BaaS
(后端即服务,持久化或第三方服务)=FaaS + BaaS
- 第二种:广义
Serverless
=服务端免运维
=具备 Serverless 特性的云服务
- FAAS:函数及服务,通俗来说就是我们可以写一个函数,在该函数内执行业务逻辑,函数由fas平台运行
- BAAS:后端及服务,通常指云服务,该云服务常指中间件服务,
FAAS+BAAS 构成了Serverless架构
整体架构十分简单明了, 用 FC 替代了 Web 服务器,但是换来的是免运维,弹性扩容,按需付费等一系列优点
目前,Serverless 的应用场景广泛,大部分传统业务均可以在 Serverless 云函数上完美支持
问题:前端和后端分离后,彼此独立,这样就导致前端需要关注一些后端关注的问题,如下。
是不是触及了很多同学的知识盲区。作为一个前端,确实大多数人对于服务端的环境,部署基础设施等等东西并不了解。但是现在前端是独立的部署,前端必然面临这些东西。这就是serverless要解决的问题。
问题:是不是可以弄一个工具,我们只需要关心前端代码,服务器的东西工具自动帮我们做好。
这就是serverless做的事情。如下图,我们只需要关心业务代码,不需要关心服务器的基础设施。
事件驱动
单事件处理
自动弹性伸缩
无状态开发
serverless
开发模式正常来说,用户开发 Server 端服务,常常面临开发效率,运维成本高,机器资源弹性伸缩等痛点,而使用 Serverless 架构可以很好的解决上述问题。下面是传统架构和 Serverless 架构的对比:
云函数计算是一个事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,并提供日志查询,性能监控,报警等功能。借助于函数计算,您可以快速构建任何类型的应用和服务,无需管理和运维。
优势
Serverless
架构中,你不用关心应用运行的资源(比如服务配置、磁盘大小)只提供一份代码就行。Serverless
架构中,计费方式按实际使用量计费(比如函数调用次数、运 行时长),不按传统的执行代码所需的资源计费(比如固定 CPU
)。计费粒度也精确到了毫 秒级,而不是传统的小时级别。个别云厂商推出了每个月的免费额度,比如腾讯云提供了每 个月 40 万 GBs 的资源使用额度和 100 万次调用次数的免费额度。中小企业的网站访问量不 是特别大的话完全可以免费使用。Serverless
架构的弹性伸缩更自动化、更精确,可以快速根据业务并发扩容更 多的实例,甚至允许缩容到零实例状态来实现零费用,对用户来说是完全无感知的。而传统 架构对服务器(虚拟机)进行扩容,虚拟机的启动速度也比较慢,需要几分钟甚至更久。不足
当然了 ServerLess 是很诱人,但却不是万能的,有些场景还是不适合的。
Serverless由开发者实现的服务端逻辑运行在无状态的计算容器中,它由事件触发, 完全被第三方管理,其业务层面的状态则被开发者使用的数据库和存储资源所记录。Serverless涵盖了很多技术,分为两类:FaaS和BaaS。
FaaS(Function as a Service,函数即服务)
相比传统系统,部署方法会有较大变化 – 将代码上传至FaaS供应商,其他事情均可由供应商完成。目前这种方式通常意味着需要上传代码的全新定义(例如上传zip或JAR文件),随后调用一个专有API发起更新过程。
FaaS中的函数可以通过供应商定义的事件类型触发。对于亚马逊AWS,此类触发事件可以包括S3(文件)更新、时间(计划任务),以及加入消息总线的消息(例如Kinesis)。通常你的函数需要通过参数指定自己需要绑定到的事件源。
大部分供应商还允许函数作为对传入Http请求的响应来触发,通常这类请求来自某种该类型的API网关(例如AWS API网关、Webtask)
BaaS(Backend as a Service,后端即服务)
BaaS(Backend as a Service,后端即服务)是指我们不再编写或管理所有服务端组件,可以使用领域通用的远程组件(而不是进程内的库)来提供服务
同步调用的特性是,客户端期待服务端立即返回计算结果。请求到达函数计算时,会立即分配执行环境执行函数。
以 API 网关为例,API 网关同步触发函数计算,客户端会一直等待服务端的执行结果,如果执行过程中遇到错误, 函数计算会将错误直接返回,而不会对错误进行重试。这种情况下,需要客户端添加重试机制来做错误处理。
异步调用的特性是,客户端不急于立即知道函数结果,函数计算将请求丢入队列中即可返回成功,而不会等待到函数调用结束。
函数计算会逐渐消费队列中的请求,分配执行环境,执行函数。如果执行过程中遇到错误,函数计算会对错误的请求进行重试,对函数错误重试三次,系统错误会以指数退避方式无限重试,直至成功。
异步调用适用于数据的处理,比如 OSS 触发器触发函数处理音视频,日志触发器触发函数清洗日志,都是对延时不敏感,又需要尽可能保证任务执行成功的场景。如果用户需要了解失败的请求并对请求做自定义处理,可以使用 Destination 功能。函数计算是 Serverless 的,这不是说无服务器,而是开发者无需关心服务器,函数计算会为开发者分配实例执行函数。
FaaS 中的冷启动是指从调用函数开始到函数实例准备完成的整个过程。 冷启动我们关注的是启动时间,启动时间越短,我们对资源的利用率就越高。现在的云服务商,基于不同的语言特性,冷启动平均耗时基本在 100~700 毫秒之间。得益于 Google 的 JavaScript 引擎 Just In Time 特性,Node.js 在冷启动方面速度是最快的。
请求第一次访问时,云服务商就可以利用构建好的缓存镜像,直接跳过冷启动的下载函数代码步骤,从镜像启动容器,这个也叫预热冷启动。所以如果我们有些业务场景对响应时间比较敏感,我们就可以通过预热冷启动或预留实例策略,加速或绕过冷启动时间。
云服务商负责的就是容器和 Runtime 的准备阶段了。而开发者自己负责的则是函数执行阶段。一旦容器 &Runtime 启动后,就会维持一段时间,这段时间内的这个函数实例就可以直接处理用户数据请求。当一段时间内没有用户请求事件发生(各个云服务商维持实例的时间和策略不同),则会销毁这个函数实例。
SFF(Serverless For Frontend)指前端数据请求过来,函数触发器触发我们的函数服务;我们的函数启动后,调用后端提供的元数据接口,并将返回的元数据加工成前端需要的数据格式;我们的 FaaS 函数完全就可以休息了。
回到我们的进程模型,用完即毁型是天然的 Stateless,因为它执行完就销毁,你无法单纯用它持久化存储任何值;常驻进程型则是天然的 Stateful,因为它的主进程不退出,主进程可以存储部分值。
BaaS 化的核心思想就是将后端应用转换成 NoOps 的数据接口,这样 FaaS 在 SFF 层就可以放开手脚,而不用再考虑冷启动时间了。
发送通知
诸如 PUSH Notification、邮件通知接口、短信,这一类服务来说,他们都需要基础设施来搭建。并且,他们对实时性的要求相对没有那么高。即使在时间上晚来几秒钟,用户还是能接受的。在我们所见到的短信发送的例子里,一般都会假设用户能在 60 秒内收到短信。因此,在这种时间 1s 的误差,用户也不会恼火的。
轻量级 API
Serverless 特别适合于,轻量级快速变化地 API。其实,我一直没有想到一个合适的例子。在我的假想里,一个 AutoSuggest 的 API 可能就是这样的 API,但是这种 API 在有些时候,往往会伴随着相当复杂的业务。于是,便想举一个 Featrue Toggle 的例子,尽管有一些不合适。但是,可能是最有价值的部分。
物联网
当我们谈及物联网的时候,我们会讨论事件触发、传输协议、海量数据(数据存储、数据分析)。而有了 Serverless,那么再多的数据,处理起来也是相当容易的一件事。对于一个物联网应用的服务端来说,系统需要收集来自各个地方的数据,并创建一个个 pipeline 来处理、过滤、转换这些数据,并将数据存储到数据库中。对于硬件开发人员来说,对接不同的硬件,本身就是一种挑战。而直接使用诸如 AWS IoT 这样国,可以在某种程度上,帮助我们更好地开发出写服务端连接的应用。
数据统计分析等
数据统计本身只需要很少的计算量,但是生成图表,则可以定期生成。在接收数据的时候,我们不需要考虑任何延时带来的问题。50~200 ms 的延时,并不会对我们的系统造成什么影响。
链接地址
选择腾讯云的缘故
serverless
方面相比其他厂商支持更好一些 serverless
合作在腾讯云中集成了 serverless Framework
用我们喜欢的框架开发 serverless
应用。也可以让我们快速部署老项目。前后台联调时间有时候更多,等项目上线需要考虑更多运维的问题,买域名买服务器等
云开发是什么
让开发者更专注于业务的开发,在云开发云函数中,我们可以很方便获取小程序用户openId、unionId一些鉴权信息,减轻后台开发量
简单的说,就是云开发是一套综合类服务的技术产品,通常开发一个完整的应用(小程序也好,Web、移动应用也好)都需要数据库、存储、CDN、后端函数、静态托管、用户登录等等,但是云开发将这些服务都集成到了一起,而且以一种全新的开发方式,让开发一个应用更加快速、方便、便宜且强大,引领未来技术开发的新趋势。
我们不需要区分那部分是前端那部分是后端,我们只需要调用函数一样去哪里这个流程就可以,云函数也可以在本地调式,调式云函数就像调式我们的代码一样的
云开发优势
小程序云开发提供哪些基础能力
产品定价
支持地域
免费额度
每个月的免费额度,会在每月开始时刻重置,不会进行累积
配额限制说明
找到云开发的环境ID,点击云开发控制台窗口里的设置图标,在环境变量的标签页找到环境名称和环境ID。
用户在开通云开发之后就创建了一个云开发环境,微信小程序可拥有最多两个环境,每个环境都对应一整套独立的云开发资源,包括数据库、云存储、云函数、静态托管等,各个环境是相互独立的。每个环境都有一个唯一的环境ID(环境名称不唯一)。
指定开发者工具的云开发环境
当云开发服务开通后,我们可以在小程序源代码cloudfunctions文件夹名看到你的环境名称。如果在cloudfunctions文件夹名显示的不是环境名称,而是“未指定环境”,可以鼠标右键该文件夹,可以看到弹窗的第一项为“当前环境”,有个小三角,在这里可以选择或切换已经建好的云开发环境。如果环境为空白,重启开发者工具,再来选择。
指定小程序的云开发环境
在开发者工具中打开源代码文件夹miniprogram
里的app.js文件,找到如下代码:
wx.cloud.init({ |
在 env: ‘my-env-id’处改成你的环境ID,注意需要填入的是你的环境ID而不是环境名称哦,结果如下:
// 因为云开发可以创建多个环境,比如微信小程序就可以创建两个免费的云开发环境,一个用于测试,一个用于正式发布。如果你没有在小程序端指定环境,会默认选择为你创建的第一个云开发环境。我们可以通过修改env的参数来切换小程序端用来调用的云开发环境。 |
小程序云开发控制台
腾讯云云开发网页控制台
我们还可以使用腾讯云云开发网页控制台来管理云开发资源,需要注意两点,一个是登录方式需要选择其他登录方式里的微信公众号,点击然后使用手机微信扫码,在微信上选择你要登录的小程序;二是要进入腾讯云后台之后切换选择云开发Cloudbase。
云开发资源还支持其他方式来调用
Tencent CloudBase Toolkit
:Tencent CloudBase Toolkit是一款Visual Studio Code的云开发插件,使用这个插件可以更好地在本地进行云开发项目开发和代码调试,并且轻松将项目部署到云端;云函数的根目录与云函数目录
cloudfuntions文件夹图标里有朵小云,表示这就是云函数根目录。展开cloudfunctions,我们可以看到里面有login、openapi、callback、echo等文件夹,这些就是云函数目录。而miniprogram文件夹则放置的是小程序的页面文件
cloudfunctions里放的是云函数,miniprogram放的是小程序的页面,这并不是一成不变的,也就是说你也可以修改这些文件夹的名称,这取决于项目配置文件project.config.json
里的如下配置项:
"miniprogramRoot": "miniprogram/", |
但是你最好是让放小程序页面的文件夹以及放云函数的文件夹处于平级关系且都在项目的根目录下,便于管理。
云函数部署与上传
npm install
命令下载依赖文件;npm i tcb-router |
使用步骤
1、在微信公众平台上获取消息模板的ID
2、获取下发的权限:
wx.requestSubscribeMessage({ |
subscribeNew
: 获取下发消息的权限,由用户自主选择订阅
subscribeNew:function(){ |
3、调用接口下发订阅消息:subscribeMessage.send
这里是云调用订阅消息,首先要创建一个云函数
需要在config.json中配置subscribeMessage.send
权限
config.json:
"permissions": { |
云函数编写
// 云函数入口文件 |
当用户订阅消息之后,就可以给用户下发消息了。
<view bindtap="sendNew">发送消息</view> |
sendNew:function(){ |
最后将云函数上传部署,使用手机测试,成功后,在微信的服务通知就会收到了订阅的消息
每天指定时间执行云函数
1. 云数据库获取100条数据突破
2. 分页查询数据库
3. 模糊查询
4. 数据权限管理
5. 云数据库中1对N关系的三种设计方式
选择“云端函数”列表右侧的 ,向云端函数发送触发事件。
const fs = require('fs') |
// utils/callCloudFn |
// utils/callCloudStorage |
// 使用 |
Web 函数运行原理如下图所示:
用户发送的 HTTP 请求经过 API 网关后,网关侧将原生请求直接透传的同时,在请求头部添加了网关触发函数时需要的函数名、函数地域等内容,并一起传递到函数环境,触发后端函数执行。
函数环境内,通过内置的 Proxy 实现 Nginx 转发,并去除头部非产品规范的请求信息,将原生 HTTP 请求通过指定端口发送给用户的 Web Server 服务。
用户的 Web Server 配置好指定的监听端口9000和服务启动文件后部署到云端,通过该端口获取 HTTP 请求并进行处理。
Web 函数请求限制
必须监听指定的 9000 端口
,不可以监听内部回环地址 127.0.0.1
。启动文件作用
scf_bootstrap 为 Web Server 的启动文件,保证您的 Web 服务正常启动并监听请求。除此之外,您还可以根据需要在 scf_bootstrap 中自定义实现更多个性化操作:
使用前提
scf_bootstrap
文件具备777或755权限,否则会因为权限不足而无法执行。#!/bin/bash
。/var/lang/${specific_lang}${version}/bin/${specific_lang}
,否则无法正常调用,详情请参见 标准语言环境绝对路径。0.0.0.0
,不可以使用内部回环地址 127.0.0.1
标准语言环境绝对路径
常见 Web Server 启动命令模版
serverless文档 https://www.serverless.com/cn/framework/docs/
1. 控制台部署
部署koa2管理端接口
官方demo https://github.com/tencentyun/serverless-demo/blob/master/WebFunc-KoaDemo/src/app.js
仓库关联GitHub,提交git代码自动更新
2. 命令行部署-Serverless Framework方式
云函数和serverless的区别
Serverless Framework
是Serverless
公司推出的一个开源的Serverless
应用开发框架。Serverless Framework
是由 Serverless Framework Plugin
和 Serverless Framework Components
组成。Serverless Framework Plugin
实际上是一个函数的管理工具,使用这个工具,可以很轻松的部署函数、删除函数、触发函数、查看函数信息、查看函数日志、回滚函数、查看函数 数据等。简单的概括就是serverless
其实就云函数的集合体,使用serverless
后我们创建的云函数不需要手动去创建触发器等操作。Serverless Framework应用场景
什么场景下需要使用
serverless
,而不是使用云函数,其实在实际开发过程中,我们都是使用serverless
而不去使用云函数,毕竟云函数的使用场景受限,或者说比较基础。打一个简单的比方,在写js
操作dom
的时候,你会选择用原生js
还是会使用jquery
一样的比喻。
Serverless Framework
,开发者可以在命令行完成函数的开发、部署、调试。还可以结合前端服务、 API 网关、数据库等其它云上资源,实现全栈应用的快速部署。Serverless Framework
提供了一套通用的框架迁移方案,通过使用 Serverless Framework
提供的框架组件(Egg/Koa/Express
等,更多的框架支持可以参考),原有应用仅需几行代码简单改造, 即可快速迁移到函数平台。同时支持命令行与控制台的开发方式。使用
serverless
命令创建第一个应用
全局安装命令
npm install -g serverless |
创建项目
在电脑的一个空目录下运行命令
serverless |
根据提示选择你要创建的模板
控制台输入
serverless
选择对应的模板
部署上线
serverless deploy |
serverless.yml配置详情
https://github.com/serverless-components/tencent-http/blob/master/docs/configure.md
部署上线后可以在这里查看你的项目
测试部署的项目
删除部署的项目
3. 在vscode中配置插件来开发serverless
在vscode
上安装插件
在vscode
安装后插件登录并且拉取应用
关于登录账号及密钥查看地址
远程拉取代码
下载后的代码如果想上传也可以直接上传的
WebIDE创建云函数实践
建议使用 Serverless Framework CLI,可快速部署本地云函数
vue
项目文件vue
项目部署都要先npm run build
,然后将打包后的dist
目录传到服务器上的nginx
静态目录下,这样才能访问oss
中的serverless
默认生成的项目是vue2
版本的,如果你要部署vue3
的项目需要手动构建# serverless.yml文件 |
手动构建一个vue3
的项目
vue3
项目serverless.yml
文件serverless init website-starter --name example |
将这个
serverless.yml
文件复制到vue3
项目中
简单的修改下
component: website |
部署上线
serverless deploy
手动部署react
项目
手动创建一个
react
项目
npx create-react-app react-demo --template typescript |
在
react
根目录下创建一个serverless.yml
的文件
component: website |
推送到云端
serverless deploy
使用静态文件托管来部署前端项目
数据库的准备
使用
serverless
开发与我们自己使用云服务器服务器ECS
不一样的,因为我们不能在serverless
上安装软件(我们不能安装第三方的mysql
、docker
、redis
)等软件,因此我们在使用serverless
开发的时候,如果我们项目中使用到了比如:mysql
、redis
、rabbitMQ
、RocketMQ
类的我们就需要自行解决。下面介绍几种方式
ECS
,我们在上面安装了需要的软件,对外提供了IP
或者域名,在安全组中开放了端口号以供我们在serverless
中使用。其实如果你自己有云服务器ECS
可能就不会考虑使用serverless
来开发了NoSQL
数据库参考文档,本训练营会介绍如何使用,但是在项目中不会使用。docker
搭建了一个mysql8
版本的数据库,以供大家学习使用,自己根据自己的名字来在上面创建自己的数据库。大家自行保存地址,如果自己有服务器的,可以自己服务器上搭建,就不需要用我这个# ip地址 |
在serverless
中连接mysql
本案例只是测试官方案例连接数据库,不涉及什么知识点,根据自身条件选择是否跳过
在函数服务中选择
mysql
数据库模板来创建数据库云函数应用。注意选择的语音和区域
在自己的数据库中创建数据库及数据表
-- 创建数据表sql |
-- 插入几条数据sql |
打开云函数的代码管理修改数据库的连接配置
进入代码编辑界面的函数代码编辑代码
function wrapPromise(connection, sql) { |
修改完成后点击部署和测试
出现下面界面表示你已经在云函数中连接mysql
成功了
如果你想在浏览器上测试访问,可以点击触发管理
Serverless Framework
是无服务器应用框架,提供将云函数SCF
、API
网关、对象存储 COS
、云数据库 DB
等资源组合的业务框架,开发者可以直接基于框架编写业务逻辑,而无需关注底层资源的配置和管理。Tencent CloudBase,TCB
)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用、自动弹性扩缩的后端云服务,包含计算、存储、托管等 serverless
化能力,可用于云端一体化开发多种端应用(小程序、公众号、Web
应用、Flutter
客户端等),帮助开发者统一构建和管理后端服务和云资源,避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。使用云开发创建一个nest
项目
在产品中选择云开发产品
创建一个项目,注意点:这里要选择好区域,下次创建了项目,区域不一样,可能项目就看不到
使用模板创建
查看自己创建应用并且访问
使用脚手架的方式创建
全局安装脚手架包官方地址
npm i -g @cloudbase/cli |
测试安装是否成功
cloudbase -v |
登录
cloudbase login |
本地创建项目
tcb new <appName> [template] |
选择自己已经创建的环境,如果没有就 创建新环境,这时候会打开浏览器
本地打开项目并且安装依赖包
npm run dev |
部署到线上
npm run deploy |
在云开发中使用NoSQL
在面板上创建一个NoSQL
的数据库,参考地址
在项目中安装连接数据库的SDK
参考文档
npm install @cloudbase/node-sdk |
初始化数据库连接参考地址
import cloudbase from '@cloudbase/node-sdk'; |
关于获取secretId
、secretKey
、env
的地址
env
的获取地址secretId
和secretKey
获取test
用户进入根据文档,我们将插入一条数据(同样先忽视类型检测)
async createAccount(data: any): Promise<any> { |
环境变量的配置
本地开发环境变量的配置
npm install dotenv |
在项目根目录下创建一个
.env
的文件用来存储一些敏感信息
PORT=7000 |
在应用中使用使用环境变量的值
const app = cloudbase.init({ |
国外挺好用的一个serverless免费平台
微前端现有的落地方案可以分为三类,自组织模式、基座模式以及模块加载模式。
这里我们通过3W(what,why,how)的方式来讲解什么是微前端:
微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。
微前端的核心在于拆, 拆完后再合!
我们是不是可以将一个应用划分成若干个子应用,再将子应用打包成一个个的lib呢?当路径切换时加载不同的子应用,这样每个子应用都是独立的,技术栈也就不用再做限制了!从而解决了前端协同开发的问题。
Single-SPA
诞生了, single-spa
是一个用于前端微服务化的JavaScript前端解决方案 (本身没有处理样式隔离、js执行隔离) 实现了路由劫持和应用加载;single-spa + sandbox + import-html-entry
),它 做到了技术栈无关,并且接入简单(有多简单呢,像iframe一样简单)总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,并且技术栈无关,靠的是协议接入(这里提前强调一下:子应用必须导出
bootstrap、mount、unmount
三个方法)。
这里先回答一下大家可能会有的疑问:
这不是iframe吗?
如果使用的是
iframe
,当iframe中的子应用切换路由时用户刷新页面就尴尬了。
应用间如何通信?
如何处理公共依赖?
externals
联邦模块
首先创建一个vue子应用,并通过
single-spa-vue
来导出必要的生命周期:
vue create spa-vue |
// main.js |
配置子路由基础路径
// router.js |
将子模块打包成类库
//vue.config.js |
<div id="nav"> |
将子应用挂载到
id="vue"
标签中
import Vue from 'vue' |
if(window.singleSpaNavigate){ |
qiankun
是目前比较完善的一个微前端解决方案,它已在蚂蚁内部经受过足够大量的项目考验及打磨,十分健壮。这里附上官网。
<template> |
注册子应用
|
// src/router.js |
// main.js |
这里不要忘记子应用的钩子导出。
// vue.config.js |
再起一个子应用,为了表明技术栈无关特性,这里使用了一个
React
项目:
// app.js |
// index.js |
重写react中的webpack配置文件 (config-overrides.js)
yarn add react-app-rewired --save-dev |
修改package.json文件
// react-scripts 改成 react-app-rewired |
在根目录新建配置文件
// 配置文件重写 |
// config-overrides.js |
配置.env文件
根目录新建
.env
PORT=20000 |
React路由配置
import { BrowserRouter, Route, Link } from "react-router-dom" |
$ npm init ice icestark-layout @icedesign/stark-layout-scaffold |
// src/app.jsx中加入 |
// 侧边栏菜单 |
# 创建一个子应用 |
// 修改vue.config.js |
src/main.js
改造
import { createApp } from 'vue' |
router改造
import { getBasename } from '@ice/stark-app'; |
create-react-app react-child |
// src/app.js |
npm run eject
后,改造config/webpackDevServer.config.js
hot: '', |
Dynamic Stylesheet
动态样式表,当应用切换时移除掉老应用样式,再添加新应用样式,保证在一个时间点内只有一个应用的样式表生效
BEM(Block Element Modifier)
约定项目前缀CSS-Modules
打包时生成不冲突的选择器名css-in-js
|
shadow DOM
内部的元素始终不会影响到它的外部元素,可以实现真正意义上的隔离
当运行子应用时应该跑在内部沙箱环境中
Proxy
代理沙箱,不影响全局环境modifyPropsMap
中,并用快照还原window属性class SnapshotSandbox { |
let sandbox = new SnapshotSandbox(); |
快照沙箱只能针对单实例应用场景,如果是多个实例同时挂载的情况则无法解决,这时只能通过Proxy代理沙箱来实现
class ProxySandbox { |
]]>每个应用都创建一个
proxy
来代理window
对象,好处是每个应用都是相对独立的,不需要直接更改全局的window
属性
首屏时间可以拆分为白屏时间、数据接口响应时间、图片加载资源等
FMP.Start()
,在首屏结束位置打上 FMP.End()
,利用 FMP.End()-FMP.Start()
获取到首屏时间。所谓自动化采集,即引入一段通用的代码来做首屏时间自动化采集,引入过程中,除了必要的配置不需要做其他事情。
自动化采集的好处是独立性更强,接入过程更自动化。具体的自动化采集代码,可以由一个公共团队来开发,试点后,推广到各个业务团队。而且统计结果更标准化,同一段统计代码,标准更统一,业务侧同学也更认可这个统计结果。
当然,它也有缺点,最明显的是,有些个性化需求无法满足,毕竟在工作中,总会有一些特殊业务场景。所以,采用自动化采集方案必须做一些取舍。
SPA 页面因为无法基于 DOMContentLoaded
做首屏指标采集,可以使用 MutationObserver
采集首屏时间。
MutationObserver
接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的Mutation Events
功能的替代品,该功能是 DOM3 Events 规范的一部分。
简单来说, 使用 MutationObserver 能监控页面信息的变化
,当页面 body
变化最剧烈的时候,我们拿到的时间数据,就是首屏时间
。
首先,在用户进入页面时,我们可以使用 MutationObserver
监控 DOM
元素 (Document Object Model,文档对象模型)。当 DOM 元素发生变化时,程序会标记变化的元素,记录时间点和分数,存储到数组中。数据的格式类似于 [200ms,18.5]
。
为了提升计算的效率,我们认为首屏指标采集到某些条件时,首屏渲染已经结束,我们需要考虑首屏采集终止的条件,即计算时间超过 30 秒还没有结束;计算了 4 轮且 1 秒内分数不再变化;计算了 9 次且分数不再变化。
接下来,设定元素权重计算分数。
递归遍历 DOM 元素及其子元素,根据子元素所在层数设定元素权重,比如第一层元素权重是 1,当它被渲染时得 1 分,每增加一层权重增加 0.5,比如第五层元素权重是 3.5,渲染时给出对应分数。
为什么需要权重呢?
因为页面中每个 DOM 元素对于首屏的意义是不同的,越往内层越接近真实的首屏内容,如图片和文字,越往外层越接近 body 等框架层。
最后,根据前面的得分,计算元素的分数变化率,获取变化率最大点对应的分数。然后找到该分数对应的时间,即为首屏时间。
分数部分核心计算逻辑是递归遍历元素,将一些无用的标签排除,如果元素超过可视范围返回 0 分,每一层增加 0.5 的权重,具体请看下面代码示例。
function CScor(el, tiers, parentScore) { |
变化率部分核心计算逻辑是获取 DOM 变化最大时对应的时间,代码如下所示
calFinallScore() { |
这个就是首屏计算的部分流程。
看完前面的流程,不知道你有没有这样的疑问:如果页面里包含图片,使用上面的首屏指标采集方案,结果准确吗
?
结论是:不准确。上述计算逻辑主要是针对 DOM元素来做的,图片加载过程是异步,图片容器(图片的 DOM 元素)和内容的加载是分开的,当容器加载出来时,内容还没出来,一定要确保内容加载出来,才算首屏。
这就需要增加一些策略了,以下是包含图片页面的首屏计算 demo。
<body><img id="imgTest" src="https://www.baidu.com/img/bd_logo1.png?where=super"> |
它的计算逻辑是这样的。
首先,获取页面所有的图片路径。在这里,图片类型分两种,一种是带 IMG 标签的,一种是带 DIV 标签的。前者可以直接通过 src 值得到图片路径,后者可以使用 window.getComputedStyle(dom)
方式获取它的样式集合。
接下来,通过正则获取图片的路径即可。
然后通过 performance.getEntriesByName(element)[0].responseEnd
的方式获取到对应图片路径的下载时间,最后与使用 MutationObserver
获得的 DOM 首屏时间相比较,哪个更长,哪个就是最终的首屏时间。
以上就是首屏采集的完整流程
注意:计算首屏时间用到的
getBoundingClientRect
和getComputedStyle
会引起强制回流
白屏时间是指从输入内容回车(包括刷新、跳转等方式)后,到页面开始出现第一个字符的时间。白屏时间的长短会影响用户对 App 或站点的第一印象
白屏指标怎么采集呢?我们先来回顾一下浏览器的页面加载过程:
客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片-> 完成渲整体染
在这个过程中,客户端解析 DOM 并渲染之前的时间,都算白屏时间。所以,白屏时间的采集思路如下:白屏时间 = 页面开始展示时间点 - 开始请求时间点
。如果你是借助浏览器的 Performance API
工具来采集,那么可以使用公式:白屏时间 FP
= domLoading - navigationStart
。
这是浏览器页面加载过程,如果放在 App场景下,就不太一样了,App下的页面加载过程:
初始化 WebView -> 客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 服务端处理并返回数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片 -> 完成整体渲染。
App下的白屏时间,多了启动浏览器内核,也就是 Webview 初始化的时间
。这个时间必须通过手动采集的方式来获得,而且因为线上线下时间差别不大,线下采集即可。具体来说,在 App 测试版本中,程序在 App 创建 WebView 时打一个点,然后在开始建立网络连接打一个点,这两个点的时间差就是 Webview 初始化的时间。
所谓卡顿,简单来说就是页面出现卡住了的不流畅的情况。 提到它的指标,你是不是会一下就想到 FPS(Frames Per Second,每秒显示帧数)?FPS 多少算卡顿?网上有很多资料,大多提到 FPS 在 60 以上,页面流畅,不卡顿。但事实上并非如此,比如我们看电影或者动画时,素虽然 FPS 是 30 (低于60),但我们觉得很流畅,并不卡顿。
FPS 低于 60 并不意味着卡顿,那 FPS 高于 60 是否意味着一定不卡顿呢?比如前 60 帧渲染很快(10ms 渲染 1 帧),后面的 3 帧渲染很慢( 20ms 渲染 1 帧),这样平均起来 FPS 为95,高于 60 的标准。这种情况会不会卡顿呢?实际效果是卡顿的。因为卡顿与否的关键点在于单帧渲染耗时是否过长。
但难点在于,在浏览器上,我们没办法拿到单帧渲染耗时的接口,所以这时候,只能拿 FPS 来计算,只要 FPS 保持稳定,且值比较低,就没问题。
它的标准是多少呢?连续 3 帧不低于 20 FPS,且保持恒定
。
以 H5 为例,H5 场景下获取 FPS 方案如下
:
var fps_compatibility= function () { |
利用
requestAnimationFrame
在一秒内执行60
次(在不卡顿的情况下)这一点,假设页面加载用时X ms
,这期间requestAnimationFrame
执行了N
次,则帧率为1000* N/X
,也就是FPS
由于用户客户端差异很大,我们要考虑兼容性,在这里我们定义 fps_compatibility
表示兼容性方面的处理,在浏览器不支持 requestAnimationFrame
时,利用 setTimeout
来模拟实现,在 fps_loop
里面完成 FPS 的计算,最终通过遍历 fpsList
来判断是否连续三次 fps 小于20。
如果连续判断 3次 FPS 都小于20,就认为是卡顿。
由于性能 SDK 最终是给各个业务使用的,所以它的设计要满足在接入性能监控平台时,简单易用和运行平稳高效,这两个要求。
要保证 SDK
接入简单,容易使用,首先要把之前首屏、白屏和卡顿采集的脚本封装在一起,并让脚本自动初始化和运行
。
分数计算部分 API(calculateScore)
、变化率计算的 API(calFinallScore)
和首屏图片时间计算 API(fmpImg)
可以一起封装成 FMP API。其中首屏图片计算 API 因为比较独立,可以专门抽离成一个 util,供其他地方调用。白屏和卡顿采集也类似,可以封装成 FP API
和 BLOCK API
。ExtensionAPI
接口,用来封装一些后续需要使用的数据,比如加载瀑布流相关的数据(将首屏时间细分为DNS、TCP连接等时间),这些数据可以通过浏览器提供的 performance
接口获得。为了进行首屏、白屏、卡顿的指标采集,我们可以封装
Perf API
,调用FMP、FP、BLOCK、ExtensionAPI
四个 API 来完成。因为是调用window.performance
接口,所以先做环境兼容性的判断,即看看浏览器是否支持window.performance
最终我们接入时只要安装一个 npm
包,然后初始化即可,具体代码如下:
npm install @common/Perf -S; |
或者以外链的形式接入:
<script type="text/javascript">https://s1.static.com/common/perf/static/js/1.0.0/perf.min.js</script> |
除了性能 SDK 自身的方案设计之外,提供帮助文档(如示例代码、 QA 列表等),也可以提高性能 SDK 的易用性。
具体来说,我们可以搭建一个简单的性能 SDK 网站,进入站点后,前端工程师可以看到使用文档,包括各种平台下如何接入,接入的示例代码是怎样的,接入性能 SDK 后去哪个 URL 看数据,遇到异常问题时怎么调试,等等。
SDK 如果想运行高效,必须有好的兼容性策略、容错机制和测试方案。
所谓兼容性策略,就是性能 SDK 可以在各个业务下都可以稳定运行。
我们知道,前端性能优化会面临的业务场景大致有:
这就要求性能 SDK 要能适应这些业务,及时采集性能指标并进行上报。那具体怎么做呢?
一般不同页面和终端,它们的技术栈也会不同,如 PC端页面使用 React,移动端页面使用 VUE 。这个时候,我们可以尽可能用原生 JavaScript 去做性能指标的采集,从而实现跨不同技术栈的采集
。
不同终端方面,我设计了一个适配层来抹平采集方面的差异。
具体来说,小程序端可以用有自己的采集 API,如 minaFMP
,其他端可以直接用 FMP
,这样在性能 SDK 初始化时,根据当前终端类型的不同,去调用各自的性能指标采集 API
。
容错方面怎么做呢?
如果是性能 SDK 自身的报错,可以通过
try catch
的方式捕获到,然后上报异常监控平台。注意,不要因为 SDK 的报错而影响引入性能 SDK 页面的正常运行。
我的建议是,在采集性能指标之后,最好先对异常数据进行过滤。
异常数据分一般有两类,第一类是计算错误导致的异常数据,比如负值或者非数值数据,第二类是合法异常值、极大值、极小值,属于网络断掉或者超时形成的数值,比如 15s 以上的首屏时间。
负值的性能指标数据影响很大,它会严重拖低首屏时间,也会把计算逻辑导致负值的问题给掩盖掉。
性能 SDK 上报数据是全量还是抽象,需要根据本身 App 或者网站的日活来确定,如果日活10万以下,那抽样就没必要了。如果是一款日活千万的 App,那就需要进行数据抽样了,因为如果上报全量日志的话,会耗费大量用户的流量和请求带宽。
一般,为了节省流量,性能 SDK 也会根据网络能力,选择合适的上报机制。在强网环境(如 4G/WIFI
),直接进行上报;在弱网(2G/3G
)下,将日志存储到本地,延时到强网下再上报。
除了网络能力,我们还可以让 SDK 根据 App 忙碌状态,选择合适的上报策略。如果 App 处于空闲状态,直接上报;如果处于忙碌状态,等到闲时(比如凌晨 2-3 点)再进行上报。
除此之外,还有一些其他的策略,如批量数据上报,默认消息数量达到 30 条才上报,或者只在 App 启动时上报等策略,等等。你可以根据实际情况进行选择。
采集到数据后,先对数据进行校验,如果发现数据异常则直接上报到数据异常平台(通过邮件或者钉钉通知的方式发送给开发者),反之如果数据是正常范围内的,则结合采样率来看是否需要上报。
前端性能平台是一个 Web 系统,主要包括后台的性能数据处理和前台的可视化展示两部分
其中,数据处理后台主要是对 SDK 上报后的性能指标进行处理和运算,具体包括数据入库、数据清洗、数据计算,做完这些后,前台会对结果进行可视化展现,我们借助它就可以实时监督前端的性能情况。下图是性能平台大盘页的效果,主要对当前用户关注的性能模块进行展示,内容包括首屏时间、秒开率和采样PV。
那么,我们该如何搭建这样一个性能平台呢?
这是具体的技术架构图,从底层到前台大致情况如下:
SDK
上报的性能数据,做数据处理后入库,包含的技术有 Node.js、Node-sechdule、Node-mailer
;Kafka、Spark、Hive、HDFS
MySQL + MongoDB
,性能平台需要的数据会来这里React、Ant design、Antv、Less
想要搭建性能平台,我们先来看它的性能处理后台情况。一般性能 SDK 上报数据的处理过程是这样的:
SDK
上报性能数据指标,数据接入层(图中绿色部分)接收相应数据,并做协议转换等简单处理后,作为生产者向 Kafka 写入数据;上述数据流程,对应的性能数据后台的搭建过程如下:
为了避免数据库出现“脏数据”(如空数据、异常数据),影响后续数据处理,我们将 SDK 上报的数据通过 URL 解析成 key-value 格式的数据,对数据进行空数据删除,异常数据舍弃等操作。然后我们让数据写进消息队列 Kafka。
为什么不是直接存入 Hive 呢?
因为客户端上报的性能数据量和用户规模有关。如果直接入库到 Hive,遇到高并发的时候,会因为服务器扛不住而导致数据丢失。与此同时,因为数据下游(数据的使用方,如数据清洗计算平台,性能预警模块)会有多个数据接收端,直接入库的话也会造成数据重复。
所以最好我们选择 Kafka,先让数据写进消息队列。Kafka 能通过缓存,慢慢接收这些数据,降低流量洪峰压力。同时,消息队列还有接收数据后将其删除的特点,可以避免数据重复的问题。
对 Kafka 中的数据,做数据清洗和数据计算
数据清洗,是指针对性能上报单条数据进行核对校验的过程。所清洗的数据包括:
这几种类型数据问题如果不处理,最终会影响计算结果的准确性。那么该怎么处理呢?
做完数据清洗之后,我们还需要
使用 Spark 做数据计算,为可视化展现准备数据
。具体需要做以下数据计算:
1s ~ 2s
占比多少,2s ~ 4s
占比多少;其中,页面瀑布流时间是对首屏时间的细分,包括 DNS 查询、TCP链接、请求耗时、内容传输、资源解析、DOM 解析和资源加载的时间
。这些细分时间点,是我们根据 SDK 上报的 Performance 接口数据指标计算出来的,前端工程师根据页面瀑布流时间,可以快速定位性能瓶颈点出现在哪个环节。
为了完成前台展现,性能平台需要登录功能,还需要做一些用户关注的模块信息,比如前端开发者添加关注的业务模块。我们可以用关系数据库去存储这些数据,具体可以选择 MySQL完成账号权限系统和关注业务模块对应的数据表。
而性能数据,因为都是单条性能信息,相互之间并没有什么关系,可以用 MongoDB 做存储。具体来说,我们可以用 NodeJS 提供的定时脚本(Node-sechdule)从 Spark 取到数据导入到 MongoDB 中。
前端数据可视化展现前台,整体上只有两个页面,大盘页和详情页。
大盘页包括一个个业务的性能简图。每一个性能简图包括首屏时间、秒开率、采样 PV 数据。点击性能简图上的“进入详情”链接,就可以进入详情页。初次进入大盘页的时候,需要你登录并关注相关的业务,然后就可以在大盘首页看到相关的性能情况。
详情页的设计的初衷是为了对性能简图做进一步的补充,除了展示对应性能简图的秒开率、性能均值细节、白屏均值细节之外,还会展示终端信息,比如多少比例在IOS端,多少比例在Android端,以方便用户根据不同场景去做优化。
同时,为了解秒开率不达标原因或者首屏时间变慢的细节在哪里,我们会给出页面加载瀑布流,前面数据处理阶段已经提到可以使用的数据(包括 DNS 查询、TCP链接、请求耗时、内容传输、资源解析、DOM 解析和资源加载的时间),套用 AntV (阿里巴巴集团的数据可视化方案)的瀑布流模板即可完成数据展现。
那么,大盘页和详情页如何实现的呢?
首先是前端展示技术栈的选择,对应技术架构图中的淡黄色部分,因为这两个页面都属于 PC 端后台页面,主要给公司前端开发者使用,功能上更多是数据可视化展示,非常适合用 React 技术栈做开发。
为了更好实现首屏时间、秒开率和采用 PV 的功能效果,我们使用 AntdPro 的模板,相关的配套的数据可视化方案,我推荐 Antv,因为它能够满足我们在首屏时间、秒开率等性能指标的展示需求,用起来比较简单(开箱即用),功能灵活且扩展性强(比如秒开率部分,要自定义一些图形,能够较好满足)。
大盘页和详情页的数据展示效果比较丰富多样,相应的 CSS 代码逻辑就比较复杂,为了让 CSS代码更容易维护和扩展,CSS 方面可以选用 Less 框架。
接下来是前后端交互方面,为了让前后台更独立,大盘页、详情页与后端的通信通过 HTTP 接口来实现,使用 nginx 作为 Web Server。为了让传输更高效,我们采用 compression 对 HTTP 传输内容进行 GZip 压缩处理。
最后是后台服务部分,为了让性能平台开发过程更简单,效率更高,同时平台本身的性能体验更流畅,后台服务方面可以选用 Egg.js(基于 NodeJS 的开发框架)做开发,进行数据处理和存储服务。
为了解决监控预警的问题,我们借助 Node-schedule
做调度和定时任务的处理,通过 node-mailer
进行邮件报警
监控预警部分,我们借助 Node-schedule 做调度和定时任务的处理,通过 node-mailer 进行邮件报警。具体来说我们通过以下几步来实现。
第一步,准备预警数据
在做完数据清洗之后,一个分支使用 Spark
做计算,另外一个分支使用 Flink
实时数据计算。这两者的区别在于后者的数据是实时处理的,因为监控预警如果不实时的话,就没有意义了。有关数据的处理,我是这样做的:超过 2s 的数据,或者认定为卡顿的数据,直接标记为预警数据。实际当中你也可以根据情况去定义和处理。
第二步,我们借助 Node-schedule,用一批定时任务将预警数据通过 Node.js,拉取数据到 MongoDB 的预警表中。
第三步,预警的展示流程。根据预警方式不同,样式展示也不同。具体来说,预警的方式有三种:企业微信报警通知、邮件报警通知、短信报警通知。
以手机列表页为例,性能标准是首屏时间 1.5s,秒开率 90%,超过这个标准就会在性能平台预警模块展示,按照严重程度倒序排列展示。如果超出 10%,平台上会标红展示,并会发企业微信报警通知;如果超过 20%,会发借助 node-mailer
做邮件报警;如果超出 30%,会发短信报警通知。
注意,预警通知需要用到通信资源,为了避免数据量太大而浪费资源,一般对 App 首页核心的导航位进行页面监控即可。
当预警功能做好后,前端性能平台就可以对重要指标进行实时监督了。当发现性能问题——不论是我们自己发现还是用户反馈,都需要先对问题进行诊断,然后看情况是否需要进一步采取措施。
一般问题诊断时需要先确认是共性问题还是个例问题。如果是共性问题,那接下来我们就开始诊断和优化;如果是个例问题,是因为偶发性因素导致的(如个人的网络抖动、手机内存占用太多、用户连了代理等),则不需要进行专门优化。
懒加载是性能优化的前头兵。什么叫懒加载呢?懒加载是指在长页面加载过程时,先加载关键内容,延迟加载非关键内容。比如当我们打开一个页面,它的内容超过了浏览器的可视窗口大小,我们可以先加载前端的可视区域内容,剩下的内容等它进入可视区域后再按需加载。
具体怎么做呢?我们可以先根据手机的可视窗口,估算需要多少条数据,比如京东 App 列表页是 4 条数据,这时候,先从后端拉取 4 条数据进行展现,然后超出首屏的内容,可以在页面下拉或者滚动时再发起加载。
那么如果首页当中图片比较多,比如搜索引擎产品的首页,如何保证首屏秒开呢?同样也可以采用懒加载。以百度图片列表页为例,可视区域范围内的图片先请求加载,一般会根据不同手机机型估算一个最大数据,比如 ihone12 Pro 屏幕比较大, 4 行 8 条数据,我们就先请求 8 条数据,用来在可视区域展示,其他位置采用占位符填充,在滑动到目标区域位置后,才使用真实的图片填充。这样,通过使用懒加载,可以最大限度降低了数据接口传输阶段的时间。
如果说懒加载本质是提供首屏后请求非关键内容的能力,那么缓存则是赋予二次访问不需要重复请求的能力。在首屏优化方案中,接口缓存和静态资源缓存起到中流砥柱的作用。
接口缓存的实现,如果是端内的话,所有请求都走 Native 请求,以此来实现接口缓存。为什么要这么做呢?
App 中的页面展现有两种形式,使用 Native 开发的页面展现和使用 H5 开发的页面展现。如果统一使用 Native 做请求的话,已经请求过的数据接口,就不用请求了。而如果使用 H5 请求数据,必须等 WebView 初始化之后才能请求(也就是串行请求),而 Native 请求时,可以在 WebView 初始化之前就开始请求数据(也就是并行请求),这样能有效节省时间。
那么,如何通过 Native 进行接口缓存呢?我们可以借助 SDK 封装来实现,即修改原来的数据接口请求方法,实现类似 Axios 的请求方法。具体来说就是,把包括 post、Get 和 Request 功能的接口,封装进 SDK 中。
这样,客户端发起请求时,程序会调用 SDK.axios 方法,WebView 会拦截这个请求,去查看 App 本地是否有数据缓存,如果有的话,就走接口缓存,如果没有的话,先向服务端请求数据接口,获取接口数据后存放到 App 缓存中。
数据接口的请求一般来说较少,只有几个,而静态资源(如 JS、CSS、图片和字体等)的请求就太多了。以京东首页为例,177 个请求中除了 1 个文档和 1 个数据接口外,其余都是静态资源请求。
那么,如何做静态缓存方案呢?这里有两种情况,一种是静态资源长期不需要修改,还有一种是静态资源修改频繁的
资源长期不变的话,比如 1 年都不怎么变化,我们可以使用强缓存,如 Cache-Control 来实现。具体来说可以通过设置 Cache-Control:max-age=31536000,来让浏览器在一年内直接使用本地缓存文件,而不是向服务端发出请求。
至于第二种,如果资源本身随时会发生改动的,可以通过设置 Etag 实现协商缓存 具体来说,在初次请求资源时,设置 Etag(比如使用资源的 md5 作为 Etag),并且返回 200 的状态码,之后请求时带上 If-none-match
字段,来询问服务器当前版本是否可用。如果服务端数据没有变化,会返回一个 304 的状态码给客户端,告诉客户端不需要请求数据,直接使用之前缓存的数据即可
离线化是指线上实时变动的资源数据静态化到本地,访问时走的是本地文件的方案。说到这里,你是不是想到了离线包?离线包是离线化的一种方案,是将静态资源存储到 App 本地的方案,不过,在这里,我重点讲的是离线化的另一个方案——把页面内容静态化到本地。
离线化一般适合首页或者列表页等不需要登录页面的场景,同时能够支持 SEO 功能。那么,如何实现离线化呢?其实,打包构建时预渲染页面,前端请求落到 index.html 上时,已经是渲染过的内容。此时,可以通过 Webpack 的 prerender-spa-plugin
来实现预渲染,进而实现离线化。Webpack 实现预渲染的代码示例如下:
// webpack.conf.js |
懒加载、缓存和离线化都是在请求本身上下功夫,想尽办法减少请求或者推迟请求,并行化则是在请求通道上功夫,解决请求阻塞问题,进而减少首屏时间。 这就像解决交通阻塞一样,除了限号减少车辆,还可以增加车道数量,我们在处理请求阻塞时,也可以加大请求通道数量——借助于HTTP 2.0 的多路复用方案来解决。
HTTP 1.1 时代,有两个性能瓶颈点,串行的文件传输和同域名的连接数限制(6个),到了HTTP 2.0 时代,因为提供了多路复用的功能,传输数据不再使用文本传输(文本传输必须按顺序传输,否则接收端不知道字符的顺序),而是采用二进制数据帧和流的方式进行传输。
其中,帧是数据接收的最小单位,流是连接中的一个虚拟通道,它可以承载双向信息。每个流都会有一个唯一的整数 ID 对数据顺序进行标识,这样接收端收到数据后,可以按照顺序对数据进行合并,不会出现顺序出错的情况。所以,在使用流的情况下,不论多少个资源请求,只要建立一个连接即可。
文件传输环节问题解决后,同域名连接数限制问题怎么解决呢?以 Nginx 服务器为例,原先因为每个域名有 6 个连接数限制,最大并发就是 100 个请求,采用 HTTP 2.0 之后,现在则可以做到 600,提升了 6倍。
你一定会问,这不是运维侧要做的事情吗,我们前端开发需要做什么?我们要改变静态文件合并(JS、CSS、图片文件)和静态资源服务器做域名散列这两种开发方式。
具体来说,使用 HTTP 2.0 多路复用之后,单个文件可以单独上线,不需要再做 JS 文件合并了。因为原先遇到由 A 和 B 组成的 C 文件,其中 A 文件稍微有点修改,整个C 文件就需要重新加载的情况,如今由于没有同域名连接数限制了,也就不需要了。
现在我们假设一个场景,有一天你想要在某电商 App 上买个手机,于是你搜索后进入商品列表页,结果屏幕一片空白,过了好久还是没什么内容出现,这时候你是不是会退出来,换另外一个电商 App 呢?这就是白屏时间过长导致用户跳出的情形。
作为前端开发者,我们遇到这种问题如何解决呢?首先去性能平台上查看白屏时间指标,确认是否是白屏问题。问题确认后,我们可以基于影响白屏时间长短的两个主要因素来解决——DNS 查询和首字符展示。
DNS 查询是指浏览器发起请求时,需要将用户输入的域名地址转换为 IP 地址的过程,这个转换时间长短就会影响页面的白屏时间。
那么如何对 DNS 查询进行优化呢?根据 DNS 查询过程,我们可以从前端和客户端这两部分采取措施。
前端侧,可以通过在页面中加入 dns-prefetch
,在静态资源请求之前对域名进行解析,从而减少用户进入页面的等待时间。如下所示:
<meta http-equiv="x-dns-prefetch-control" content="on" /> |
其中第一行中的 x-dns-prefetch-control
表示开启 DNS
预解析功能,第二行 dns-prefetch
表示强制对 s.google.com
的域名做预解析。这样在 s.google.com
的资源请求开始前,DNS
解析完成,后续请求就不需要重复做解析了。不要小看这个标签哦,它可以为你减少 150ms
左右的 DNS 解析时间。
客户端侧呢?可以在启动 App 时,同步创建一个肉眼不可见的 WebView(例如 1*1 像素的 webview),将常用的静态资源路径写入这个 WebView 中,然后对它做域名解析并放入缓存中。这样后面需要使用 WebView 打开真正所需的页面时,由于已经做过域名解析了,客户端直接从缓存中获取即可。
当然如果是端外页面,因为没在 App 里面,就没法使用 1*1 WebView
的策略了,我们可以使用 iframe
,也能达到类似效果。
以上是一个轻量级的方案,通过它可以将 DNS 解析时间控制在 400ms
以内(这个算是比较快的)。如果你想要将耗时进一步压缩,比如控制在 200ms,此时就需要一个重量级的方案了。具体来说,可以采用 IP 直连方式,原来是请求 www.google.com
,现在我们通过调用 SDK 进行域名解析,拿到对应的 IP(如 6.6.6.6),然后直接请求这个 IP 地址拿到数据。
当然,这个实现起来需要避过许多坑,比如,HTTPS 证书和配置文件。
Https
证书是指当客户端使用 IP 直连时,请求 URL 中的 host 会被替换成对应的 IP,所以在证书验证时,会出现 domain 不匹配的情况,导致 SSL/TLS 握手不成功。
怎么解决呢?在非 SNI(Server Name Indication,表示单 IP多域名)的场景下,可以把证书验证环节独立出来 (如 Hook证书校验环节),然后将 IP 替换为原来的域名。在 SNI 场景下,可以定制 SSLSocketFactory
,在 createSocket 时替换为 IP,并进行 SNI/HostNameVerify 配置。
而配置文件方面,一般在域名只有两三个的情况时,我们可以用到它来做 IP 和域名的映射。但随着机房的扩大,每次扩机器都要升级配置文件,后续会非常麻烦。
对此我们可以采用 httpDNS 来解决。这是因为 httpDNS 可以准确调度到对应区域的服务器 IP 地址给用户,同时还可以避免运行商 DNS 劫持。具体来说, SDK 会通过发报文(类似系统向 DNS 运营商发的报文)向 httpDNS 做一个 HTTP 请求(也是通过 IP 直接请求),请求通过后拿到对应域名,然后进行 IP 直连,完成资源或者数据接口请求。
所谓首字符展示,通常我们会在页面加载过程中出现一个 loading 图,用来告诉用户页面内容需要加载,请耐心等待。但这样一个 loading 图既无法让用户感受到页面加载到什么程度,也无法给用户视觉上一个焦点,让人们的注意力集中在上面。
如何解决这个问题呢?我们可以使用骨架屏。骨架屏(Skeleton Screen)是指在页面数据加载完成前,先给用户展示出页面的大致结构(灰色占位图),告诉用户页面正在渐进式地加载中,然后在渲染出实际页面后,把这个结构替换掉。骨架屏并没有真正减少白屏时间,但是给了用户一个心理预期,让他可以感受到页面上大致有什么内容。
那么,如何构建骨架屏呢?因为考虑到每次视觉修改或者功能迭代,骨架屏都要配合修改,我建议采用自动化方案,而不是手动骨架屏方案(也就是自己编写骨架屏代码)。骨架屏的实现方法有以下三个步骤。
以上就是白屏时间优化方面相关的内容,但即便首屏展示比较快,如果有卡顿的现象,用户操作也会很不流畅,那怎么解决这个问题呢。下面我们就讲聊聊卡顿治理。
卡顿现象,一般可以通过用户反馈或性能平台来发现。比如我们接到用户说某页面比较卡,然后在性能平台上查看卡顿指标后,发现页面出现连续 5 帧超过 50ms ,这就属于严重卡顿。如何处理呢?
首先也还是问题的定位,先通过 charles 等工具抓包看一下数据接口,如果是和数据相关的问题,找后端同事,或者用数据缓存的方式解决。如果问题出在前端,一般和以下两种情形有关:浏览器的主线程与合成线程调度不合理,以及计算耗时操作
。
比如,在某电商 App 页面点击抽奖活动时,遇到一个红包移动的效果,在红包位置变化时,页面展现时特别卡,这就是主线程和合成线程调度的问题。怎么解决呢?
一般来说,主线程主要负责运行 JavaScript,计算 CSS 样式,元素布局,然后交给合成线程,合成线程主要负责绘制。当使用 height、width、margin、padding 等作为 transition 值时,会让主线程压力很大。此时我们可以使用 transform 来代替直接设置 margin 等操作。
比如红包元素从 margin-left:-10px 渲染到 margin-left:0,主线程需要计算样式 margin-left:-9px,margin-left:-8px,一直到 margin-left:0,每一次主线程计算样式后,合成线程都需要绘制到 GPU 再渲染到屏幕上,来来回回需要进行 10 次主线程渲染,10 次合成线程渲染,这给浏览器造成很大压力,从而出现卡顿。
如何解决呢?我们可以利用 transform 来做,比如 tranform:translate(-10px,0) 到 transform:translate(0,0),主线程只需要进行一次tranform:translate(-10px,0) 到 transform:translate(0,0),然后合成线程去一次将 -10px 转换到 0px。这样的话,总计 11 次计算,可以减少 9 步操作,假设一次 10ms,将减少 90ms。
除了主线程和合成线程调度不合理导致的卡顿,还有因为计算耗时过大导致的卡顿。遇到这类问题,一般有两种解法:空间换时间和时间换空间。
空间换时间方面,比如你需要频繁增加删除很多 DOM 元素,这时候一定会很卡,在对 DOM 元素增删的过程中最好先在 DocumentFragment (DOM文档碎片)上操作,而不是直接在 DOM上操作。只在最后一步操作完成后,将所有 DocumentFragment 的变动更新到 DOM上,从而解决频繁更新 DOM 带来的卡顿问题。
至于时间换空间,一般是通过将一个复杂的操作细分成一个队列,然后通过多次操作解决复杂操作的问题。
网站的性能怎么样。不能单单是靠某种工具去检测,就能得出的结果。因为影响它的因素有很多(dns解析、网络、缓存…)
Performance
是一个做前端性能监控离不开的API
,最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload
事件中读取各种数据。
1. 页面加载
一个页面的请求到响应再到显示出来,需要经过下面一些重要过程,当我们在浏览器输入一个
URL
或者说点击一个URL
开始,会出现如下流程
header
定义了重定向才会有这个过程,如果没有重定向,不会产生这个过程。app cache
:会先检查这个域名是否有缓存,如果有缓存就不需要DNS解析域名。这里的app
是值应用程序application
,不指手机app
。DNS
解析:把域名解析成IP
,如果直接用ip
地址访问,不产生这个过程。TCP
连接:http
协议是经过TCP
来传输的,所以产生一个http
请求就会有TCP connect
,但是依赖于长连接,不会产生这个过程。request header
:请求头信息。request body
:请求体信息,比如get
请求是没有请求体信息的,所以没有这个过程,这就是为什么把头跟体分开写的原因。response header
:响应头信息。response body
:响应体信息。HTML
结构JS
、css
都是外部加载的,当然有不正常的人啊,比如我。HTML DOM
树:这个过程可以去了解下DOM
树是怎样的就明白啦。2. 重定向分析
app cach
DNS
解析TCP
连接request header
app cach
DNS
解析TCP
连接request header
3. performance.timing
这个API能帮我们得到整个页面请求的时间,如下图,在
Chrome
的Console
是可以直接运行的
先解释下这些时间都是代表什么
timing 对象里边的数据比较多,梳理如下几个关键性的节点
fetchStart
:发起获取当前文档的时间点,我的理解是浏览器收到发起页面请求的时间点;domainLookupStart
:返回浏览器开始DNS
查询的时间,如果此请求没有DNS
查询过程,如长连接、资源cache
、甚至是本地资源等,那么就返回fetchStart
的值;domainLookupEnd
:返回浏览器结束DNS
查询的时间,如果没有DNS
查询过程,同上;connectStart
:浏览器向服务器请求文档,开始建立连接的时间,如果此连接是一个长连接,或者无需与服务器连接(命中缓存),则返回domainLookupEnd
的值;connectEnd
:浏览器向服务器请求文档,建立连接成功的时间;requestStart
:开始请求文档的时间(注意没有requestEnd
);responseStart
:浏览器开始接收第一个字节数据的时间,数据可能来自于服务器、缓存、或本地资源;unloadEventStart
:卸载上一个文档开始的时间;unloadEventEnd
:卸载上一个文档结束的时间;domLoading
:浏览器把document.readyState
设置为“loading”
的时间点,开始构建dom
树的时间点;responseEnd
:浏览器接收最后一个字节数据的时间,或连接被关闭的时间;domInteractive
:浏览器把document.readyState设
置为“interactive”
的时间点,DOM
树创建结束;domContentLoadedEventStart
:文档发生DOMContentLoaded
事件的时间;domContentLoadedEventEnd
:文档的DOMContentLoaded
事件结束的时间;domComplete
:浏览器把document.readyState
设置为“complete”
的时间点;loadEventStart
:文档触发load
事件的时间;loadEventEnd
:文档出发load
事件结束后的时间再来一张图,表示各阶段的开始与结束对应的时间
从以上的分析,我们就可以得到一些时间的计算
fetchStart - navigationStart
redirectEnd - redirectStart
App Cache
时间:domainLookupStart - fetchStart
DNS
解析时间:domainLookupEnd -domainLookupStart
TCP
连接时间:connectEnd - connectStart
request
时间:responseEnd - requestStart
这个计算是代表请求响应加起来的时间DOM
树加载:domInteractive -responseEnd
DOM
树,加载资源时间:domCompleter -domInteractive
load
时间:loadEventEnd - loadEventStart
loadEventEnd -navigationStart
responseStart-navigationStart
let timing = performance.timing |
4. performance.getEntries()
这个API能帮我们获得资源的请求时间,包括JS、CSS、图片等
如上图可以看到这个API请求返回的是一个数组,这个数组包括整个页面所有的资源加载,上图打开了一个其中一个资源,可以看到如下信息
entryType
:类型为resource
name
:资源的url
initiatorType
:资源是link
duration
的值,是responseEnd - startTime
得到的performance.memory
这个API主要是得到浏览器内存情况
jsHeapSizeLimit
:内存大小限制totalJSHeapSize
:可使用的内容userdJSHeapSize
:已使用的内容
userdJSHeapSize
表示所有被使用的JS堆栈内存,totalJSHeapSize
可使用的JS堆栈内存,如果userdJSHeapSize
的值大于totalJSHeapSize
,就可能出现内存泄漏
5. getEntriesByType
这个 API 可以让我们通过传入 type
获取一些相应的信息:
performance.mark
调用信息performance.measure
调用信息最后两个 type
是性能检测中获取指标的关键类型。当然你如果还想分析加载资源相关的信息的话,那可以多加上 resource
类型。
6. PerformanceObserver
PerformanceObserver
也是用来获取一些性能指标的 API,用法如下:
const perfObserver = new PerformanceObserver((entryList) => { |
结合
getEntriesByType
以及PerformanceObserver
,我们就能获取到所有需要的指标了。
js 运行时报错
为了更好的保证网站正常的运行,我们会通过
window.onerror
捕获,js具体的堆栈信息和错误行和列。一般我们的js都是打包压缩、混淆后上传到cdn的(无法定位到具体错误)。因此在打包时,同时生产.map
文件,用sourcemap
js库(nodejs)来还原具体错误信息
资源加载出错
为了防止加载资源失败,而导致网站打不开。一般我们会通过
window.addEventListener('error')
对资源加载进行监控。
后端api接口监控
一般对于小公司而言,可能连后端都很少会有接口方面的监控。一旦出现问题,却又不好排查问题,因此我们可以通过对浏览器底层的xhr对象进行拦截,上报相关调用数据和接口耗时。一方面可以检测到接口的实时调用情况,同时也方便后期对接口的数据统计。
用到 es(elasticsearch)
来对数据进行实时查询和分析。可是怎么把数据推到es里面呢?这对于前端同学来说,这又是一个难点。别急,“logstash” 了解一下。logstash主要对数据进行采集、分析、过滤的工具,然后推送到es里面。数据既然有了,那么怎么展示呢?这时候 Kibana 出来了,来作为数据展示的承托。这就是后端开源届的日志分析系统“ELK”。
其实对于数据的展示,可以不用kibana或者其他开源的产品进行展示,也可以自己通过es的restful接口,来搭建数据展示
在搭建前端监控系统开始之前,我们先来了解下整个系统的架构是怎么样的。因为你只有了解到流程是怎样走的,才清楚每个步骤我们该做些什么事情。
1. 监控js错误信息上报
通过window.onerror
方法,对页面js错误进行监控。上报具体的错误信息和堆栈信息(如下图),利用webpack/gulp打包出来的.map文件
,来解析出具体的错误信息。
nodejs通过上传.map文件,解析出具体错误信息。代码如下:
let sourceMap = require('source-map'); |
首先在管理后台,上传对应错误js的map文件,最终还原效果图如下:
通过上图,我们可以快速定位错误的具体位置,大大节省了排查错误的时间。
2. api接口调用请求上报
可能有些朋友会好奇,怎么去实时监听ajax异步接口请求情况呢?其实很简单:首先我们先通过代理XMLHttpRequest对象,来实现对ajax异步请求进行数据劫持。如下图:
通过上图不难发现,我们通过xhr对象的onreadystatechange
事件进行劫持,针对后端api接口返回的数据,进行处理上报接口的调用情况,因此达到实时监控接口调用情况,是不是觉得很神奇!
如有同学想了解这块内容具体的代码,github社区有开源的模块,可自行查看: https://github.com/wendux/Ajax-hook
根据需要统计哪些数据进行上报,比如:page query
参数、api接口请求数据…方便后续出错,定位问题。如下图:
3. 页面性能数据上报
关于页面性能数据,我们一般通过浏览器
performance.timingAPI
来进行上报
各阶段耗时图
关键性能指标图
4. 能支持自定义数据上报
上述一般都是自动进行错误采集进行上报,但有些情况是需要我们手动调用进行上报(自定义数据的埋点)。因此sdk的设计,最好支持手动调用上报。
let report = new Reort({ |
关于SDK设计的时候,需要注意的以下几点:
get
(image
方式上报,因为它避免的跨域的限制,目前也是业界主流的上报方式)get
和post
两种方式上报。因为有些复杂的数据结构,采用post上报,在服务端解析会比较方便。get
上报时,一般是在xx.gif
后带参数,一定要对参数进行转义,不然在某些场景下会报错!page
参数时,也要进行encodeURIComponent
进行转义,不然在某些场景下会报错!pid
sdk设计好了,上报数据也有了。现在我们开始设计服务怎么接收和处理数据。以Nodejs为例:
通过上图不难发现,我们通过接收到参数,然后通过decode解析出参数,并对参数进行处理。利用 nodejs
file-stream-rotator
这个库,把数据写入本地到日志中。(不过在写入日志的时候,记得对一些数据进行校检。
比如:不存在站点id,不给写入。对于外部网站的接口请求,不给写入。通过实战来看,有些情况,竟然会把UC浏览器部分请求给抓上来,所以需要过滤处理)
当数据写入日志文件中,通过Logstash监测日志变化,收集到数据并对数据进行格式化和过滤处理,并收集推送到ES(数据存储)。
比如利用geoip
和useragent
插件,分别对ip和浏览器的UA进行解析,分别得到对应的地理位置信息和浏览器相关距离信息。如下图:
关于logstash
的安装和配置,在这里不多说啦,可自行google安装配置。(建议使用docker版本,简单省事)
通过ES(elasticsearch)提供对应的restful api,手动搭建数据大盘(或者用kibana)。对于es数据管理,前期可以先安装一个es-head的插件,来查看数据情况。
关于修改
es header 5.0 request content-type
不对的情况。可以对/usr/src/app/_site/ vendor.js
文件进行修改。以docker镜像为例:
copy vendor.js
文件出来,如:docker cp elasticsearch-head:/usr/src/app/_site/vendor.js /Users/duanliang/logstash |
content-type
大约在6886行 |
docker cp /Users/duanliang/logstash/vendor.js elasticsearch-head:/usr/src/app/_site/vendor.js |
docker restart elasticsearch-head |
你可能用到的 docker 镜像地址:
es:docker pull docker.elastic.co/elasticsearch/elasticsearch:6.5.4 |
通过后台配置自定义报警规则,来告警开发人员。原理:通过nodejs启动定时任务来读取配置表,根据不同的规则去读取es里面的数据(一分钟查询一次),如果有错误,则通知报警。
最终以:首页 Dashboard为例
]]>插件的开发和使用自小程序基础库版本
1.9.6
开始支持
插件,是可被添加到小程序内直接使用的功能组件。开发者可以像开发小程序一样开发一个插件,供其他小程序使用。同时,小程序开发者可直接在小程序内使用插件,无需重复开发,为用户提供更丰富的服务
插件可以是
除了可以做这些方面还有很多很多,但小程序插件目前限制了开放范围及服务类目(开放类目)
开放范围:企业、媒体、政府及其他组织主体
开发者可选择当前小程序帐号已选类目中的一个,作为插件的服务类目。以下为当前已开放的插件服务类目,将逐步开放更多类目。
一级类目 | 二级类目 | 特殊说明 |
---|---|---|
快递业与邮政 | 所有二级类目 | |
医疗 | 就医服务、互联网医院 | 仅医疗类小程序可使用 |
政务民生 | 所有二级类目 | |
金融业 | 征信业务 | |
出行与交通 | 所有二级类目 | |
生活服务 | 票务、生活缴费 | |
IT科技 | 所有二级类目 | |
餐饮 | 点评与推荐、菜谱、餐厅排队、点餐平台、外卖平台 | |
旅游 | 所有二级类目 | |
文娱 | 视频、FM/电台、音乐、有声读物、动漫 | |
工具 | 记账、投票、日历、天气、备忘录、办公、字典、计算类、报价/比价、发票查询、企业管理 | |
电商平台 | 电商平台 | |
商业服务 | 招聘/求职 | |
汽车 | 所有二级类目 |
开发小程序插件的流程
小程序管理后台-小程序插件
小程序的 AppID 可以创建小程序插件项目,插件是独立于小程序之外的,但是 AppID 是公用的,所以不要使用原有的小程序项目进行插件开发
。 在创建项目页面,选择一个空文件夹作为项目路径,可以选择创建小程序插件快速启动模板
没有插件appId的,我们需要去申请一个,用来开发插件
miniprogram
目录:放置一个小程序,用于调试插件。plugin
文件就是小程序插件项目,用来编写小程序插件的代码。project.config.json
需要关注 compileType
字段,compileType == 'plugin'
时才能正常的使用插件项目。详情doc 目录
:插件文档必须放置在插件项目根目录中的 doc 目录下doc/README.md
,在 README.md
中引用到的图片资源不能是网络图片,且必须放在这个目录下。README.md
之后,可以在开发者工具左侧资源管理器的文件栏中右键单击README.md
,并选择上传文档。发布上传文档后,文档不会立刻发布。此时可以使用帐号和密码登录 管理后台 ,在 小程序插件 > 基本设置
中预览、发布插件文档总大小不能大于 2M
,超过时上传将返回错误码 80051一个插件可以包含若干个自定义组件、页面,和一组 js 接口。插件的目录内容如下
plugin |
提供给使用者小程序使用的自定义组件必须在配置文件的 publicComponents 段中列出
{ |
这个配置文件将向使用者小程序开放一个自定义组件 hello-component
,一个页面 hello-page
和 index.js
下导出的所有 js 接口。
插件没有体验版
。在开发版小程序中测试
miniprogram
下的代码当做使用插件的小程序代码,来进行插件的调试和测试需要将插件的代码放在实际运行的小程序中进行调试、测试
。此时,可以使用开发版的小程序直接引用开发版插件
。方法如下:新生成的 ID
;version
设置为 "version": "dev-[开发版 ID]"
的形式,如 "version": "dev-abcdef0123456789abcdef0123456789"
即可。如果开发版小程序引用了开发版插件,此时这个小程序就不能上传发布了。必须要将插件版本设为正式版本之后,小程序才可以正常上传、发布
注意事项:
每次上传插件所生成的 ID 不一定相同
,即使是同一个插件和同一个开发者,多次上传也可能会改变 ID
;“设置-第三方服务-插件管理”
中添加插件。开发者可登录小程序管理后台,通过 appid 查找插件并添加。如果插件无需申请,添加后可直接使用;否则需要申请并等待插件开发者通过后,方可在小程序中使用相应的插件。引入插件代码包
使用插件前,使用者要在 app.json
中声明需要使用的插件,例如
在主包中使用插件
{ |
plugins
定义段中可以包含多个插件声明,每个插件声明以一个使用者自定义的插件引用名作为标识,并指明插件的 appid 和需要使用的版本号。其中,引用名(如上例中的 myPlugin)由使用者自定义,无需和插件开发者保持一致或与开发者协调。在后续的插件使用中,该引用名将被用于表示该插件在分包内引入插件代码包
如果插件只在一个分包内用到,可以将插件仅放在这个分包内,例如:
{ |
在分包内使用插件有如下限制:
使用插件方式
使用插件时,插件的代码对于使用者来说是不可见的。阅读由插件开发者提供的插件开发文档,通过文档来明确插件提供的自定义组件、页面名称及提供的 js 接口规范等。
使用插件提供的自定义组件
plugin:// 协议指明插件的引用名和自定义组件名
,例如插件跳转到自身页面时, url 应设置为这样的形式:plugin-private://PLUGIN_APPID/PATH/TO/PAGE
。需要跳转到其他插件时,也可以这样设置 url 。
{ |
出于对插件的保护,插件提供的自定义组件在使用上有一定的限制:
this.selectComponent
接口无法获得插件的自定义组件实例对象;wx.createSelectorQuery
等接口的 >>> 选择器无法选入插件内部使用插件提供的页面
plugin:// 前缀
,形如 plugin://PLUGIN_NAME/PLUGIN_PAGE
<navigator url="plugin://myPlugin/hello-page"> |
js 接口
使用插件的 js 接口时,可以使用 requirePlugin
方法。例如,插件提供一个名为 hello 的方法和一个名为 world 的变量,则可以像下面这样调用:
var myPluginInterface = requirePlugin('myPlugin'); |
基础库 2.14.0
起,也可以通过插件的 AppID 来获取接口
,如:var myPluginInterface = requirePlugin('wxidxxxxxxxxxxxxxxxx')
使用插件的小程序可以导出内容到插件中共享
export
字段来指定一个文件"myPlugin": { |
为插件提供小程序自定义组件
在插件中可以将一部分区域交给使用的小程序来渲染
给插件名为 plugin-index
的页面中的抽象节点 mp-view
指定小程序的自定义组件 components/comp-from-miniprogram
作为实现的话
{ |
<!-- miniprogram/page/index.wxml --> |
小程序插件中不能使用API
wx.login | 登录 |
---|---|
wx.getUserInfo | 获取用户信息 |
wx.chooseAddress | 获取用户收货地址 |
wx.requestPayment | 【发起微信支付】 |
wx.addCard | 添加卡券 |
wx.openCard | 打开卡券 |
wx.saveFile | 保存文件 |
wx.getSavedFileList | 获取已保存的文件列表 |
wx.getSavedFileInfo | 获取已保存的文件信息 |
wx.removeSavedFile | 删除已保存的文件信息 |
wx.openDocument | 打开文件 |
wx.getStorageInfo | 获取本地缓存的相关信息 |
wx.getStorageInfoSync | 获取本地缓存的相关信息 |
wx.clearStorage | 清理本地数据缓存 |
wx.clearStorageSync | 清理本地数据缓存 |
wx.setNavigationBarTitle | 设置当前页面标题 |
wx.showNavigationBarLoading | 显示导航条加载动画 |
wx.hideNavigationBarLoading | 隐藏导航条加载动画 |
wx.navigateTo | 新窗口打开页面 |
wx.redirectTo | 原窗口打开页面 |
wx.switchTab | 切换到 tabbar 页面 |
wx.navigateBack | 退回上一个页面 |
wx.stopPullDownRefresh | 停止下拉刷新动画 |
在插件开发中,以下组件不能在插件页面中使用:
plugin-private://APPID 访问插件的自定义组件、页面
(暂不能使用 plugin:// )。requirePlugin
,但目前尚不能在文件一开头就使用 requirePlugin
,因为被依赖的插件可能还没有初始化,请考虑在更晚的时机调用 requirePlugin
,如接口被实际调用时、组件 attached 时插件功能页从小程序基础库版本 2.1.0
开始支持。
某些接口不能在插件中直接调用(如
wx.login
),但插件开发者可以使用插件功能页的方式来实现功能。目前,插件功能页包括
openid
和昵称等(相当于 wx.login
和 wx.getUserInfo
的功能),详见用户信息功能页wx.requestPayment
),详见支付功能页;wx.chooseAddress
),详见收货地址功能页。要使用插件功能页,需要先激活功能页特性,配置对应的功能页函数,再使用 functional-page-navigator 组件跳转到插件功能页,从而实现对应的功能
app.json
文件中添加 functionalPages
定义段{ |
功能页不能使用
wx.navigateTo
来进行跳转,而是需要一个名为functional-page-navigator
的组件。以获取用户信息为例,可以在插件中放置如下的functional-page-navigator
<functional-page-navigator name="loginAndGetUserInfo" args="" version="develop" bind:success="loginSuccess"> |
navigator
时,会自动跳转到插件所有者小程序的对应功能页。功能页会提示用户进行登录或其他相应的操作。操作结果会以组件事件的方式返回。functional-page-navigator
将没有任何反应注意:
functional-page-navigator
的version=develop
仅用于调试,因此在插件提审前,需要:
"functionalPages": true
的插件所有者小程序;functional-page-navigator
组件属性设置为 version="release"
1. 用户信息功能页
用户信息功能页用于帮助插件获取用户信息,包括 openid 和昵称等,相当于 wx.login 和 wx.getUserInfo 的功能。
此外,自基础库版本 2.3.1 起,用户在这个功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo
。无需再次进入功能页获取用户信息。自基础库版本 2.6.3 起,可以使用 wx.getSetting
来查询用户是否授权过
2. 支付功能页
wx.requestPayment
的功能。“小程序插件 -> 基本设置 -> 支付能力”
设置项中。另外,无论是否通过申请,主体为个人小程序在使用插件时,都无法正常使用插件里的支付功能。3. 收货地址功能页
收货地址功能页用于展示用户的收货地址列表,用户可以选择其中的收货地址。自基础库版本 2.4.0 开始支持。
]]>需要安装以下两个软件,搜索框直接搜索即可
cd wxappUnpacker |
选择压缩文件发送给好友保存到电脑上
使用adb命令快速拉取
查看当前是否有设备连接到
adb get-state
,如果没有连接到,查看下面adb connect
连接上模拟器即可使用adb pull
拉取模拟器的文件
主流安卓模拟器连接方式:
adb connect 127.0.0.1:62001
adb connect 127.0.0.1:21503
adb connect 127.0.0.1:6555
adb connect 127.0.0.1:53001
adb connect 127.0.0.1:7555
MacOS: adb connect 127.0.0.1:5555
adb connect 127.0.0.1:5555
adb connect <设备的IP地址>:5555
检查完上面步骤后,使用adb拉取到本地文件夹即可
adb pull /data/data/com.tencent.mm/MicroMsg/056e5e31623203e0efe74762a2584f40/appbrand/pkg
执行命令
# _-1215506245_427.wxapkg 主包 |
输出结果
node /Users/poetry/Downloads/小程序反编译工具/wuWxapkg.js |
命令格式: ./bingo.sh 分包.wxapkg -s=主包目录 |
$ ./bingo.sh 小程序压缩包/_1462998946_427.wxapkg -s=test |
node /Users/poetry/Downloads/小程序反编译工具/wuWxapkg.js |
将分包内容拷贝至主包相应目录
重复以上过程,把所有的分包都拷贝到主包对应的目录
注意
miniprogram_npm
包可能下载源码的时候没有完整合并,具体看报错提示处理