成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

前端容器化實踐

開發 前端
前后端分離的趨勢已形成現狀,前端工程復雜度疊加增長,新、老項目部署依賴的環境和Node.js版本會存在差異,生產環境下構建混淆后的腳本、靜態資源文件依賴環境部署服務進行訪問,前端工程未能形成"單體工件"部署,容器的出現大大簡化了部署流程。

1.引言

前端容器化是一種將前端應用程序打包成容器的技術,使其可以在不同的環境中快速、高效地部署和運行。

2.背景

前后端分離的趨勢已形成現狀,前端工程復雜度疊加增長,新、老項目部署依賴的環境和Node.js版本會存在差異,生產環境下構建混淆后的腳本、靜態資源文件依賴環境部署服務進行訪問,前端工程未能形成"單體工件"部署,容器的出現大大簡化了部署流程。

前端容器化可以方便的管理前端環境變量注入、運行環境(不同項目依賴不同的node環境,node的版本兼容是個很大的問題)、節約服務器成本、更快捷方便的版本回滾、多架構部署、CI/CD自動化集成部署、DevOps等等,好處只有多到你想不到(此處手動偷笑)。

本文基于React項目結合Docker,分享在前端引入容器技術帶來的變革。

3.容器化在github的運用

github推出了github-action來做容器化的ci/cd,我下面展示用github-action做一個npm自動化發包的示例:

  • 在項目根目錄下新建.github/workflows/ci.yml文件
  • 去npm官網申請一個token(具體怎么去申請,請自己去搜索解決)
  • 將這段代碼貼入ci.yml文件
  • push代碼到master分支,就會自動走ci/cd進行部署啦!
name: CI
on:
  push:
    branches:
      - master
jobs:
  build:
    # 指定操作系統
    runs-on: ubuntu-latest
    steps:
      # 將代碼拉到虛擬機
      - name: Checkout repository
        uses: actions/checkout@v2
      # 指定node版本
      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16.x'
          registry-url: 'https://registry.npmjs.org'
      # 依賴緩存策略
      - name: Cache
        id: cache-dependencies
        uses: actions/cache@v3
        with:
          path: |
            **/node_modules
          key: ${{runner.OS}}-${{hashFiles('**/pnpm-lock.yaml')}}
      - name: Install pnpm
        run: npm install -g pnpm@7.5.0
      # 依賴下載
      - name: Installing Dependencies
        if: steps.cache-dependencies.outputs.cache-hit != 'true'
        run: pnpm install
      # 打包
      - name: Running Build
        run: pnpm run build
      # 測試
      - name: Running Test
        run: pnpm run test-unit
      # 發布
      - name: Running Publish
        run: npm publish
        env:
          # NPM_TOKEN is access token
         NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

4.基于docker構建前端鏡像

在學習前端項目ci/cd構建之前,讓我們先學習下前端鏡像怎么構建

4.1 安裝docker

點此處坐飛機去安裝docker安裝完成后執行以下命令查看docker版本,盡量帶buildx的版本

docker -v
Docker version 24.0.2, build cb74dfc
4.2 編寫Dockerfile

這里先需要普及一個前端工程知識,我們都知道一個基于npm的項目,需要一個package.json文件,然后執行npm run install下載包,npm run build打包,打包出來的文件其實是不能直接運行的,需要啟動一個node服務運行,所以我們就寫一個最基本的基于node和nginx的鏡像,示例如下

在項目根目錄下添加nginx配置文件,取名為nginx.conf,內容如下

worker_processes  1;
 
events {  
    worker_connections  1024;  
} 
http {  
    sendfile         on;  
    tcp_nodelay       on;  
    keepalive_timeout  30; 
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/front/dist;
        autoindex on;   
        autoindex_exact_size off;   
        autoindex_localtime on;
        location / {
            try_files $uri $uri/ =404;
            index index.html index.htm;
            gzip_static on;
            expires max;
            add_header Cache-Control public;
            if ($request_filename ~* ^.*?\.(eot)|(ttf)|(woff)|(svg)|(otf)$) {
                add_header Access-Control-Allow-Origin *;
            }
        }
    }
}

在項目根目錄下添加docker配置文件,取名為Dockerfile,內容如下

FROM node:17-buster as builder

WORKDIR /src
COPY ./ /src

RUN npm install -g pnpm \
    && pnpm install \
    && pnpm build

FROM nginx:alpine-slim

RUN mkdir /usr/share/nginx/front \
    && mkdir /usr/share/nginx/front/dist \
    && rm -rf /etc/nginx/nginx.conf
 
