百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

前端容器化实践

bigegpt 2024-08-07 17:43 5 浏览

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 -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项目技术架构方案


相关推荐

VS Code上也能玩转Jupyter Notebook,这是一份完整教程

点击上方"码农真经"关注,星标或者置顶22点24分准时推送,第一时间送达来源:公众号机器之心|编辑:真经君码农真经(ID:coder_experience)第115次推文图源:...

看开发一款AR眼镜产品都需要那些东东?

开发AR眼镜应用时,使用的操作系统和编程语言主要取决于目标设备和开发平台。以下是主流AR眼镜的开发系统和语言总结:一、操作系统WindowsHolographic设备:微软HoloLens系列特点:...

ONNX Runtime 黑科技曝光:一行 C++ 代码让 ResNet 起飞,GitHub 已爆星

1.ONNXRuntime简介ONNXRuntime(ORT)是由微软开发的高性能推理引擎,支持跨平台(Windows/Linux/macOS)运行ONNX格式的深度学习模型。其核心优势...

海康工业相机SDK开发介绍

这篇文章,我来介绍一下海康工业相机SDK的使用方法。如果是老手,你可以跳过,如果是新手,可以看看,欢迎拍砖要使用海康的工业相机SDK,要先下载机器视觉工业相机SDKV3.2.0版本Runtime组...

深圳机器视觉相机USB3.0数字相机及千兆网口相机有什么特点?

四元数机器视觉相机的目的是将通过镜头投影到传感器的图像传送到能够储存、分析和(或者)显示的机器设备上。那么深圳机器视觉相机USB3.0数字相机及千兆网口相机有什么特点?相信不少人是有疑问的,今天深圳四...

蚁利智慧工地展厅控制系统:多媒体交互软件及中控系统开发方案

来源:蚁利科技以下是一篇关于隆兰智慧工地安全体验数字展厅多媒体互动软件及中控系统开发技术的技术方案框架,包含核心代码示例和实现思路:---#隆兰智慧工地安全体验数字展厅开发技术方案##一、系统架构...

C#程序员在工控行业到底有没有立足之地

当年忐忑的从互联网卷到工控圈时也一脸懵逼,结果发现咱C#程序员在工控界简直是万金油!今天就用我踩坑几年的经验给你们盘一盘转行路线。一、能撸哪些岗位?上位机开发工程师(工控圈入场券):天天和PLC、传感...

C#一行代码搞定OCR

本文将介绍如何使用C#代码来实现OCR功能。一、在VS中新建一个控制台应用,选择.net框架,.net6.0~8.0均可。创建好的项目如下图,然后在配置管理器中修改活动解决方案平台为x64,以及项目平...

在 C# WinForms 中 OpenCvSharp 进行边缘检测:Laplacian 算子介绍

边缘检测在计算机视觉和图像处理领域中具有非常重要的作用,它是物体识别、特征提取的基础。Laplacian算子是一种经典且常用的边缘检测方法,它对噪声非常敏感,能够很好地检测到图像中的细节和边缘。本文...

C# OpenCV机器视觉:对位贴合

在热闹非凡的手机维修街上,阿强开了一家小小的手机贴膜店。每天看着顾客们自己贴膜贴得歪歪扭扭,不是膜的边缘贴不整齐,就是里面充满了气泡,阿强心里就想:“要是我能有个自动贴膜的神器,那该多好啊,就可以让顾...

C# OpenCV机器视觉:缺陷检测

在一个阳光明媚的早晨,阿强正准备享受他的一杯咖啡,突然接到了老板的电话。“阿强,我们的生产线出现了问题!有几个产品的质量不合格,客户投诉不断!你能不能想办法解决这个问题?”阿强一听,心中一紧,随即灵光...

CHAPTER II 第二章

CHAPTERIICHAPTERII第二章Iresistedalltheway:anewthingforme...

万字图文,将&quot;类加载器&quot;与&quot;双亲委派机制&quot;一网打尽

引子大家想必都有过平时开发springboot项目的时候稍微改动一点代码,就得重启,就很烦网上一般介绍2种方式spring-boot-devtools,或者通过JRebel插件来实现"...

Java:Java中的微服务

  如果你想使用Java中的云原生微服务快速实现大规模可扩展性,那么不需要重新发明轮子。一些创新工具可以帮助你。通过Java培训课程,在Java(软件开发中最常用的编程语言)方面打下坚实的基础,更好地...

&quot;类加载器&quot;与&quot;双亲委派机制&quot;一网打尽

引子大家好,我是呼噜噜,大家想必都有过平时开发springboot项目的时候稍微改动一点代码,就得重启,就很烦网上一般介绍2种方式spring-boot-devtools,或者通过JRebel插件...