特色

Dcokerfile详解

引言

制作Docker image 有两种方式:一是使用 Docker container,直接构建容器,再导出成 image 使用;二是使用 Dockerfile,将所有动作写在文件中,再 build 成 image。因此本文将介绍Dockerfile配置文件的编写。

Dockerfile基本结构

# This my first nginx Dockerfile
# Version 1.0

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER tianfeiyu 

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  文件放在当前目录下,拷过去会自动解压
ADD nginx-1.8.0.tar.gz /usr/local/  
ADD epel-release-latest-7.noarch.rpm /usr/local/  

#RUN 执行以下命令 
RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm
RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre && yum clean all
RUN useradd -s /sbin/nologin -M www

#WORKDIR 相当于cd
WORKDIR /usr/local/nginx-1.8.0 

RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install

RUN echo "daemon off;" >> /etc/nginx.conf

#EXPOSE 映射端口
EXPOSE 80

#CMD 运行以下命令
CMD ["nginx"]

#开头的表示注释行,说明dockerfile中的指令

维护者的信息

镜像操作指令

容器操作指令

基础镜像信息

Dockerfile中常见的操作指令和作用

FROM:指定创建镜像的基础镜像

MAINTAINER:Dockerfile作者信息,一般写的是联系方式

RUN:运行Linux系统的命令使用

CMD:指定容器启动执行的命令;启动容器中的服务

LABEL:指定生成镜像的源数据标签

EXPOSE:指定镜像容器监听端口号;发布服务使用

ENV:使用环境变量

ADD:对压缩文件进行解压缩;将数据移动到指定的目录

COPY:复制宿主机数据到镜像内部使用

WORKDIR:切换到镜像容器中的指定目录中

VOLUME:挂载数据卷到镜像容器中

USER:指定运行容器的用户

ARG:指定镜像的版本号信息

ONBUILD:创建镜像,作为其他镜像的基础镜像运行操作指令

ENTRYPOINT:指定运行容器启动过程执行命令,覆盖CMD参数

1、FROM

指定基础镜像,必须为第一个命令

格式:
  FROM <image>
  FROM <image>:<tag>
  FROM <image>@<digest>
示例:
  FROM mysql:5.6
注:
  tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像

2、MAINTAINER

维护者信息

格式:
    MAINTAINER <name>
示例:
    MAINTAINER Jasper Xu
    MAINTAINER sorex@163.com
    MAINTAINER Jasper Xu <sorex@163.com>

3、RUN

构建镜像时执行的命令

RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:
shell执行
格式:
    RUN <command>
exec执行
格式:
    RUN ["executable", "param1", "param2"]
示例:
    RUN ["executable", "param1", "param2"]
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]
注:
  RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache

4、ADD

将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget

格式:
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
示例:
    ADD hom* /mydir/          # 添加所有以"hom"开头的文件
    ADD hom?.txt /mydir/      # ? 替代一个单字符,例如:"home.txt"
    ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
    ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/

5、COPY

功能类似ADD,但是是不会自动解压文件,也不能访问网络资源

6、CMD

构建容器后调用,也就是在容器启动时才进行调用。

格式:
    CMD ["executable","param1","param2"] (执行可执行文件,优先)
    CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
    CMD command param1 param2 (执行shell内部命令)
示例:
    CMD echo "This is a test." | wc -
    CMD ["/usr/bin/wc","--help"]
注:
   CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。

7、ENTRYPOINT

配置容器,使其可执行化。配合CMD可省去”application”,只使用参数。

格式:
    ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
    ENTRYPOINT command param1 param2 (shell内部命令)
示例:
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
注:
   ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。

8、LABEL

用于为镜像添加元数据

格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
  LABEL version="1.0" description="这是一个Web服务器" by="IT笔录"
注:
  使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

9、ENV

设置环境变量

格式:
    ENV <key> <value>  #<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
    ENV <key>=<value> ...  #可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
示例:
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat=fluffy

10、EXPOSE

指定于外界交互的端口

格式:
    EXPOSE <port> [<port>...]
示例:
    EXPOSE 80 443
    EXPOSE 8080
    EXPOSE 11211/tcp 11211/udp
注:
  EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口

11、VOLUME

用于指定持久化目录

格式:
    VOLUME ["/path/to/dir"]
示例:
    VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"
注:
  一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
1 卷可以容器间共享和重用
2 容器并不一定要和其它容器共享卷
3 修改卷后会立即生效
4 对卷的修改不会对镜像产生影响
5 卷会一直存在,直到没有任何容器在使用它

12、WORKDIR

工作目录,类似于cd命令

格式:
    WORKDIR /path/to/workdir
示例:
    WORKDIR /a  (这时工作目录为/a)
    WORKDIR b  (这时工作目录为/a/b)
    WORKDIR c  (这时工作目录为/a/b/c)
注:
  通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。

13、USER

指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户

格式:
  USER user
  USER user:group
  USER uid
  USER uid:gid
  USER user:gid
  USER uid:group
 示例:
  USER www
 注:
  使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。

14、ARG

用于指定传递给构建运行时的变量

格式:
    ARG <name>[=<default value>]
示例:
    ARG site
    ARG build_user=www

15、ONBUILD

用于设置镜像触发器

格式:
  ONBUILD [INSTRUCTION]
示例:
  ONBUILD ADD . /app/src
  ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:
  当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发
特色

Docker与Docker-Compose

简述两者的区别

  1. docker是自动化构建镜像,并启动镜像。 docker compose是自动化编排容器。
  2. docker是基于Dockerfile得到images,启动的时候是一个单独的container。
  3. docker-compose是基于docker-compose.yml,通常启动的时候是一个服务,这个服务通常由多个container共同组成,并且端口,配置等由docker-compose定义好。
  4. 两者都需要安装,但是要使用docker-compose,必须已经安装docker。

docker-compose的使用

docker-compose安装

pip安装方法(推介)

pip install docker-compose  -i  https://pypi.douban.com/simple

安装完成,版本检查

docker-compose version

官方包安装

访问https://github.com/docker/compose/releases,下载 docker-compose-Linux-x86_64,我是复制链接地址,在迅雷中下载的,下载后,将docker-compose-Linux-x86_64重命名为docker-compose,并上传到到服务器的的/usr/local/bin/目录下。

# 添加可执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 查看docker-compose版本
docker-compose -v

Docker-Compose生命周期

由于docker-compose是建立在docker的基础上的,因此docker-compose的生命周期与docker相似。

一句话的生命周期:编写docker-compose.yml,通过build建立服务镜像,start启动服务,pause/unpause暂停/恢复服务,restart重启服务,stop停止服务,rm停止容器服务并强制删除。


Docker-Compose常用命令

命令需在docker-compose.yml文件目录下进行,否则提示无法找到服务。

build创建服务

创建时必须在docker-compose.yml文件目录下进行。

#用来创建或重新创建服务使用的镜像

[root@localhost ~]

# docker-compose build 例如:docker-compose build service_a #创建一个镜像名叫service_a

pull拉取服务

除了使用docker-compose.yml建立镜像,也可以从云端拉取容器服务。

如果你docker-compose pull ServiceNamedocker-compose.yml定义服务的文件所在的目录下运行,Docker 会提取 postgres 镜像。例如,要在我们的示例中调用postgres配置为db服务的映像,您可以运行docker-compose pull db

提取与在docker-compose.ymldocker-stack.yml文件中定义的服务相关联的映像,但不会基于这些映像启动容器。

例如,假设您docker-compose.yml从 Quickstart:Compose 和 Rails 示例中获得此文件。

version: '2'services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:      - .:/myapp
    ports:      - "3000:3000"
    depends_on:      - db

如果你docker-compose pull ServiceNamedocker-compose.yml定义服务的文件所在的目录下运行,Docker 会提取 postgres 镜像。例如,要在我们的示例中调用postgres配置为db服务的映像,您可以运行docker-compose pull db

Usage: pull [options] [SERVICE...]
    Options:    
    --ignore-pull-failures  Pull what it can and ignores images with pull failures.    
    --parallel              Pull multiple images in parallel.    
    --quiet                 Pull without printing progress information
#用于拉取服务依赖的镜像

[root@localhost ~]

# docker-compose pull db

run一次性命令

Usage: run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
Options:    
  -d, --detach               Run container in background and print container ID  后台运行容器
  -e, --env list             Set environment variables                           设置环境变量
  --expose list              Expose a port or a range of ports                   暴露端口
  -i, --interactive          Keep STDIN open even if not attached                交互模式
  --name string              Assign a name to the container                      给当前运行的容器指定名字
  -p, --publish list         Publish a container's port(s) to the host           指定端口映射
  -P, --publish-all          Publish all exposed ports to random ports           随机指定暴露的端口映射
  -t, --tty                  Allocate a pseudo-TTY                               分配一个终端
  -v, --volume list          Bind mount a volume                                 挂载数据卷
  --volumes-from list        Mount volumes from the specified container(s)       从一个数据卷容器挂载目录
  -w, --workdir string       Working directory inside the container              指定容器中的工作目录

使用的命令run从具有由服务定义的配置的新容器中启动,包括卷,链接和其他详细信息。

run启动的区别:

  1. web服务配置以bash开头,则将其docker-compose run web python app.py覆盖python app.py
  2. docker-compose run命令不会创建服务配置中指定的任何端口。这可以防止端口与已打开的端口发生冲突。如果您确实想要创建服务的端口并将其映射到主机,请指定--service-ports标志:
docker-compose run --service-ports web python manage.py shell

或者,可以使用--publish-p选项,就像使用docker run

docker-compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell

如果启动使用链接配置的服务,则该run命令首先检查链接服务是否正在运行,并在服务停止时启动该服务。一旦所有链接的服务正在运行,run执行您通过它的命令。例如,您可以运行:

docker-compose run db psql -h db -U docker

这将为链接db容器打开一个交互式 PostgreSQL shell。

如果您不希望run命令启动链接容器,请使用--no-deps

docker-compose run --no-deps web python manage.py shell

start启动服务

Usage: start [SERVICE...]

启动指定服务的容器

pause暂停服务

Usage: pause [SERVICE...]

暂停服务,不可以与unpause一起使用。

stop停止服务

Usage: stop [options] [SERVICE...]
    Options:
        -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds (default: 10).

停止容器

restart重启服务

Usage: stop [options] [SERVICE...]
    Options:
        -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds (default: 10).

重启服务