COPY --from=builder /src/nginx.conf /etc/nginx/nginx.conf

COPY --from=builder /src/dist /usr/share/nginx/front/dist

EXPOSE 80

接下來使用docker build打包鏡像(如果有桌面工具,打包成功后docker桌面工具的images欄目能看到), docker run執行鏡像(如果有桌面工具,運行成功后docker桌面工具的containers欄目能看到), docker run運行成功后可以打開瀏覽器輸入:http://localhost 進行查看

docker buildx build -t webapp-demo:v1 .

docker run -d -p 80:80 webapp-demo:v1
4.3 如何基于Dockerfile做pnpm緩存

這里我引用一段話: 使用多階段構建,構建的鏡像中只包含了目標文件夾 dist,但仍然存在一些問題,當 package.json 文件變動時,RUN npm i && rm -rf ~/.npm 這一層會重新執行,變更多次后,生成了大量的中間層鏡像。

為解決這個問題,進一步的我們可以設想一個類似 數據卷 的功能,在鏡像構建時把 node_modules 文件夾掛載上去,在構建完成后,這個 node_modules 文件夾會自動卸載,實際的鏡像中并不包含 node_modules 這個文件夾,這樣我們就省去了每次獲取依賴的時間,大大增加了鏡像構建效率,同時也避免了生成了大量的中間層鏡像。

此處課代表總結一下:就是盡量減少中間層鏡像的可能,最小化docker映像大小和構建時間

由于我使用的是pnpm進行npm包管理,所以我去翻閱了pnpm官方文檔關于此優化如下:

FROM node:20-slim AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

COPY . /app
WORKDIR /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]

于是本著依葫蘆畫瓢的精神,還有使用生產化的nginx配置,我找了同事寫的nginx包裝鏡像,同樣你可以執行docker build、docker run進行驗證,然后我改造后的代碼如下:

FROM node:17-buster AS builder

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /src
COPY ./ /src

RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
    --mount=type=cache,target=/pnpm/store,id=pnpm_cache \
        pnpm install

RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
        pnpm run build

FROM ghcr.io/zboyco/webrunner:0.0.7

COPY --from=builder /src/dist /app
4.4 如何利用buildx制作多架構鏡像

docker buildx的工具,說白了就是給你提供一個能力,當你的宿主機是x86 64的架構時,你想構建鏡像為ARM64的架構,就需要這個工具,給人的感覺有點類似交叉編譯,諸如:go build的交叉編譯,在win10下編譯可執行程序,可用于特定linux平臺

buildx本質上調用了 buildkit 的 api,構建是在 buildkit 的環境中進行的。 是否支持多架構,取決于 buildkit 的環境,如果需要 buildkit支持多架構,需要在宿主機執行(當然這個不是必須的,按構建的需求進行控制,Docker桌面版無需進行此項設置):

docker run --privileged --rm tonistiigi/binfmt --install all

我們這里改造上面Dockerfile代碼,使它支持多架構,由于platform是實驗性質,所以需要先執行docker pull docker/dockerfile 拉取鏡像

# syntax = docker/dockerfile:experimental
# --platform, 會讓 builder 只會有一份,且 arch 與宿主機一致
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:17-buster AS builder

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /src
COPY ./ /src

RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
    --mount=type=cache,target=/pnpm/store,id=pnpm_cache \
        pnpm install

RUN --mount=type=cache,target=/src/node_modules,id=myapp_pnpm_module,sharing=locked \
        pnpm run build

FROM ghcr.io/zboyco/webrunner:0.0.7

COPY --from=builder /src/dist /app

在執行打包鏡像命令之前,我們先查看下我們機器默認的 builder 實例

docker buildx ls

NAME/NODE       DRIVER/ENDPOINT STATUS  BUILDKIT                              PLATFORMS
default         docker
  default       default         running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
desktop-linux * docker
  desktop-linux desktop-linux   running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

使用buildx執行上面的腳本打包鏡像會報錯如下

docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t webapp-official-website:v1 .

ERROR: Multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")

由于 Docker 默認的 builder 實例不支持同時指定多個 --platform,我們必須首先創建一個新的 builder 實例。同時由于國內拉取鏡像較緩慢,我們可以使用配置了鏡像加速地址的 dockerpracticesig/buildkit:master 鏡像替換官方鏡像。

如果你有私有的鏡像加速器,可以基于 https://github.com/docker-practice/buildx 構建自己的 buildkit 鏡像并使用它。

# 適用于國內環境
$ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master

# 適用于騰訊云環境(騰訊云主機、coding.net 持續集成)
$ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master-tencent

# $ docker buildx create --name mybuilder --driver docker-container

$ docker buildx use mybuilder

我們選擇適用于國內環境的方案的命令進行創建, 可以看到多了name為 mybuilder-cn 的實例

docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master

docker buildx ls
NAME/NODE       DRIVER/ENDPOINT  STATUS   BUILDKIT                              PLATFORMS
mybuilder-cn *  docker-container
  mybuilder-cn0 desktop-linux    inactive
default         docker
  default       default          running  v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
desktop-linux   docker
  desktop-linux desktop-linux    running  v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

$ docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t myusername/hello . --push

# 查看鏡像信息
$ docker buildx imagetools inspect myusername/hello

在不同架構運行該鏡像,可以得到該架構的信息。

$ docker run -it --rm myusername/hello

5.如何利用容器化做前端環境變量注入

  • 前端雖然對環境變量的需求場景不多,但是基本的api baseURL, appName, env這些是需要的
  • 如果是微前端的場景,那么需要的其他網站url都是環境變量,還挺多的
  • 依稀記得剛入行時,前端區分測試環境,線上環境就是直接通過域名進行判斷,例如: inlcudes(url, ".com")?, 然后得到isProd去獲取項目中配置的不同環境的變量,這樣顯得很low
  • 后面出了vue, react這種框架,可以在npm run dev時候指明--prod,然后通過process拿到isProd,去獲取對應配置
  • 現在可以直接通過容器化注入環境變量,然后使用nginx將容器中的環境變量注入前端項目html的meta標簽content中, 然后從meta標簽獲取變量
  • 如果是monorepo項目,npm run build的時候,Dockerfile里面也需要通過容器中的環境變量獲取打包哪個項目
  • 測試環境通過ts文件配置環境變量,然后項目啟動時組合這些環境變量信息,生成config default.yml, ci/cd時k8s自動將default.yml中配置的環境變量寫入容器中
  • 線上環境直接提供UI頁面配置環境變量,然后調用api,后端api通過k8s將變量寫入容器中
  • 如何通過k8s讀取配置的環境變量并寫入容器中且聽下回分解

下面貼一段生產場景下的Dockerfile示例代碼(公司應該不會砍我吧!),這里省略k8s如何往容器中注入環境變量,只考慮如何從容器中讀取環境變量(假設環境變量已經注入容器中)

FROM --platform=${BUILDPLATFORM} hub-dev.rockontrol.com/rk-infrav2/docker.io/library/node:17-bullseye as builder

WORKDIR /src
COPY ./ ./

ARG APP
ARG ENV

ARG PROJECT_GROUP
ARG PROJECT_NAME
ARG PROJECT_VERSION

ARG YARN_NPM_REGISTRY_SERVER

RUN npm install -g --registry=${YARN_NPM_REGISTRY_SERVER} pnpm
RUN pnpm --registry=${YARN_NPM_REGISTRY_SERVER} install


RUN PROJECT_GROUP=${PROJECT_GROUP} PROJECT_VERSION=${PROJECT_VERSION} \
    npx devkit build --prod ${APP} ${ENV}

FROM hub-dev.rockontrol.com/rk-infrav2/ghcr.io/zboyco/webrunner:0.0.7

ARG PROJECT_NAME
COPY --from=builder /src/public/${PROJECT_NAME} /app

下面貼一段nginx如何將環境變量寫入html中

#!/bin/sh

# This script is used to start the application

# 初始化一個字符串,用于存儲拼接后的值
app_config="${APP_CONFIG}"
ext_config=""

# 遍歷所有環境變量
for var in $(env | cut -d= -f1); do
    # 檢查變量是否以 "APP_CONFIG__" 開頭
    if [ "$(echo "$var" | grep '^APP_CONFIG__')" ]; then
        # 去除變量名前綴 "APP_CONFIG__"
        trimmed_var=$(echo "$var" | sed 's/^APP_CONFIG__//')
        # 使用 eval 來獲取變量值并拼接到字符串中
        value=$(eval echo "\$$var")
        app_config="${app_config},${trimmed_var}=${value}"
    fi
done

# 去掉起始的逗號
export app_config=$(echo "$app_config" | sed 's/^,//')

# 解析app_config變量
# 以,分割 app_config
IFS=","
set -- $app_config
# 遍歷數組
for config in "$@"; do
    # 以等號分剝數組
    IFS="="
    set -- $config
    # 將單個環境變量單獨注入
    ext_config="${ext_config}        sub_filter '__$1__' '$2';\n"
    echo "$1: $2"