up启动所有容器服务

Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]
Options:    -d              Detached mode: Run containers in the background,
                       print new container names.
                       Incompatible with --abort-on-container-exit.    
        --no-color          Produce monochrome output.    
        --no-deps           Don't start linked services.    
        --force-recreate       Recreate containers even if their configuration
                       and image haven't changed.
                       Incompatible with --no-recreate.    
        --no-recreate         If containers already exist, don't recreate them.
                       Incompatible with --force-recreate.    
        --no-build          Don't build an image, even if it's missing.    
        --build            Build images before starting containers.    
        --abort-on-container-exit   Stops all containers if any container was stopped.
                       Incompatible with -d.    
        -t, --timeout TIMEOUT    Use this timeout in seconds for container shutdown
                       when attached or when containers are already
                       running. (default: 10)    
        --remove-orphans       Remove containers for services not defined in
                       the Compose file    
        --exit-code-from SERVICE   Return the exit code of the selected service container.
                       Implies --abort-on-container-exit.    
        --scale SERVICE=NUM     Scale SERVICE to NUM instances. Overrides the `scale`
                       setting in the Compose file if present.

构建,(重新)创建,启动并附加到服务的容器。

docker-compose up

退出命令时,所有容器也停止。

docker-compose up -d

该命令将以后台启动所有容器服务。

容器服务后服务的配置或映像已更改的处理

docker-compose up -d --no-recreat

防止撰写选取更改

强制停止重启容器服务

docker-compose up -d --force-recreate

如果进程遇到错误,则此命令的退出代码为1

如果使用SIGINTctrl+ C)中断进程或者SIGTERM容器停止,并且退出代码为0

如果SIGINTSIGTERM在此关闭阶段再次发送,正在运行的容器将被终止,并且退出代码为2

logs服务日志查看

Usage: logs [options] [SERVICE...]
    Options:
    --no-color          Produce monochrome output.
    -f, --follow        Follow log output
    -t, --timestamps    Show timestamps
    --tail="all"        Number of lines to show from the end of the logs                    
                        for each container.

服务日志查看

down停止并删除容器

Usage: down [options]
Options:    
    --rmi type          Remove images. Type must be one of:                        
                        'all': Remove all images used by any service.                        
                        'local': Remove only images that don't have a custom tag                        
                        set by the `image` field.    
    -v, --volumes       Remove named volumes declared in the `volumes` section                        
                        of the Compose file and anonymous volumes
                        attached to containers.    
    --remove-orphans    Remove containers for services not defined in the
                        Compose file

停止容器并移除通过创建的容器,网络,卷和图像up

默认情况下,唯一删除的内容是:

  • 在撰写文件中定义的服务容器
  • 网络在networks撰写文件的部分中定义
  • 默认网络(如果使用的话)

定义为external的网络和卷从不删除。

rm删除停止的容器

Usage: rm [options] [SERVICE...]
Options:    
-f, --force   Don't ask to confirm removal    
-s, --stop    Stop the containers, if required, before removing    
-v            Remove any anonymous volumes attached to containers

删除已停止的服务容器。

默认情况下,附加到容器的匿名卷不会被删除。你可以用这个覆盖它-v。要列出所有卷,请使用docker volume ls

任何不在卷中的数据都将丢失。

运行没有选项的命令也会删除由docker-compose up或创建的一次性容器docker-compose run

$ docker-compose rm
Going to remove djangoquickstart_web_run_1
Are you sure? [yN] y
Removing djangoquickstart_web_run_1 ... done

create创建容器

为服务创建容器。

docker-compose create [options] [SERVICE…]
  --force-recreate:重新创建容器,即使配置和镜像没有改变,不兼容–no-recreate参数
  --no-recreate:如果容器已经存在,不需要重新创建,不兼容–force-recreate参数
  --no-build:不创建镜像,即使缺失
  --build:创建容器前,生成镜像

kill强制停止服务容器

通过发送SIGKILL信号来强制停止服务容器。

支持通过-s参数来指定发送的信号,例如通过如下指令发送SIGINT信号。

docker-compose kill [options] [SERVICE…]
	-s SIGNAL ,向容器发出信号。默认信号是SIGKILL。
 docker-compose kill -s SIGINT

ps显示在运行的服务

显示正在运行的服务

docker-compose ps [options] [SERVICE…]
  -q, --quiet ,只显示id
  --services  ,显示服务
  --filter KEY=VAL ,根据属性筛选服务
  -a, --all  ,显示所有已停止的容器(包括run命令创建的容器)

port暴露端口

docker-compose port [options] SERVICE PRIVATE_PORT
Options:
    --protocol=proto  tcp or udp [default: tcp]
    --index=index     index of the container if there are multiple
                      instances of a service [default: 1]

打印端口绑定的公共端口

例子:

docker-compose ps --services #找出服务名称

映射端口,并查询服务状态信息

ports暴露容器端口到主机的任意端口或指定端口,用法:

ports:

  – “80:80”         # 绑定容器的80端口到主机的80端口

  – “9000:80”       # 绑定容器的80端口到主机的9000端口

  – “443”           # 绑定容器的443端口到主机的任意端口,容器启动时随机分配绑定的主机端口号

不管是否指定主机端口,使用ports都会将端口暴露给主机和其他容器

配置中的expose暴露端口

expose暴露容器给link到当前容器的容器,或者暴露给同一个networks的容器,用法:

expose:

  – “3000”

  – “8000”

以上指令将当前容器的端口30008000暴露给其他容器

ports的区别是,expose不会将端口暴露给主机,主机无法访问expose的端口。


docker-compose.yml配置

docker-compose.yml是配置应用程序需要的所有服务。

Compose和Docker兼容性:

Compose 文件格式有3个版本,分别为1, 2.x 和 3.x

目前主流的为 3.x 其支持 docker 1.13.0 及其以上的版本

version: '2'
services:
  web:
    image: dockercloud/hello-world
    ports:
      - 8080
    networks:
      - front-tier
      - back-tier
 
  redis:
    image: redis
    links:
      - web
    networks:
      - back-tier
 
  lb:
    image: dockercloud/haproxy
    ports:
      - 80:80
    links:
      - web
    networks:
      - front-tier
      - back-tier
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock 
 
networks:
  front-tier:
    driver: bridge
  back-tier:
driver: bridge

标准配置文件应该包含 version、services、networks 三大部分,其中最关键的就是 services 和 networks 两个部分,下面先来看 services 的书写规则。

1. image

1. services:
2. web:
3. image: hello-world

在 services 标签下的第二级标签是 web,这个名字是用户自己自定义,它就是服务名称。

image 则是指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。

例如下面这些格式都是可以的:

1. image: redis
2. image: ubuntu:14.04
3. image: tutum/influxdb
4. image: example-registry.com:4000/postgresql
5. image: a4bc65fd

2. build

服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。

1. build: /path/to/build/dir
2.

也可以是相对路径,只要上下文确定就可以读取到 Dockerfile。

build: ./dir

设定上下文根目录,然后以该目录为准指定 Dockerfile。

1. build:
2.   context: ../
3.   dockerfile: path/of/Dockerfile
4.

注意 build 都是一个目录,如果你要指定 Dockerfile 文件需要在 build 标签的子级标签中使用 dockerfile 标签指定,如上面的例子。

如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。

1. build: ./dir
2. image: webapp:tag

既然可以在 docker-compose.yml 中定义构建任务,那么一定少不了 arg 这个标签,就像 Dockerfile 中的 ARG 指令,它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:

1. build:
2. context: .
3. args:
4. buildno: 1
5. password: secret

下面这种写法也是支持的,一般来说下面的写法更适合阅读。

1. build:
2. context: .
3. args:
4. - buildno=1
5. - password=secret

与 ENV 不同的是,ARG 是允许空值的。例如:

1. args:
2.   - buildno
3.   - password

这样构建过程可以向它们赋值。

注意:YAML 的布尔值(true, false, yes, no, on, off)必须要使用引号引起来(单引号、双引号均可),否则会当成字符串解析。

3. command

使用 command 可以覆盖容器启动后默认执行的命令。

command: bundle exec thin -p 3000

也可以写成类似 Dockerfile 中的格式:

command: [bundle, exec, thin, -p, 3000]

4.container_name

前面说过 Compose 的容器名称格式是:<项目名称><服务名称><序号>

虽然可以自定义项目名称、服务名称,但是如果你想完全控制容器的命名,可以使用这个标签指定:

container_name: app

这样容器的名字就指定为 app 了。

5.depends_on

在使用 Compose 时,最大的好处就是少打启动命令,但是一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。

例如在没启动数据库容器的时候启动了应用容器,这时候应用容器会因为找不到数据库而退出,为了避免这种情况我们需要加入一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。

例如下面容器会先启动 redis 和 db 两个服务,最后才启动 web 服务:

1. version: '2'
2. services:
3.   web:
4.     build: .
5.     depends_on:
6.       - db
7.       - redis
8.   redis:
9.     image: redis
10.   db:
11.     image: postgres

注意的是,默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redis 和 db 两个服务,因为在配置文件中定义了依赖关系。

6.dns

和 –dns 参数一样用途,格式如下:

1. dns: 8.8.8.8
2.

也可以是一个列表:

1. dns:
2.   - 8.8.8.8
3.   - 9.9.9.9
4.

此外 dns_search 的配置也类似:

1. dns_search: example.com
2. dns_search:
3.   - dc1.example.com
4.   - dc2.example.com
5.

7. tmpfs

挂载临时目录到容器内部,与 run 的参数一样效果:

1. tmpfs: /run
2. tmpfs:
3.   - /run
4.   - /tmp

8. entrypoint

在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点,第四章有对比过与 CMD 的区别。

在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义:

1. entrypoint: /code/entrypoint.sh
2.

格式和 Docker 类似,不过还可以写成这样:

1. entrypoint:
2.     - php
3.     - -d
4.     - zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
5.     - -d
6.     - memory_limit=-1
7.     - vendor/bin/phpunit
8.

9.env_file

还记得前面提到的 .env 文件吧,这个文件可以设置 Compose 的变量。而在 docker-compose.yml 中可以定义一个专门存放变量的文件。

如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中路径会使用配置文件路径。

如果有变量名称与 environment 指令冲突,则以后者为准。格式如下:

1. env_file: .env
2.

或者根据 docker-compose.yml 设置多个:

1. env_file:
2.   - ./common.env
3.   - ./apps/web.env
4.   - /opt/secrets.env
5.

注意的是这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中使用变量还是首选前面刚讲的 arg 标签。

10. environment

与上面的 env_file 标签完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同。

一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果。

1. environment:
2.   RACK_ENV: development
3.   SHOW: 'true'
4.   SESSION_SECRET:
5. 
6. environment:
7.   - RACK_ENV=development
8.   - SHOW=true
9.   - SESSION_SECRET

11. expose

这个标签与Dockerfile中的EXPOSE指令一样,用于指定暴露的端口,但是只是作为一种参考,实际上docker-compose.yml的端口映射还得ports这样的标签。

1. expose:
2.  - "3000"
3.  - "8000"

12. external_links

在使用Docker过程中,我们会有许多单独使用docker run启动的容器,为了使Compose能够连接这些不在docker-compose.yml中定义的容器,我们需要一个特殊的标签,就是external_links,它可以让Compose项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。

格式如下:

1. external_links:
2.  - redis_1
3.  - project_db_1:mysql
4.  - project_db_1:postgresql
5.

13. extra_hosts

添加主机名的标签,就是往/etc/hosts文件中添加一些记录,与Docker client的–add-host类似:

1. extra_hosts:
2.  - "somehost:162.242.195.82"
3.  - "otherhost:50.31.209.229"

启动之后查看容器内部hosts:

1. 162.242.195.82  somehost
2. 50.31.209.229   otherhost
3.

14. labels

向容器添加元数据,和Dockerfile的LABEL指令一个意思,格式如下:

1. labels:
2.   com.example.description: "Accounting webapp"
3.   com.example.department: "Finance"
4.   com.example.label-with-empty-value: ""
5. labels:
6.   - "com.example.description=Accounting webapp"
7.   - "com.example.department=Finance"
8.   - "com.example.label-with-empty-value"
9.

15. links

还记得上面的depends_on吧,那个标签解决的是启动顺序问题,这个标签解决的是容器连接问题,与Docker client的–link一样效果,会连接到其它服务中的容器。

格式如下:

1. links:
2.  - db
3.  - db:database
4.  - redis
5.

使用的别名将会自动在服务容器中的/etc/hosts里创建。例如:

1. 172.12.2.186  db
2. 172.12.2.186  database
3. 172.12.2.187  redis
4.

相应的环境变量也将被创建。

16. logging

这个标签用于配置日志服务。格式如下:

1. logging:
2.   driver: syslog
3.   options:
4.     syslog-address: "tcp://192.168.0.42:123"

默认的driver是json-file。只有json-file和journald可以通过docker-compose logs显示日志,其他方式有其他日志查看方式,但目前Compose不支持。对于可选值可以使用options指定。

有关更多这方面的信息可以阅读官方文档:

https://docs.docker.com/engine/admin/logging/overview/

17. pid

pid: "host"

将PID模式设置为主机PID模式,跟主机系统共享进程命名空间。容器使用这个标签将能够访问和操纵其他容器和宿主机的名称空间。

18. ports

映射端口的标签。

使用HOST:CONTAINER格式或者只是指定容器的端口,宿主机会随机映射端口。

1. ports:
2.  - "3000"
3.  - "8000:8000"
4.  - "49100:22"
5.  - "127.0.0.1:8001:8001"
6.

注意:当使用HOST:CONTAINER格式来映射端口时,如果你使用的容器端口小于60你可能会得到错误得结果,因为YAML将会解析xx:yy这种数字格式为60进制。所以建议采用字符串格式。

19. security_opt

为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签。比如设置全部服务的user标签值为USER。

1. security_opt:
2.   - label:user:USER
3.   - label:role:ROLE
4.

20. stop_signal

设置另一个信号来停止容器。在默认情况下使用的是SIGTERM停止容器。设置另一个信号可以使用stop_signal标签。

stop_signal: SIGUSR1

21. volumes

挂载一个目录或者一个已存在的数据卷容器,可以直接使用 [HOST:CONTAINER] 这样的格式,或者使用 [HOST:CONTAINER:ro] 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。

Compose的数据卷指定路径可以是相对路径,使用 . 或者 .. 来指定相对目录。

数据卷的格式可以是下面多种形式:

1. volumes:
2.   // 只是指定一个路径,Docker 会自动在创建一个数据卷(这个路径是容器内部的)。
3.   - /var/lib/mysql
4.  
5.   // 使用绝对路径挂载数据卷
6.   - /opt/data:/var/lib/mysql
7.  
8.   // 以 Compose 配置文件为中心的相对路径作为数据卷挂载到容器。
9.   - ./cache:/tmp/cache
10.  
11.   // 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
12.   - ~/configs:/etc/configs/:ro
13.  
14.   // 已经存在的命名的数据卷。
15.   - datavolume:/var/lib/mysql
16.

如果你不使用宿主机的路径,你可以指定一个volume_driver。

volume_driver: mydriver

22. volumes_from

从其它容器或者服务挂载数据卷,可选的参数是 :ro或者 :rw,前者表示容器只读,后者表示容器对数据卷是可读可写的。默认情况下是可读可写的。

1. volumes_from:
2.   - service_name
3.   - service_name:ro
4.   - container:container_name
5.   - container:container_name:rw
6.

23. cap_add, cap_drop

添加或删除容器的内核功能。详细信息在前面容器章节有讲解,此处不再赘述。

1. cap_add:
2.   - ALL
3. 
4. cap_drop:
5.   - NET_ADMIN
6.   - SYS_ADMIN

24. cgroup_parent

指定一个容器的父级cgroup。

cgroup_parent: m-executor-abcd

25. devices

设备映射列表。与Docker client的–device参数类似。

1. devices:
2.   - "/dev/ttyUSB0:/dev/ttyUSB0"

26. extends

这个标签可以扩展另一个服务,扩展内容可以是来自在当前文件,也可以是来自其他文件,相同服务的情况下,后来者会有选择地覆盖原有配置。

1. extends:
2.   file: common.yml
3.   service: webapp
4.

用户可以在任何地方使用这个标签,只要标签内容包含file和service两个值就可以了。file的值可以是相对或者绝对路径,如果不指定file的值,那么Compose会读取当前YML文件的信息。

更多的操作细节在后面的12.3.4小节有介绍。

27. network_mode

网络模式,与Docker client的–net参数类似,只是相对多了一个service:[service name] 的格式。

例如:

1. network_mode: "bridge"
2. network_mode: "host"
3. network_mode: "none"
4. network_mode: "service:[service name]"
5. network_mode: "container:[container name/id]"

可以指定使用服务或者容器的网络。

28. networks

加入指定网络,格式如下:

1. services:
2. some-service:
3. networks:
4.      - some-network
5.      - other-network

关于这个标签还有一个特别的子标签aliases,这是一个用来设置服务别名的标签,例如:

1. services:
2. some-service:
3. networks:
4. some-network:
5. aliases:
6.          - alias1
7.          - alias3
8. other-network:
9. aliases:
10.          - alias2

相同的服务可以在不同的网络有不同的别名。

29. 其它

还有这些标签:cpu_shares, cpu_quota, cpuset, domainname, hostname, ipc, mac_address, mem_limit, memswap_limit, privileged, read_only, restart, shm_size, stdin_open, tty, user, working_dir

上面这些都是一个单值的标签,类似于使用docker run的效果。

cpu_shares: 73
cpu_quota: 50000
cpuset: 0,1
 
user: postgresql
working_dir: /code
 
domainname: foo.com
hostname: foo
ipc: host
mac_address: 02:42:ac:11:65:43
 
mem_limit: 1000000000
memswap_limit: 2000000000
privileged: true
 
restart: always
 
read_only: true
shm_size: 64M
stdin_open: true
tty: true

JavaScript(不可逆加密)近似解密

前言


目前,我们日常浏览的网站中,日常会有防止网站被黑客克隆网站、爬取网站、压缩代码等的需求,都会使用一些方法对源码进行处理。其中,有的使用Webpack对JavaScript代码进行打包,也有使用js代码加密的方法。因此,本篇文章将会对js代码加密进行一个简单的分析,并主要针对不可逆加密提供一些常用的近似解密思路。

JavaScript加密分析


首先,我们能确定的一件事是,无论加密与否,加载的js文件都能够在浏览器其中执行,并得到对应的结果。

有的思路是根据一些参数特殊处理进行加密,也有的是跟后端达成协议,进行加解密的方式得出结果并执行。但是,后者的方案会对我们的服务造成额外的压力,因此使用前者更为主流。因此本篇文章只讨论前者这种的方案。
近似解密的方案是基于代码逻辑正确、提高可读性的方向进行的。

这里列举了一些加密方式(可逆的):

  • 最简单的加密解密
  • 转义字符””的妙用
  • 使用Microsoft出品的脚本编码器Script Encoder来进行编码
  • 任意添加NUL空字符(十六进制00H)
  • 无用内容混乱以及换行空格TAB大法
  • 自写解密函数法
    对应的加解密思路可以看这里,我就不一一列举了

不可逆的加密方式:

根据各种指定的参数混淆+不可逆的算法进行加密https://www.jsjiami.com/
但是,即使是不可逆加密过后,得出的结果也是能正常执行的,符合JavaScript的语法的,因此,我们就可以就此对源码进行翻译。

举个栗子并分享一个思路

举个栗子