done

# Install envsubst
echo "Installing envsubst"
# 將擴展變量替換到 conf.template 中
sed "s@__EXTENT_CONFIG__@${ext_config}@g" /etc/nginx/conf.d/conf-base.template > /etc/nginx/conf.d/conf.template 

envsubst '${PROJECT_VERSION} ${ENV} ${app_config}' < /etc/nginx/conf.d/conf.template > /etc/nginx/conf.d/default.conf

# Start nginx
echo "Starting nginx"
nginx -g 'daemon off;'

下面貼一段前端如何從html meta標簽中讀取環境變量

import appConfig from "../../config";

interface IConfig {
  appName: string;
  baseURL: string;
  version?: string;
  env?: string;
}

export function getConfig(): IConfig {
  const defaultAppConfig = {
    appName: "",
    version: "",
    env: "",
    baseURL: "",
  };
  console.log("metaEnv", import.meta.env);

  if (import.meta.env.DEV) {
    return appConfig;
  } else {
    const appConfigStr = getMeta("app_config");

    if (!appConfigStr) return defaultAppConfig;

    return parseEnvVar(appConfigStr);
  }
}

function getMeta(metaName: string) {
  const metas = document.getElementsByTagName("meta");

  for (let i = 0; i < metas.length; i++) {
    if (metas[i].getAttribute("name") === metaName) {
      return metas[i].getAttribute("content");
    }
  }

  return "";
}

function parseEnvVar(envVarURL: string) {
  const arrs = envVarURL.split(",");

  return arrs.reduce((pre, item) => {
    const keyValues = item.split("=");

    return {
      ...pre,
      [keyValues[0]]: keyValues[1],
    };
  }, {} as IConfig);
}

const BASE_URL = getConfig().baseURL;

const instance = axios.create({
  baseURL: BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
  timeout: 60000, // 超時時間60秒
});

最后的最后,都貼了Dockerfile了,那就一不做二不休,貼個真實場景下的ci文件源碼吧!!!

stages:
  - ship
  - deploy

ship:
  stage: ship
  image: hub-dev.rockontrol.com/rk-infrav2/gitlab-runner-buildx:0.0.0-b0450fe
  # variables:
  #   MULTI_ARCH_BUILDER: 1
  before_script:
    - echo "${DOCKER_PASSWORD}" | docker login "${DOCKER_REGISTRY}" -u="${DOCKER_USERNAME}" --password-stdin
    - BUILDKIT_NAME=node-buildkit hx buildx ci-setup
  script:
    - export PLATFORM=linux/amd64,linux/arm64
    - |
      if [[ -f ./.platform ]]; then
        source ./.platform
      else
        echo "WARNING, there is no .platform in project, USE default PLATFORM=${PLATFORM} "
      fi
    - hx buildx --with-builder --push --platform=${PLATFORM}
  tags:
    - webapp

deploy:
  stage: deploy
  script:
    - hx config
    - hx deploy
  dependencies:
    - ship
  tags:
    - webapp

6.前端應用的Kubernetes部署

Kubernetes是一個開源的容器編排平臺,可以實現自動化部署、擴展和管理容器化應用程序。以下是將前端應用部署到Kubernetes集群的步驟:

6.1 創建Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-app
  template:
    metadata:
      labels:
        app: frontend-app
    spec:
      containers:
        - name: frontend-app
          image: my-frontend-app:latest
          ports:
            - containerPort: 3000
6.2 創建Service
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer
6.3 部署到Kubernetes集群

使用kubectl命令部署應用到Kubernetes集群:

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

現在,您的前端應用已經在Kubernetes集群中運行,并可以通過LoadBalancer類型的Service外部訪問。

7.關于前端React項目架構
7.1 核心技術
  • 打包編譯 - vite
  • 包管理 - pnpm
  • 編程語言 - typescript
  • 前端框架 - react
  • 路由 - react-router
  • UI組件庫 - antd
  • cssinjs(不考慮性能開銷) - emotion
  • 全局數據共享 - zustand
  • 自動生成api - openapi
  • 網絡請求 - axios
  • 數據請求利器 - react-query
  • 通用hook(可不用) - ahooks
  • 錯誤邊界 - react-error-boundary
  • 前端日志(暫未集成) - sentry-javascript
  • hack - babel
  • 代碼檢查 - eslint
  • ts代碼檢查插件 - typescript-eslint
  • 代碼美化 - prettier
  • git鉤子 - husky
  • commit格式化 -commitlint