var __encode = 'jsjiami.com', _a = {}, _0xb483 = ["\x5F\x64\x65\x63\x6F\x64\x65", "\x68\x74\x74\x70\x3A\x2F\x2F\x77\x77\x77\x2E\x73\x6F\x6A\x73\x6F\x6E\x2E\x63\x6F\x6D\x2F\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x6F\x62\x66\x75\x73\x63\x61\x74\x6F\x72\x2E\x68\x74\x6D\x6C"]; (function (_0xd642x1) { _0xd642x1[_0xb483[0]] = _0xb483[1] })(_a); var __Oxb24bc = ["\x6C\x69\x74\x65\x2D\x61\x6E\x64\x72\x6F\x69\x64\x26", "\x73\x74\x72\x69\x6E\x67\x69\x66\x79", "\x26\x61\x6E\x64\x72\x6F\x69\x64\x26\x33\x2E\x31\x2E\x30\x26", "\x26", "\x26\x38\x34\x36\x63\x34\x63\x33\x32\x64\x61\x65\x39\x31\x30\x65\x66", "\x31\x32\x61\x65\x61\x36\x35\x38\x66\x37\x36\x65\x34\x35\x33\x66\x61\x66\x38\x30\x33\x64\x31\x35\x63\x34\x30\x61\x37\x32\x65\x30", "\x69\x73\x4E\x6F\x64\x65", "\x63\x72\x79\x70\x74\x6F\x2D\x6A\x73", "", "\x61\x70\x69\x3F\x66\x75\x6E\x63\x74\x69\x6F\x6E\x49\x64\x3D", "\x26\x62\x6F\x64\x79\x3D", "\x26\x61\x70\x70\x69\x64\x3D\x6C\x69\x74\x65\x2D\x61\x6E\x64\x72\x6F\x69\x64\x26\x63\x6C\x69\x65\x6E\x74\x3D\x61\x6E\x64\x72\x6F\x69\x64\x26\x75\x75\x69\x64\x3D\x38\x34\x36\x63\x34\x63\x33\x32\x64\x61\x65\x39\x31\x30\x65\x66\x26\x63\x6C\x69\x65\x6E\x74\x56\x65\x72\x73\x69\x6F\x6E\x3D\x33\x2E\x31\x2E\x30\x26\x74\x3D", "\x26\x73\x69\x67\x6E\x3D", "\x61\x70\x69\x2E\x6D\x2E\x6A\x64\x2E\x63\x6F\x6D", "\x2A\x2F\x2A", "\x52\x4E", "\x4A\x44\x4D\x6F\x62\x69\x6C\x65\x4C\x69\x74\x65\x2F\x33\x2E\x31\x2E\x30\x20\x28\x69\x50\x61\x64\x3B\x20\x69\x4F\x53\x20\x31\x34\x2E\x34\x3B\x20\x53\x63\x61\x6C\x65\x2F\x32\x2E\x30\x30\x29", "\x7A\x68\x2D\x48\x61\x6E\x73\x2D\x43\x4E\x3B\x71\x3D\x31\x2C\x20\x6A\x61\x2D\x43\x4E\x3B\x71\x3D\x30\x2E\x39", "\x75\x6E\x64\x65\x66\x69\x6E\x65\x64", "\x6C\x6F\x67", "\u5220\u9664", "\u7248\u672C\u53F7\uFF0C\x6A\x73\u4F1A\u5B9A", "\u671F\u5F39\u7A97\uFF0C", "\u8FD8\u8BF7\u652F\u6301\u6211\u4EEC\u7684\u5DE5\u4F5C", "\x6A\x73\x6A\x69\x61", "\x6D\x69\x2E\x63\x6F\x6D"]; function taskUrl(_0x7683x2, _0x7683x3 = {}) { let _0x7683x4 = + new Date(); let _0x7683x5 = `${__Oxb24bc[0x0]}${JSON[__Oxb24bc[0x1]](_0x7683x3)}${__Oxb24bc[0x2]}${_0x7683x2}${__Oxb24bc[0x3]}${_0x7683x4}${__Oxb24bc[0x4]}`; let _0x7683x6 = __Oxb24bc[0x5]; const _0x7683x7 = $[__Oxb24bc[0x6]]() ? require(__Oxb24bc[0x7]) : CryptoJS; let _0x7683x8 = _0x7683x7.HmacSHA256(_0x7683x5, _0x7683x6).toString(); return { url: `${__Oxb24bc[0x8]}${JD_API_HOST}${__Oxb24bc[0x9]}${_0x7683x2}${__Oxb24bc[0xa]}${escape(JSON[__Oxb24bc[0x1]](_0x7683x3))}${__Oxb24bc[0xb]}${_0x7683x4}${__Oxb24bc[0xc]}${_0x7683x8}${__Oxb24bc[0x8]}`, headers: { '\x48\x6F\x73\x74': __Oxb24bc[0xd], '\x61\x63\x63\x65\x70\x74': __Oxb24bc[0xe], '\x6B\x65\x72\x6E\x65\x6C\x70\x6C\x61\x74\x66\x6F\x72\x6D': __Oxb24bc[0xf], '\x75\x73\x65\x72\x2D\x61\x67\x65\x6E\x74': __Oxb24bc[0x10], '\x61\x63\x63\x65\x70\x74\x2D\x6C\x61\x6E\x67\x75\x61\x67\x65': __Oxb24bc[0x11], '\x43\x6F\x6F\x6B\x69\x65': cookie } } } (function (_0x7683x9, _0x7683xa, _0x7683xb, _0x7683xc, _0x7683xd, _0x7683xe) { _0x7683xe = __Oxb24bc[0x12]; _0x7683xc = function (_0x7683xf) { if (typeof alert !== _0x7683xe) { alert(_0x7683xf) }; if (typeof console !== _0x7683xe) { console[__Oxb24bc[0x13]](_0x7683xf) } }; _0x7683xb = function (_0x7683x7, _0x7683x9) { return _0x7683x7 + _0x7683x9 }; _0x7683xd = _0x7683xb(__Oxb24bc[0x14], _0x7683xb(_0x7683xb(__Oxb24bc[0x15], __Oxb24bc[0x16]), __Oxb24bc[0x17])); try { _0x7683x9 = __encode; if (!(typeof _0x7683x9 !== _0x7683xe && _0x7683x9 === _0x7683xb(__Oxb24bc[0x18], __Oxb24bc[0x19]))) { _0x7683xc(_0x7683xd) } } catch (e) { _0x7683xc(_0x7683xd) } })({})

首先

看到上面的内容,分析得出里面包含着\x5F\x64\x65\x63\x6F\x64\x65\u5220\u9664这些特殊字符串,我们可以得出,里面的混淆会是进行过unicode和16进制的转换得出的,因此,我们可以先对此先进行一次转换,最终得出的结果是

var __encode = 'jsjiami.com',
    _a = {},
    _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function(_0xd642x1) {
    _0xd642x1[_0xb483[0]] = _0xb483[1]
})(_a);
var __Oxb24bc = ["lite-android&", "stringify", "&android&3.1.0&", "&", "&846c4c32dae910ef", "12aea658f76e453faf803d15c40a72e0", "isNode", "crypto-js", "", "api?functionId=", "&body=", "&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=", "&sign=", "api.m.jd.com", "*/*", "RN", "JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)", "zh-Hans-CN;q=1, ja-CN;q=0.9", "undefined", "log", "删除", "版本号,js会定", "期弹窗,", "还请支持我们的工作", "jsjia", "mi.com"];

function taskUrl(_0x7683x2, _0x7683x3 = {}) {
    let _0x7683x4 = +new Date();
    let _0x7683x5 = `${__Oxb24bc[0x0]}${JSON[__Oxb24bc[0x1]](_0x7683x3)}${__Oxb24bc[0x2]}${_0x7683x2}${__Oxb24bc[0x3]}${_0x7683x4}${__Oxb24bc[0x4]}`;
    let _0x7683x6 = __Oxb24bc[0x5];
    const _0x7683x7 = $[__Oxb24bc[0x6]]() ? require(__Oxb24bc[0x7]) : CryptoJS;
    let _0x7683x8 = _0x7683x7.HmacSHA256(_0x7683x5, _0x7683x6).toString();
    return {
        url: `${__Oxb24bc[0x8]}${JD_API_HOST}${__Oxb24bc[0x9]}${_0x7683x2}${__Oxb24bc[0xa]}${escape(JSON[__Oxb24bc[0x1]](_0x7683x3))}${__Oxb24bc[0xb]}${_0x7683x4}${__Oxb24bc[0xc]}${_0x7683x8}${__Oxb24bc[0x8]}`,
        headers: {
            'Host': __Oxb24bc[0xd],
            'accept': __Oxb24bc[0xe],
            'kernelplatform': __Oxb24bc[0xf],
            'user-agent': __Oxb24bc[0x10],
            'accept-language': __Oxb24bc[0x11],
            'Cookie': cookie
        }
    }
}(function(_0x7683x9, _0x7683xa, _0x7683xb, _0x7683xc, _0x7683xd, _0x7683xe) {
    _0x7683xe = __Oxb24bc[0x12];
    _0x7683xc = function(_0x7683xf) {
        if (typeof alert !== _0x7683xe) {
            alert(_0x7683xf)
        };
        if (typeof console !== _0x7683xe) {
            console[__Oxb24bc[0x13]](_0x7683xf)
        }
    };
    _0x7683xb = function(_0x7683x7, _0x7683x9) {
        return _0x7683x7 + _0x7683x9
    };
    _0x7683xd = _0x7683xb(__Oxb24bc[0x14], _0x7683xb(_0x7683xb(__Oxb24bc[0x15], __Oxb24bc[0x16]), __Oxb24bc[0x17]));
    try {
        _0x7683x9 = __encode;
        if (!(typeof _0x7683x9 !== _0x7683xe && _0x7683x9 === _0x7683xb(__Oxb24bc[0x18], __Oxb24bc[0x19]))) {
            _0x7683xc(_0x7683xd)
        }
    } catch (e) {
        _0x7683xc(_0x7683xd)
    }
})({})

到此

我们大致上已把对应的内容简单地解密出来了。但是,目前而言,源码的可读性比较差,我们可以再观察一下。可以发现,在开头已定义了__Oxb24bc这个数组变量,文中的部分变量也使用到数组中的内容,因此,我们可以根据这样转化一波。可以得出一下:

var __encode = 'jsjiami.com',
  _a = {},
  _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function (_0xd642x1) {
  _0xd642x1["_decode"] = "http://www.sojson.com/javascriptobfuscator.html"
})(_a);
var __Oxb24bc = ["lite-android&", "stringify", "&android&3.1.0&", "&", "&846c4c32dae910ef", "12aea658f76e453faf803d15c40a72e0", "isNode", "crypto-js", "", "api?functionId=", "&body=", "&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=", "&sign=", "api.m.jd.com", "*/*", "RN", "JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)", "zh-Hans-CN;q=1, ja-CN;q=0.9", "undefined", "log", "删除", "版本号,js会定", "期弹窗,", "还请支持我们的工作", "jsjia", "mi.com"];

function taskUrl(_0x7683x2, _0x7683x3 = {}) {
  let _0x7683x4 = +new Date();
  let _0x7683x5 = `lite-android&${JSON.stringify(_0x7683x3)}&android&3.1.0&${_0x7683x2}&${_0x7683x4}&846c4c32dae910ef`;
  let _0x7683x6 = "12aea658f76e453faf803d15c40a72e0";
  const _0x7683x7 = $.isNode() ? require("crypto-js") : CryptoJS;
  let _0x7683x8 = _0x7683x7.HmacSHA256(_0x7683x5, _0x7683x6).toString();
  return {
    url: ""+`${JD_API_HOST}api?functionId=${_0x7683x2}&body=${escape(JSON.stringify(_0x7683x3))}&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=${_0x7683x4}&sign=${_0x7683x8}`+"",
    headers: {
      'Host': 'api.m.jd.com',
      'accept': '*/*',
      'kernelplatform': 'RN',
      'user-agent': 'JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)',
      'accept-language': 'zh-Hans-CN;q=1, ja-CN;q=0.9',
      'Cookie': cookie
    }
  }
}
(function (_0x7683x9, _0x7683xa, _0x7683xb, _0x7683xc, _0x7683xd, _0x7683xe) {
  _0x7683xe = 'undefined';
  _0x7683xc = function (_0x7683xf) {
    if (typeof alert !== _0x7683xe) {
      alert(_0x7683xf)
    };
    console.log(console)
    if (typeof console !== _0x7683xe) {
      console.log(_0x7683xf)
    }
  };
  _0x7683xb = function (_0x7683x7, _0x7683x9) {
    return _0x7683x7 + _0x7683x9
  };
  _0x7683xd = _0x7683xb('删除', _0x7683xb(_0x7683xb('版本号,js会定', '期弹窗,'), '还请支持我们的工作'));
  try {
    _0x7683x9 = __encode;
    if (!(typeof _0x7683x9 !== _0x7683xe && _0x7683x9 === _0x7683xb('jsjia', 'mi.com'))) {
      _0x7683xc(_0x7683xd)
    }
  } catch (e) {
    _0x7683xc(_0x7683xd)
  }
})({})

其中,我们可以看到_0x7683xb这个方法,其实实际上处理的操作是在做字符串拼接,对此可以简化整理一下,并得出:

var __encode = 'jsjiami.com',
  _a = {},
  _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function (_0xd642x1) {
  _0xd642x1["_decode"] = "http://www.sojson.com/javascriptobfuscator.html"
})(_a);
var __Oxb24bc = ["lite-android&", "stringify", "&android&3.1.0&", "&", "&846c4c32dae910ef", "12aea658f76e453faf803d15c40a72e0", "isNode", "crypto-js", "", "api?functionId=", "&body=", "&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=", "&sign=", "api.m.jd.com", "*/*", "RN", "JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)", "zh-Hans-CN;q=1, ja-CN;q=0.9", "undefined", "log", "删除", "版本号,js会定", "期弹窗,", "还请支持我们的工作", "jsjia", "mi.com"];

function taskUrl(_0x7683x2, _0x7683x3 = {}) {
  let _0x7683x4 = +new Date();
  let _0x7683x5 = `lite-android&${JSON.stringify(_0x7683x3)}&android&3.1.0&${_0x7683x2}&${_0x7683x4}&846c4c32dae910ef`;
  let _0x7683x6 = "12aea658f76e453faf803d15c40a72e0";
  const _0x7683x7 = $.isNode() ? require("crypto-js") : CryptoJS;
  let _0x7683x8 = _0x7683x7.HmacSHA256(_0x7683x5, _0x7683x6).toString();
  return {
    url: ""+`${JD_API_HOST}api?functionId=${_0x7683x2}&body=${escape(JSON.stringify(_0x7683x3))}&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=${_0x7683x4}&sign=${_0x7683x8}`+"",
    headers: {
      'Host': 'api.m.jd.com',
      'accept': '*/*',
      'kernelplatform': 'RN',
      'user-agent': 'JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)',
      'accept-language': 'zh-Hans-CN;q=1, ja-CN;q=0.9',
      'Cookie': cookie
    }
  }
}
(function (_0x7683x9, _0x7683xa, _0x7683xc, _0x7683xd, _0x7683xe) {
  _0x7683xe = 'undefined';
  _0x7683xc = function (_0x7683xf) {
    if (typeof alert !== _0x7683xe) {
      alert(_0x7683xf)
    };
    console.log(console)
    if (typeof console !== _0x7683xe) {
      console.log(_0x7683xf)
    }
  };
  _0x7683xd = '删除版本号,js会定期弹窗,还请支持我们的工作';
  try {
    _0x7683x9 = __encode;
    if (!(typeof _0x7683x9 !== _0x7683xe && _0x7683x9 === 'jsjiami.com')) {
      _0x7683xc(_0x7683xd)
    }
  } catch (e) {
    _0x7683xc(_0x7683xd)
  }
})({})

对此,基本已经翻译完成,剩下的给这些_0x7683开头的变量换个名字,让他看起来更加舒服,在这里,我就大致根据这些变量定义的数据起个名字,并简化一些累赘的代码~
Magic~

var __encode = 'jsjiami.com',
  _a = {};
(function (_0xd642x1) {
  _0xd642x1["_decode"] = "http://www.sojson.com/javascriptobfuscator.html"
})(_a);
var __Oxb24bc = ["lite-android&", "stringify", "&android&3.1.0&", "&", "&846c4c32dae910ef", "12aea658f76e453faf803d15c40a72e0", "isNode", "crypto-js", "", "api?functionId=", "&body=", "&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=", "&sign=", "api.m.jd.com", "*/*", "RN", "JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)", "zh-Hans-CN;q=1, ja-CN;q=0.9", "undefined", "log", "删除", "版本号,js会定", "期弹窗,", "还请支持我们的工作", "jsjia", "mi.com"];

function taskUrl(id, data = {}) {
  let time = +new Date();
  let form = `lite-android&${JSON.stringify(data)}&android&3.1.0&${id}&${time}&846c4c32dae910ef`;
  let code = "12aea658f76e453faf803d15c40a72e0";
  const sha = $.isNode() ? require("crypto-js") : CryptoJS;
  let sign = sha.HmacSHA256(form, code).toString();
  return {
    url: ""+`${JD_API_HOST}api?functionId=${id}&body=${escape(JSON.stringify(data))}&appid=lite-android&client=android&uuid=846c4c32dae910ef&clientVersion=3.1.0&t=${time}&sign=${sign}`+"",
    headers: {
      'Host': 'api.m.jd.com',
      'accept': '*/*',
      'kernelplatform': 'RN',
      'user-agent': 'JDMobileLite/3.1.0 (iPad; iOS 14.4; Scale/2.00)',
      'accept-language': 'zh-Hans-CN;q=1, ja-CN;q=0.9',
      'Cookie': cookie
    }
  }
}
(function (encode, alarm) {
  alarm = function (msg) {
    if (typeof alert !== 'undefined') {
      alert(msg)
    };
    if (typeof console !== 'undefined') {
      console.log(msg)
    }
  };
  try {
    encode = __encode;
    if (!(typeof encode !== 'undefined' && encode === 'jsjiami.com')) {
      alarm('删除版本号,js会定期弹窗,还请支持我们的工作')
    }
  } catch (e) {
    alarm('删除版本号,js会定期弹窗,还请支持我们的工作')
  }
})({})

就此,解密已经处理好了

或许,写到这里,会有很多朋友会问
Q:为什么_0x7683开头的变量没不能直接解析出他具体的变量名是什么呢?
A:首先,这个变量的生成具体是根据哪些参数算出来的,我们不清楚,想要去解开他需要尝试各种组合,有可能有朋友说,一看就知道是进制转换出来的。这么想,没错,但是,具体是哪个进制,你还要不断的尝试,而且,其实这只是一个变量名,到最后也不会影响里面的逻辑。因此,为了高效而言,还是直接起一个新的名字就好了。(只是审查源码的逻辑,不要太纠结)


工具方法(网上)

下面这个方法可以直接把数组的内容在文中替换,并且,把xxx[“yyy”]的格式转为xxx.yyy的格式。其中,__Oxb7f82需要根据实际情况下的字符串数组对应的变量进行替换,以本文的栗子为例是__Oxb24bc,其中code的数据是对应需要解码的js数据。