7.2 基于openapi自動獲取api請求函數
// src/core/openapi/index.ts

// 示例代碼
generateService({
  // openapi地址
  schemaPath: `${appConfig.baseURL}/${urlPath}`,
  // 文件生成目錄
  serversPath: "./src",
  // 自定義網絡請求函數路徑
  requestImportStatement: `/// <reference types="./typings.d.ts" />\nimport request from "@request"`,
  // 代碼組織命名空間, 例如:Api
  namespace: "Api",
});
7.3 調用接口(react-query), 支持自動loading和接口請求聯動
// HelloGet是一個基于axios的promise請求
export async function HelloGet(
  // 疊加生成的Param類型 (非body參數swagger默認沒有生成對象)
  params: Api.HelloGetParams,
  options?: { [key: string]: any },
) {
  return request<Api.HelloResp>('/gin-demo-server/api/v1/hello', {
    method: 'GET',
    params: {
      ...params,
    },
    ...(options || {}),
  });
}

// 自動調用接口獲取數據
const { data, isLoading } = useQuery({
  queryKey: ["hello", name],
  queryFn: () => {
    return HelloGet({ name: name });
  },
});

export async function HelloPost(body: Api.HelloPostParam, options?: { [key: string]: any }) {
  return request<Api.HelloResp>('/gin-demo-server/api/v1/hello', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}

// 提交編輯數據
const { mutate, isLoading } = useMutation({
  mutationFn: HelloPost,
  onSuccess(data) {
    setName(data?.data || "");
  },
  onError() {
    // 清除queryKey為hello的接口數據緩存,自動重新獲取接口數據
    queryClient.invalidateQueries({ queryKey: ['hello'] });
  }
})

mutate({ name: "lisi" });

8.前端React代碼CLI

代碼地址:https://github.com/rookie-luochao/create-vite-app-cli

9.結語

  • 介紹了gitlab-action的在前端npm領域的基本配置
  • 介紹了前端Dockerfile文件的編寫,以及pnpm在docker的優化方案,如何利用buildx生成前端多架構鏡像
  • 介紹了生產場景下前端環境變量的使用
  • 介紹了前端React項目技術架構方案
責任編輯:武曉燕 來源: 佳華云原生實踐
相關推薦

2022-08-01 07:27:36

JavaDocker容器

2017-01-10 16:04:02

容器MySQL實踐

2022-08-10 10:32:47

編程實踐

2024-09-19 08:49:13

2020-09-22 12:20:23

前端架構插件

2024-05-29 12:39:55

2020-10-10 07:14:08

前端項目斷點

2023-06-09 14:14:45

大數據容器化

2023-06-30 22:34:34

2024-08-23 10:31:14

2024-10-05 00:00:25

Cursor網站代碼

2022-07-25 14:24:53

Docker容器安全

2017-10-25 09:15:46

鏡像部署容器

2019-12-16 12:11:53

Docker容器Kubernetes

2021-04-15 08:08:48

微前端Web開發

2020-05-06 09:25:10

微前端qiankun架構

2022-07-08 11:18:33

前端實踐自動化

2021-09-08 10:32:29

微服務容器化Serverless

2022-01-17 11:41:50

前端Vite組件

2022-07-11 15:35:42

云計算銀行本文主要從銀行實際應
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产福利91精品一区二区三区 | 一区二区免费在线观看 | 免费成人在线网站 | 亚洲精品久久久蜜桃网站 | 不用播放器看的av | 一级欧美视频 | 久久久久久一区 | 亚洲自拍偷拍视频 | 亚洲精品一区二区三区 | 精品国产乱码久久久久久丨区2区 | 最近日韩中文字幕 | 久久综合入口 | 欧美精品在线播放 | 99久久精品国产一区二区三区 | 国产一区久久 | 亚洲男人天堂av | 日韩激情一区 | 欧美国产精品一区二区三区 | 7777精品伊人久久精品影视 | 成人精品国产 | 国产高清在线 | 国产精品国产三级国产aⅴ无密码 | 日韩国产免费 | 国产女人精品视频 | 中文字幕第九页 | 99视频在线免费观看 | 一区二区三区欧美在线 | 久久国产视频一区 | 中文字幕黄色大片 | av网站免费在线观看 | 婷婷久久综合 | 超碰成人在线观看 | 国产在线www | 国产精品久久久久久久久久久免费看 | 免费在线观看一区二区三区 | 欧美精品在线播放 | 色精品视频 | 精品亚洲一区二区三区 | 波多野吉衣久久 | av国产精品毛片一区二区小说 | 一色一黄视频 |