console.log(code.replace(/__Oxb7f82\[0x[\da-f]+\]/g,a=>{
a=`"${eval(a).replace(/"/g,"\"")}"`;
console.log(a);
return a
}).replace(/\[s*['"][^"']+['"]\s*\]/g,a=>{
a=a.trim();
a=a.substring(2,a.length-2)
console.log(a);
return "."+a
}))

写到最后,这篇文章仅做技术分享,若涉及版权和信息安全问题,请联系小编会马上处理~

Easy-Mock官方文档

Easy Mock

Easy Mock 是一个可视化,并且能快速生成 模拟数据 的持久化服务。 忘掉下面这些实用但麻烦的 Mock 方式吧。在你用了 Easy Mock 之后,你肯定会爱不释手的。另外,如果使用时让你不快乐,那么请一定要记得 反馈

常见的 Mock 方式:

  • 将模拟数据直接写在代码里
  • 利用 JavaScript 拦截请求
  • 利用 Charles、 Fiddler 等代理工具拦截请求

快速开始

演示项目

每个接口旁都有一个小箭头,点击之后会展开并显示该接口的详细信息。

所有 新注册 的用户都会为其创建一个演示项目,如下图所示。这个项目中已经创建了大部分场景下的演示接口,点击接口旁的 预览按钮 即可。如果你已经掌握了 Easy Mock 的使用,可以将其删除。

基础语法

Easy Mock 引入了 Mock.js,下面只提供部分语法展示。更详尽的用例及文档请参考 Mock.js 官网。

  • 支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等
  • 支持扩展更多数据类型,支持自定义函数和正则
{
  "string|1-10": "★",
  "string2|3": "★★",
  "number|+1": 202,
  "number2|1-100.1-10": 1,
  "boolean|1": true,
  "regexp": /[a-z][A-Z][0-9]/,
  "absolutePath": "@/string @/user/name",
  "user": {
    "name": "demo"
  },
  "object|2": {
    "310000": "上海市",
    "320000": "江苏省"
  },
  "array|1": [ "AMD" ]
}

上面只展示了最基本的语法,如果你不明白为何要这样定义?请阅读 Mock.js 文档。 下面是生成的随机数据,会随请求发生改变。

{
  "string": "★",
  "string2": "★★★★★★",
  "number": 202,
  "number2": 71.73566,
  "boolean": false,
  "regexp": "qS8",
  "absolutePath": "★ demo",
  "user": {
    "name": "demo"
  },
  "object": {
    "310000": "上海市",
    "320000": "江苏省"
  },
  "array": "AMD"
}

数据占位符

掌握 数据占位符 可以让你的随机数据更真实。

{
  "string|1-2": "@string",
  "integer": "@integer(10, 30)",
  "float": "@float(60, 100, 2, 2)",
  "boolean": "@boolean",
  "date": "@date(yyyy-MM-dd)",
  "datetime": "@datetime",
  "now": "@now",
  "url": "@url",
  "email": "@email",
  "region": "@region",
  "city": "@city",
  "province": "@province",
  "county": "@county",
  "upper": "@upper(@title)",
  "guid": "@guid",
  "id": "@id",
  "image": "@image(200x200)",
  "title": "@title",
  "cparagraph": "@cparagraph",
  "csentence": "@csentence",
  "range": "@range(2, 10)"
}

上面只展示了些基本的占位符,如果你想了解更多?请阅读 Mock.js 文档。 下面是生成的随机数据,会随请求发生改变。

{
  "string": "&b(V",
  "integer": 29,
  "float": 65.93,
  "boolean": true,
  "date": "2013-02-05",
  "datetime": "1983-09-13 16:25:29",
  "now": "2017-08-12 01:16:03",
  "url": "cid://vqdwk.nc/iqffqrjzqa",
  "email": "u.ianef@hcmc.bv",
  "region": "华南",
  "city": "通化市",
  "province": "陕西省",
  "county": "嵊州市",
  "upper": "DGWVCCRR TLGZN XSFVHZPF TUJ",
  "guid": "c09c7F2b-0AEF-B2E8-74ba-E1efC0FecEeA",
  "id": "650000201405028485",
  "image": "http://dummyimage.com/200x200",
  "title": "Orjac Kwovfiq Axtwjlop Xoggxbxbw",
  "cparagraph": "他明林决每别精与界受部因第方。习压直型示多性子主求求际后世。严比加指安思研计被来交达技天段光。全千设步影身据当条查需府有志。斗中维位转展新斯克何类及拉件科引解。主料内被生今法听或见京情准调就品。同六通目自观照干意音期根几形。",
  "csentence": "命己结最方心人车据称温增划眼难。",
  "range": [2, 3, 4, 5, 6, 7, 8, 9]
}

创建一个项目

现在我们要开始创建第一个项目了,准备好了吗?

第一步:点击蓝色按钮

不管你在哪个页面?这个蓝色按钮都会固定在那。

参数描述
归属如果你是创建团队项目,一定要记得选团队
项目基础 URL没有特殊要求,尽量简短表意。例:/nba
Swagger Docs API如果有,详见 Swagger 一节

第二步:点击进入刚刚创建的项目 第三步:点击创建接口

关于 em.demo.all ,详见 语法提示 一节。

在数据编辑器中,输入 em.demo.all 按下回车,神奇的事情发生了。已经基于 Mock.js 语法自动创建好了数据对象,点击创建完成最后一步吧!

最后一步:预览接口

每个接口旁边都会对应很多操作按钮。现在点击红框标注的 预览按钮,看看会发生什么事情吧!

进阶指南

如果你已经掌握了上面基础部分,那么进阶指南将会使你把 Easy Mock 用到极致。

善用工作台

一旦项目变多,并且要在各个项目中来回切换的时候,请一定要记得使用工作台。虽然搜索也能解决问题,但还是有很多多余的步骤。更快捷的方式就是点击项目上的 小星星,将其加入到工作台中。一旦项目结束记得用 同样的方式 将项目移除工作台。

Swagger

如果你还不知道 Swagger 是什么?

效率一直我们关注的问题。Easy Mock 支持基于 Swagger 创建项目,以节省手动创建接口的时间。Swagger 文档更新也能通过更新操作重新生成接口,并且这是智能的,只会对修改过的接口进行更新。

基于 Swagger 创建的项目,会有以下几种能力。

  • 创建时会匹配状态为 200 的响应数据,生成相应的 Class Model,这样可以节省我们创建实体类的时间。
  • 支持查看 请求参数响应参数(前提是 Swagger 文档有定义这些字段)。

获取 Swagger Docs API

进入 文档首页 获取文档接口地址

下面以 Swagger 官方演示项目 Petstore 为例。如图所示,红框标注部分就是文档的接口地址,我们只需要在 创建项目 的时候,在 Swagger Docs API 一栏填写上即可。

由于版本问题,也许你会看到这样的文档首页。不要紧,复制如下地址即可。

如果不确定接口地址对不对?将地址复制到浏览器上打开,看看内容是否跟以下内容相似(注意:不同版本,接口内容是不同的,但可以大致判断出)。

{
  "apiVersion": "1.0",
  "swaggerVersion": "1.2",
  "info": {
    "description": "api list",
    "termsOfServiceUrl": "http://www.example/api-docs",
    "title": "api"
  },
  "apis": [
    {
      "description": "用户相关",
      "path": "/user",
      "position": 0
    },
    {
      "description": "图片相关",
      "path": "imgs",
      "position": 0
    }
  ]
}

如果创建项目之后没有接口数据,那么该接口文档可能存在一些问题(例如:数据对象交叉引用)。建议阅读下面的 OpenAPI 规范,再修改文档。

OpenAPI 规范,感兴趣可以了解下

API 自动生成

如果你想进一步提高自己的效率,现在可以通过 Easy Mock CLI 自动创建诸如 api.js 这样的文件了。

响应式数据

虽然无法做到数据联动,但是借助于 响应式数据,也能玩出许多新花样。

我们可以在 数据编辑器 中,为某个属性指定一个 Function。在 Function 中,我们提供了 _req 对象,这使得我们可以通过请求对象编写逻辑,实现响应式数据,如图所示。

当我们请求这个接口时,数据返回如下。如果我们传入的 namenk,那么 name 将会随机变化。

{
  "success": true,
  "data": {
    "default": "hah",
    "_req": {
      "method": "GET",
      "url": "/mock/599e9e962f17da111139eaf9/example/query",
      "header": {
        "connection": "keep-alive",
        "accept": "application/json, */*",
        "content-type": "application/json",
        "accept-encoding": "gzip, deflate",
        "accept-language": "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ja;q=0.2",
        // ...
      }
    },
    "name": "nk_Melissa Hernandez"
  }
}

Function 参数说明

对象描述
MockMock 对象
_req.url获得请求 url 地址
_req.method获取请求方法
_req.params获取 url 参数对象
_req.querystring获取查询参数字符串(url中?后面的部分),不包含 ?
_req.query将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象
_req.body当 post 请求以 x-www-form-urlencoded 方式提交时,我们可以拿到请求的参数对象
_req.path获取请求路径名
_req.header获取请求头对象
_req.originalUrl获取请求原始地址
_req.search获取查询参数字符串,包含 ?
_req.host获取 host (hostname:port)
_req.hostname获取 hostname
_req.type获取请求 Content-Type,不包含像 “charset” 这样的参数
_req.protocol返回请求协议
_req.ip请求远程地址
_req.get(field)获取请求 header 中对应 field 的值
_req.cookies(field)获取请求 cookies 中对应 field 的值

响应式数据注意事项

响应式数据提高了数据的可扩展性和灵活性,但同时也带来了一些问题。例如:代码中出现了死循环,这些问题往往都是致命的,因此我们做了一些限制。

如果生成数据的时间 超过1s,系统将会返回一个 timed out 的错误信息,此时应该检查代码是否出现了 异步操作死循环

另外,无法在函数中使用 setIntervalsetTimeout 等方法进行异步操作。

自定义响应

当定义的数据结构中包含 _res 字段的时候,会进入一个特殊逻辑,可以给返回的请求添加一些定制信息。

字段描述
status可以定制返回的 http status code,默认是 200
cookies可以定制需要设置的 cookie(暂时不支持设置过期时间等)
headers可以定制返回的 response 的 header
data如果有这个字段,会以此数据直接覆盖整个返回的数据,并且此处不支持 mock 的语法(如果 _res.status 的值为 200,则不会覆盖默认定义的 mock 数据)
{
  "success": true,
  "data": {
    "default": "hah"
  },
  "_res":{
    "status": 400,
    "data": {
      "success": false
    },
    "cookies": {
      "test": "true"
    },
    "headers": {
      "Power": "easy-mock"
    }
  }
}

以此定义为例,当 _res.status 的值为 400 的时候,用户端接收到的响应将会是 _res.data 中定义的数据,并且返回一个 status code 为 400 的响应,响应的 header 中会包含一个叫做 Power 的值,并为浏览器设置一个叫做 test 的 cookie。

当你想要返回正常的 mock 数据的时候,修改 _res.status 为 200,或者将整个 _res 删掉即可。

Tips

掌握这部分内容,可进一步提高效率。

快捷键

目前 Safari 不支持。

现在试试键盘按下 shift + ?,不出意外会出现一个快捷键提示框,如果没有可以切换下输入法试试。左侧的快捷键是全局的,不同页面有不同的快捷键,自己去其它页面试试吧。

语法提示

现在数据编辑器支持 语法提示 了。如果你想获得关于 Mock.js 的语法提示,可以输入 em. 即可。 按照 Mock.js 的分类,分为:

  • em.base
  • em.date
  • em.image
  • em.color
  • em.text
  • em.name
  • em.web
  • em.address
  • em.helper
  • em.miscellaneous
  • em.demo.all

数据编辑器支持按下 shift + f 搜索哦!

批量操作

目前,接口删除接口下载 支持批量操作。只需在接口旁边勾选上,执行相应操作即可。

移动端调试

步骤:

  • 利用 Charles Fiddler 等代理工具,抓取客户端中包含页面入口的接口
  • 在 Easy Mock 上创建一个接口
  • 将刚刚抓取的接口数据复制到 数据编辑器 中,修改页面入口为本地的入口然后保存。要用本地 ip 哦,localhost 在手机上是无效的
  • 复制接口地址
  • 回到代理工具,点击最开始抓取的接口,将其代理成刚刚复制的地址即可

特性

接口代理

不难发现 演示项目 下有一个地址为 /proxy 的接口。在数据编辑器中我们只填写了一个接口地址,预览的时候可以把这个接口的数据代理回来。除此之外,我们向 /proxy 传入的参数(query 和 body)都会被传递过去。 并且,如果你的代理接口地址为 http://example.com/api/user/:id,mock 接口地址为 /api/user/:id,那么这个 id 也会被正确的解析出来。

注意:接口代理的超时时间为 3s

JSONP

为了满足更多的使用场景,Easy Mock 支持 JSONP。不管是什么接口,只需要在请求中加入 jsonp_param_name 参数即可。 一般情况下,JSONP 的参数名多为 callback。但为了满足更多情况,我们通过 jsonp_param_name 参数去指定这个参数名。

// .../account/updSex?jsonp_param_name=callback&callback=success

协同编辑

如果是多人开发,那么你可以邀请小伙伴一起编辑 Mock。

项目中的成员,皆能 邀请移除 成员。作为被邀请者如果想移除项目,可在编辑项目时将自己移除,保存即可。另外,只有创建者才能删除项目哦!

团队项目

为了满足更多场景,Easy Mock 支持团队项目。

  • 所有用户 都可以创建、加入团队,并且 没有条件限制
  • 所有用户 都可以在团队下创建、修改项目,但无法删除项目 只有创建者能删除
  • 虽然个人项目也能通过邀请的方式达到同样的目的,但这种方式会更适合团队协作
  • 因为第一条的原因,建议在创建团队时,将团队名取特殊一点

Restful

如果后端提供 /restful/:id/list 这样或者这样 /restful/{id}/list 的接口地址。别慌,Easy Mock 是支持的。

常见问题

忘记密码

如果你忘记了密码?那么很遗憾的告诉你,目前没有提供找回密码的通道,建议重新创建一个账号。

如何本地部署

本地部署的相关介绍,请移步 GitHub

数据如何联动

无法进行数据联动,但是可以根据 响应式数据 实现一些需求。

响应式数据返回异常

如果使用了响应式数据,并且接口返回异常,请先根据 message 的描述 检查数据模型 中可能存在的 语法错误

请确保你已经了解了 响应式数据注意事项

加入团队不用审核吗

起初为了使用方便,并没有涉及到这块的支持。所以建议大家在创建团队的时候可以将团队名称取 特殊长一点

如何定义参数/响应状态

目前只有基于 Swagger 创建的项目才会自动创建相关的参数和响应数据定义,无法进行手动添加。

没有 Swagger 文档

Easy Mock 并没有强制要求使用 Swagger,所以即使没有 Swagger 我们也能愉快的使用 Easy Mock。如果你真的很想体验用 Swagger 创建项目,那么可以到 Swagger Editor 上写接口文档,然后生成 JSON,再使用 内网 Swagger 文档 的解决方案即可。

内网 Swagger 文档

如果 Swagger 文档只能内网访问,那么外网 Easy Mock 将无法为其创建项目。

解决方案

不适用于 OAS 1.2

  • 复制文档接口的 JSON 数据,以 Petstore 为例
  • 在 Easy Mock 中新建一个接口,数据编辑器 中粘贴刚刚复制的接口数据(可能有点大)并完成创建
  • 复制刚刚创建的接口的 URL
  • 创建一个新的项目,Swagger Docs API 文本框填写刚刚复制的 URL
  • 填写完其它内容点击创建即可

其它

反馈

扫描以下二维码,有任何问题可以在群内反馈。

Easy-Mock接口管理平台

Easy-Mock源项目地址

Gitee镜像项目地址

docker-compose部署

gitee国内docker-compose部署

1.简述

Easy-Mock是一个可视化,并且能快速生成模拟数据的持久化服务。可通过规则模拟生成数据进行调试。同时,提供在线接口调试功能。


2.特性

  • 支持接口代理
  • 支持快捷键操作
  • 支持协同编辑
  • 支持团队项目
  • 支持 RESTful
  • 支持Swagger| OpenAPI Specification (1.2&2.0&3.0)
  • 基于 Swagger 快速创建项目
  • 支持显示接口入参与返回值
  • 支持显示实体类
  • 支持灵活性与扩展性更高的响应式数据开发
  • 支持自定义响应配置(例:status/headers/cookies)
  • 支持 Mock.js 语法
  • 支持 restc 方式的接口预览

3.Easy-Mock安装

Easy-Mock源项目地址

Gitee镜像项目地址

可以通过拉取该项目代码仓到本地运行

$ git clone https://github.com/easy-mock/easy-mock.git
$ cd easy-mock && npm install
$ npm run dev

其中,相关依赖软件必须安装 Node.jsv8.x, 不支持 v10.x)& MongoDB>= v3.4)& Redis>= v4.0

配置文件

找到 config/default.json,或者创建一个 config/local.json 文件,将如下需要替换的字段换成自己的配置即可。

不同环境会加载不同的配置文件,在此之前你应该对 node-config 有所了解。

{
  "port": 7300,
  "host": "0.0.0.0",
  "pageSize": 30,
  "proxy": false,
  "db": "mongodb://localhost/easy-mock",
  "unsplashClientId": "",
  "redis": {
    "keyPrefix": "[Easy Mock]",
    "port": 6379,
    "host": "localhost",
    "password": "",
    "db": 0
  },
  "blackList": {
    "projects": [], // projectId,例:"5a4495e16ef711102113e500"
    "ips": [] // ip,例:"127.0.0.1"
  },
  "rateLimit": { // https://github.com/koajs/ratelimit
    "max": 1000,
    "duration": 1000
  },
  "jwt": {
    "expire": "14 days",
    "secret": "shared-secret"
  },
  "upload": {
    "types": [".jpg", ".jpeg", ".png", ".gif", ".json", ".yml", ".yaml"],
    "size": 5242880,
    "dir": "../public/upload",
    "expire": {
      "types": [".json", ".yml", ".yaml"],
      "day": -1
    }
  },
  "ldap": {
    "server": "", // 设置 server 代表启用 LDAP 登录。例:"ldap://localhost:389" 或 "ldaps://localhost:389"(使用 SSL)
    "bindDN": "", // 用户名,例:"cn=admin,dc=example,dc=com"
    "password": "",
    "filter": {
      "base": "", // 查询用户的路径,例:"dc=example,dc=com"
      "attributeName": "" // 查询字段,例:"mail"
    }
  },
  "fe": {
    "copyright": "",
    "storageNamespace": "easy-mock_",
    "timeout": 25000,
    "publicPath": "/dist/"
  }
}

背景图配置:

登录页的背景图服务目前支持 UnsplashBing

如果 unsplashClientId 配置留空,默认由 Bing 提供服务。

注意:

  • publicPath 默认是 '/dist/'。如有需要,可以将其替换成自己的 CDN;
  • 关于 fe 的配置,一旦发生改变应该重新执行 build 命令。

Docker-Compose部署

docker-compose部署

gitee国内docker-compose部署

通过拉取上述代码仓文件,安装docker-compose,在Dockerfile目录下使用

$ docker-compose up -d #部署启动项目
$ docker-compose start #启动容器
$ docker-compose status #检查状态
$ docker-compose stop #停止容器

若无特殊修改,登录地址是http://127.0.0.1:7300


4.Easy-Mock简单快速使用

登录后,新建项目,填写对应的信息

Swagger-API导入

在项目设置中,填写swagger的文档地址,或保存为json格式上传。

基于 Swagger 创建的项目,会有以下几种能力。

  • 创建时会匹配状态为 200 的响应数据,生成相应的 Class Model,这样可以节省我们创建实体类的时间。
  • 支持查看 请求参数响应参数(前提是 Swagger 文档有定义这些字段)。

获取 Swagger Docs API

进入 文档首页 获取文档接口地址

下面以 Swagger 官方演示项目 Petstore 为例。如图所示,红框标注部分就是文档的接口地址,我们只需要在 创建项目 的时候,在 Swagger Docs API 一栏填写上即可。

由于版本问题,也许你会看到这样的文档首页。不要紧,复制如下地址即可。

如果不确定接口地址对不对?将地址复制到浏览器上打开,看看内容是否跟以下内容相似(注意:不同版本,接口内容是不同的,但可以大致判断出)。

{
  "apiVersion": "1.0",
  "swaggerVersion": "1.2",
  "info": {
    "description": "api list",
    "termsOfServiceUrl": "http://www.example/api-docs",
    "title": "api"
  },
  "apis": [
    {
      "description": "用户相关",
      "path": "/user",
      "position": 0
    },
    {
      "description": "图片相关",
      "path": "imgs",
      "position": 0
    }
  ]
}

在线调用调试接口

点击查看按钮,即可进入调试界面进行调试。

API接口mock数据

基础语法

Easy Mock 引入了 Mock.js,下面只提供部分语法展示。更详尽的用例及文档请参考 Mock.js 官网。

  • 支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等
  • 支持扩展更多数据类型,支持自定义函数和正则
{
  "string|1-10": "★",
  "string2|3": "★★",
  "number|+1": 202,
  "number2|1-100.1-10": 1,
  "boolean|1": true,
  "regexp": /[a-z][A-Z][0-9]/,
  "absolutePath": "@/string @/user/name",
  "user": {
    "name": "demo"
  },
  "object|2": {
    "310000": "上海市",
    "320000": "江苏省"
  },
  "array|1": [ "AMD" ]
}

上面只展示了最基本的语法,如果你不明白为何要这样定义?请阅读 Mock.js 文档。 下面是生成的随机数据,会随请求发生改变。

{
  "string": "★",
  "string2": "★★★★★★",
  "number": 202,
  "number2": 71.73566,
  "boolean": false,
  "regexp": "qS8",
  "absolutePath": "★ demo",
  "user": {
    "name": "demo"
  },
  "object": {
    "310000": "上海市",
    "320000": "江苏省"
  },
  "array": "AMD"
}

数据占位符

掌握 数据占位符 可以让你的随机数据更真实。

{
  "string|1-2": "@string",
  "integer": "@integer(10, 30)",
  "float": "@float(60, 100, 2, 2)",
  "boolean": "@boolean",
  "date": "@date(yyyy-MM-dd)",
  "datetime": "@datetime",
  "now": "@now",
  "url": "@url",
  "email": "@email",
  "region": "@region",
  "city": "@city",
  "province": "@province",
  "county": "@county",
  "upper": "@upper(@title)",
  "guid": "@guid",
  "id": "@id",
  "image": "@image(200x200)",
  "title": "@title",
  "cparagraph": "@cparagraph",
  "csentence": "@csentence",
  "range": "@range(2, 10)"
}

上面只展示了些基本的占位符,如果你想了解更多?请阅读 Mock.js 文档。 下面是生成的随机数据,会随请求发生改变。

{
  "string": "&b(V",
  "integer": 29,
  "float": 65.93,
  "boolean": true,
  "date": "2013-02-05",
  "datetime": "1983-09-13 16:25:29",
  "now": "2017-08-12 01:16:03",
  "url": "cid://vqdwk.nc/iqffqrjzqa",
  "email": "u.ianef@hcmc.bv",
  "region": "华南",
  "city": "通化市",
  "province": "陕西省",
  "county": "嵊州市",
  "upper": "DGWVCCRR TLGZN XSFVHZPF TUJ",
  "guid": "c09c7F2b-0AEF-B2E8-74ba-E1efC0FecEeA",
  "id": "650000201405028485",
  "image": "http://dummyimage.com/200x200",
  "title": "Orjac Kwovfiq Axtwjlop Xoggxbxbw",
  "cparagraph": "他明林决每别精与界受部因第方。习压直型示多性子主求求际后世。严比加指安思研计被来交达技天段光。全千设步影身据当条查需府有志。斗中维位转展新斯克何类及拉件科引解。主料内被生今法听或见京情准调就品。同六通目自观照干意音期根几形。",
  "csentence": "命己结最方心人车据称温增划眼难。",
  "range": [2, 3, 4, 5, 6, 7, 8, 9]
}

创建一个项目

现在我们要开始创建第一个项目了,准备好了吗?

第一步:点击蓝色按钮

不管你在哪个页面?这个蓝色按钮都会固定在那。

参数描述
归属如果你是创建团队项目,一定要记得选团队
项目基础 URL没有特殊要求,尽量简短表意。例:/nba
Swagger Docs API如果有,详见 Swagger 一节

第二步:点击进入刚刚创建的项目 第三步:点击创建接口

关于 em.demo.all ,详见 语法提示 一节。

在数据编辑器中,输入 em.demo.all 按下回车,神奇的事情发生了。已经基于 Mock.js 语法自动创建好了数据对象,点击创建完成最后一步吧!

最后一步:预览接口

每个接口旁边都会对应很多操作按钮。现在点击红框标注的 预览按钮,看看会发生什么事情吧!


5.相关Mock规范(Mockjs)

Mockjs文档:http://mockjs.com/examples.html#Number


6.EasyMock文档

Easy-Mock中文文档

7.常用mock类型

Restful的mock

如果后端提供 /restful/:id/list 这样或者这样 /restful/{id}/list 的接口地址。

可以这样配置mock:

配置:id或{id}这样格式的字段在url上,在mock数据时加上对应的参数获取方法,对该值进行获取并处理。若在mock数据中不对此参数处理,也不影响该接口正常mock数据输出。

Proxy代理

该方法是可以向 /proxy 传入的参数(query 和 body)都会被传递过去。 并且,如果你的代理接口地址为 http://example.com/api/user/:id,mock 接口地址为 /api/user/:id,那么这个 id 也会被正确的解析出来。

Tip:该mock方法超时时间为3s,这里建议在服务器中自己编写一个接口,避免请求超时,使用百度等其他网站亦可。

直接填写对应的地址,同时,该地址支持Restful格式,支持/api/user/:id格式的地址。

语法提示

现在数据编辑器支持 语法提示 了。如果你想获得关于 Mock.js 的语法提示,可以输入 em. 即可。 按照 Mock.js 的分类,分为:

  • em.base
  • em.date
  • em.image
  • em.color
  • em.text
  • em.name
  • em.web
  • em.address
  • em.helper
  • em.miscellaneous
  • em.demo.all

数据编辑器支持按下 shift + f 搜索

8.Easy-Mock在线平台(可提供测试)

http://172.81.246.73:7300/

Docker安装与常用命令

1.Docker简述

Docker是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。

2.Docker生命周期

一句话简述生命周期

Docker镜像拉取pull一个image并创建在本地,通过creat在服务器上创建容器,通过start命令启动已创建的容器,随后根据需求,使用pause/unpause/stop控制容器的启停/暂停。

3.Docker安装

官网下载方式

https://docs.docker.com/engine/install/

脚本安装方式

官方脚本

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

国内镜像

curl -sSL https://get.daocloud.io/docker | sh

更多安装方式

4.安装完成

启动docker

sudo systemctl start docker

设置开机启动

sudo systemctl enable docker

测试docker是否安装完成

docker run hello-world

从中央仓中拉取名为hello-world的容器

结果

出现此信息,Docker已正确安装,后面通过docker ps -a可以查到部署过的hello-world容器

5.配置开机docker容器开机自启

1.docker的update命令

[root@iZbp1e9mxelwe7pwimpw8sZ ~]# docker update --help
Usage:	docker update [OPTIONS] CONTAINER [CONTAINER...]
Update configuration of one or more containers
Options:
      --blkio-weight uint16        Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
      --cpu-period int             Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int              Limit CPU CFS (Completely Fair Scheduler) quota
      --cpu-rt-period int          Limit the CPU real-time period in microseconds
      --cpu-rt-runtime int         Limit the CPU real-time runtime in microseconds
  -c, --cpu-shares int             CPU shares (relative weight)
      --cpus decimal               Number of CPUs
      --cpuset-cpus string         CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string         MEMs in which to allow execution (0-3, 0,1)
      --kernel-memory bytes        Kernel memory limit
  -m, --memory bytes               Memory limit
      --memory-reservation bytes   Memory soft limit
      --memory-swap bytes          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --pids-limit int             Tune container pids limit (set -1 for unlimited)
      --restart string             Restart policy to apply when a container exits

[root@iZbp1e9mxelwe7pwimpw8sZ ~]

#

翻译结果

选项:
      --blkio-weight uint16块IO(相对权重),介于10到1000之间,或者禁用0(默认为0)
      --cpu-period int限制CPU CFS(完全公平的调度程序)期限
      --cpu-quota int限制CPU CFS(完全公平的调度程序)配额
      --cpu-rt-period int限制CPU实时时间(以微秒为单位)
      --cpu-rt-runtime int以毫秒为单位限制CPU实时运行时间
  -c,--cpu-shares int CPU份额(相对权重)
      --cpus十进制CPU数
      --cpuset-cpus允许执行的字符串CPU(0-3,0,1)
      --cpuset-mems允许执行的字符串MEM(0-3,0,1)
      --kernel-memory字节内核内存限制
  -m,--memory bytes内存限制
      --memory-reservation字节内存软限制
      --memory-swap字节交换限制等于内存加交换:'-1'以启用无限交换
      --pids-limit int调整容器的pids限制(设置-1为无限制)
      --restart字符串容器退出时要应用的重新启动策略

2.–restart参数解释

参数名解释
no不自动重启容器. (默认值)
on-failure容器发生error而退出(容器退出状态不为0)重启容器,可以指定重启的最大次数,如:on-failure:3
unless-stopped在容器已经stop掉或Docker stoped/restarted的时候才重启容器
always在容器已经stop掉或Docker stoped/restarted的时候才重启容器,手动stop的不算

3.设置开机自启动(以容器名为tomcat8的容器为例)

docker update --restart=always tomcat8

4.测试结果

关闭所有容器

[root@iZbp1e9mxelwe7pwimpw8sZ ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

[root@iZbp1e9mxelwe7pwimpw8sZ ~]

#

重启开机后的结果:容器已经自启动

[root@iZbp1e9mxelwe7pwimpw8sZ ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
b397fad059a2        tomcat:8            "/usr/local/tomcat8/…"   5 weeks ago         Up 8 seconds        0.0.0.0:80->8080/tcp   tomcat8

[root@iZbp1e9mxelwe7pwimpw8sZ ~]

#

6.Docker常用命令

1.通过Dockerfile构建镜像容器

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

首先,编写一份Dockerfile文件(Dockerfile文件语法编写待续,可直接学习这里的)

Dockerfile详解

编写完,运行Dockerfile创建需要拉取的镜像并配置容器中安装的软件、部署的项目。

bulid命令

下面以部署一个nginx进容器为例

该命令需在Dockerfile文件的存放目录下运行

通过目录下的 Dockerfile 构建一个 nginx:v3(镜像名称:镜像标签)。

:最后的.代表本次执行的上下文路径。

上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。

解析:由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。

如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。

注意:上下文路径下不要放无用的文件,因为会一起打包发送给 docker 引擎,如果文件过多会造成过程缓慢。

docker build -t nginx:v3 .

然后通过run命令,将镜像运行

run命令

命令格式:

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

可当create和start命令使用

1、不传参运行

$ docker run  nginx:test

容器内会默认运行以下命令,启动主进程。

nginx -c /etc/nginx/nginx.conf

2、传参运行

$ docker run  nginx:test -c /etc/nginx/new.conf

容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)

nginx -c /etc/nginx/new.conf

2.容器增删查、状态pull、create、start、pause、unpause、stop、run、restart

docker容器的生命周期

docker pull [OPTIONS] NAME[:TAG|@DIGEST]
从远程docker hub中提取图像并将其保存在本地计算机中。此命令与Git pull命令的作用相同。
docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
此命令按指定的图像创建新的Docker容器。
docker start [OPTIONS] CONTAINER [CONTAINER...]
此命令用于将已退出或新创建的容器启动到运行状态。
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
此命令创建一个新容器,并在新创建的容器中运行该映像。“docker create”和“docker start”命令都可以通过此单个命令执行。
docker stop [OPTIONS] CONTAINER [CONTAINER...]
此命令将停止正在运行的容器。这会将容器状态从运行更改为激动。
docker pause CONTAINER [CONTAINER...]
此命令通过发出SIGSTOP命令来暂停正在运行的容器中的正在运行的进程。
docker unpause CONTAINER [CONTAINER...]
此命令将暂停进程返回到运行状态。

查看

docker ps #查看正在运行的容器
docker ps -a #查看所有容器
docker images #查看所有镜像

查看容器状态

docker stats 容器名/容器id

删除容器、镜像

docker rm 容器名/容器id #删除容器
docker rmi 镜像id #删除镜像

3.进入容器exec、attach

进入容器的命令有两种:

docker exec: 在已运行的容器中,执行命令,操作对象是容器,如果你要进入已运行的容器,并且执行命令,用exec。

docker attach: 同样操作的是已运行的容器,可以将本机标准输入(键盘输入)输到容器中,也可以将容器的输出显示在本机的屏幕上,如果你想查看容器运行过程中产生的标准输入输出,用attach。

一般情况,我们使用的是:

exec

docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
OPTIONS说明:
	-d :分离模式: 在后台运行
	-i :即使没有附加也保持STDIN 打开
	-t :分配一个伪终端
CONTAINER:容器名称或ID,必选,看出exec操作的对象是容器
COMMAND:命令,必选(一般填写bash)

例子

$ docker exec -i -t  mynginx /bin/bash

attach

docker attach [OPTIONS] CONTAINER
CONTAINER:容器名称或ID,必选,attach的操作对象也是容器

例子

docker attach mynginx

退出快捷键,使用该方法可以避免退出容器时同时停止容器

Ctrl+P+Q

进入容器时卡停

  1. 只用-i时,由于没有分配伪终端,看起来像pipe执行一样。但是执行结果、命令返回值都可以正确获取。
  2. 如果只使用-t参数,则可以看到一个console窗口,但是执行命令会发现由于没有获得stdin的输出,无法看到命令执行情况。
  3. 在后台执行一个进程。可以看出,如果一个命令需要长时间进程,使用-d参数会很快返回。
  4. 使用-it时,则和我们平常操作console界面类似。而且也不会像attach方式因为退出,导致整个容器退出。
docker exec -it +容器ID /bin/bash

详细exec解析看这里

4.查看容器日志

docker logs 容器名/容器id

持续查看容器日志(使用场景:容器启动失败排查)

docker logs -f 容器名/容器id

5.查看容器状态

容器运行时不一定有/bin/bash终端来交互执行top命令,而且容器还不一定有top命令,可以使用docker top来实现查看container中正在运行的进程。

docker top 容器名称/id

7.docker容器部署镜像加速

docker镜像源配置

Docker加速镜像源配置

配置修改

方法一

进入配置文件修改

vim /usr/lib/systemd/system/docker.service

配置文件信息

[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=network.target rhel-push-plugin.socket registries.service
Wants=docker-storage-setup.service
Requires=docker-cleanup.timer

[Service]
Type=notify
NotifyAccess=all
EnvironmentFile=-/run/containers/registries.conf
EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
Environment=GOTRACEBACK=crash
Environment=DOCKER_HTTP_HOST_COMPAT=1
Environment=PATH=/usr/libexec/docker:/usr/bin:/usr/sbin
ExecStart=/usr/bin/dockerd-current --registry-mirror=https://nw071uxz.mirror.aliyuncs.com \
          --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \
          --default-runtime=docker-runc \
          --exec-opt native.cgroupdriver=systemd \
          --userland-proxy-path=/usr/libexec/docker/docker-proxy-current \
          $OPTIONS \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $ADD_REGISTRY \
          $BLOCK_REGISTRY \
          $INSECURE_REGISTRY\
          $REGISTRIES
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
Restart=on-abnormal
MountFlags=slave
KillMode=process

[Install]
WantedBy=multi-user.target

在18行追加–registry-mirror=https://nw071uxz.mirror.aliyuncs.com \

修改完后重新加载配置并启动docker

systemctl daemon-reload
systemctl start docker

方法二(推介)

编辑以下配置文件

vim /etc/docker/daemon.json

添加镜像地址,registry-mirrors数组中添加镜像地址

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

方法三

使用命令方式添加

dockerd --registry-mirror=https://registry.docker-cn.com

国内镜像地址

Docker中国区官方镜像

https://registry.docker-cn.com

网易

http://hub-mirror.c.163.com

ustc

https://docker.mirrors.ustc.edu.cn

中国科技大学

https://docker.mirrors.ustc.edu.cn

阿里云容器服务

https://cr.console.aliyun.com/

首页点击“创建我的容器镜像” 得到一个专属的镜像加速地址,类似于“https://1234abcd.mirror.aliyuncs.com