当前位置: 首页 > news >正文

漫谈HBuilderX App-Jenkins热更新构建

漫谈Uniapp App热更新包-Jenkins CI/CD打包工具链的搭建

零、写在前面

HBuilderX是DCloud旗下的IDE产品,目前只提供了Windows和Mac版本使用。本项目组在开发阶段经常需要向测试环境提交热更新包,使用Jenkins进行CD是非常有必要的一步。尽管HBuilderX提供了CLI,但Jenkins服务通常都是搭建在Linux环境下的。当前的Uniapp wgt打包服务是使用了Windows Server + HBuilderX CLI的解决方案来进行打包,再用Jenkins远程调用接口。这套方案的弊病有如下几点:

  • Jenkins侧仅负责少量参数的传递,如项目名、Git repo地址、分支名等,大部分流程不受控制,流水线的构建阶段显示不透明。
  • 核心由一个shell script和一个python脚本实现,代码逻辑存在一定重复,维护难度也比较高。
  • 从Git更新代码的流程耗费较长时间,因为每次执行流水线都要删除掉本地的repo并重新拉取。这对于带宽只有个位数的公网测试服务器来说是致命的,每次构建花费在此步的时间就有2分钟以上。
  • 后端平台侧获取的Token没有缓存,即使打包提速了也会受到验证码获取1分钟节流的限制。
  • Windows服务器上运行的HBuilderX经常出现登录态失效或启动打包任务失败的情况,测试在Jenkins侧只能得到简单的任务失败提示。而且这个提示是出现在Git拉取代码之后的,意味着每次失败前都要干等2分钟。

为了避免服务器资源的浪费,节省不必要的维护开支,我决定在等待测试的gap期研究一下这套流程的优(chong)化(gou)。

一、初次优化-Windows下脱离HBuilderX主程序

打包指令提取

首先从Windows端下手,先打开HBuilderX并登录账号,正常打一个wgt包。在打包前使用DebugView来查看HBuilderX执行任务时的输出,这是一款用于捕获Windows桌面系统程序中由TRACE和OutputDebugString输出的信息的工具。抓取到的有效日志如下:

[16764] 2023-05-16 09:50:20.583 [INFO:] node "D:/HBuilderX/plugins/node/node.exe"
[16764] 2023-05-16 09:50:20.583 [INFO:] args ("--max-old-space-size=2048", "--no-warnings", "D:/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/vite-plugin-uni/bin/uni.js")

盘一下uni.js

require('../dist/cli/index.js')

可以看到备注很完善的函数cli,这证明Uniapp打包使用的工具就是由node构建的,原则上来说可以不受系统平台的限制,移植到Linux上使用是没有问题的。至于HBuilderX软件本体就是Qt写的一个壳子,只要剖析整个打包流程就可以脱离HBuilderX本体了。

cli.command('build').option('--outDir <dir>', `[string] output directory (default: dist)`).option('--assetsInlineLimit <number>', `[number] static asset base64 inline threshold in bytes (default: 4096)`).option('--sourcemap', `[boolean] output source maps for build (default: false)`).option('--manifest', `[boolean] emit build manifest json`).option('--ssrManifest', `[boolean] emit ssr manifest json`).option('--emptyOutDir', `[boolean] force empty outDir when it's outside of root`, {default: true,
}).option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`).action(action_1.runBuild);

根据参数表整理一下调用命令:

node --max-old-space-size=2048 --no-warnings "D:/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/vite-plugin-uni/bin/uni.js" build --platform app --outDir "D:/test/crp-app-dist"

直接执行上述命令是不行的,因为uniapp的node_modules下根本就没有编译该工程所需的各种依赖,并且缺少很多环境变量。具体逻辑在uniapp-cli-vite/node_modules/@dcloudio/vite-plugin-uni/dist/cli/build.js/dist/index.js中都有所表现。

显而易见的是,我们的工程目录下(非cli模式)根本就没有vuepinia之类的包,那么上面的命令是如何确定工作目录(工程目录)和其它依赖所在地的呢?先说其它依赖的问题,分析plugins目录可以看到HBuilderX自带了一套nodenpm,那么去看一下npm的脚本(在Windows版本叫做npm.cmd):

#!/bin/sh
(set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix
basepath=$(cd `dirname $0`; pwd)
plugin_dir=$(dirname $basepath)which "node" >/dev/null 2>&1
if ! [ $? -eq 0 ]; thennode_Path=$plugin_dir/nodenew_path=$PATH:$node_Pathexport PATH=$new_path 
fi
$basepath/node_modules/npm/bin/npm-cli.js $@

环境变量修补

HBuilderX在打包阶段会使用自己的nodenpm,我们要做的就是修补环境变量使得这些工具都能找到正确的工作目录。以下是一个使用node.js成功在Windows下脱离HbuilderX主程序调用打包的例子:

/*** @author myd*/
import {exec} from "child_process";
import * as util from "util";
import path from "path";
import os from "os";const execAsync = util.promisify(exec);
export const build = (repoName: string) => {const systemTempFolderPath = os.tmpdir();return new Promise(async (resolve, reject) => {try {const HBUILDER_DIR = "D:\\HBuilderX";const UNI_INPUT_DIR = path.join(systemTempFolderPath, repoName);const VITE_ROOT_DIR = UNI_INPUT_DIR;const UNI_HBUILDERX_PLUGINS = path.join(HBUILDER_DIR, 'plugins');const UNI_CLI_CONTEXT = path.join(UNI_HBUILDERX_PLUGINS, 'uniapp-cli-vite');const UNI_NPM_DIR = path.join(UNI_HBUILDERX_PLUGINS, 'npm');const UNI_NODE_DIR = path.join(UNI_HBUILDERX_PLUGINS, 'node');const NODE_ENV: any = 'production';const NODE = path.join(UNI_NODE_DIR, 'node');const UNI_CLI = path.join(UNI_CLI_CONTEXT, 'node_modules', '@dcloudio', 'vite-plugin-uni', 'bin', 'uni.js');const PATH_ADDONS = process.env.PATH + `;${UNI_INPUT_DIR}/node_modules/.bin;`;const childEnv = {...process.env,PATH: PATH_ADDONS,HBUILDER_DIR,UNI_INPUT_DIR,VITE_ROOT_DIR,UNI_CLI_CONTEXT,UNI_HBUILDERX_PLUGINS,UNI_NPM_DIR,UNI_NODE_DIR,NODE_ENV,NODE};process.chdir(UNI_CLI_CONTEXT);const buildCommand = `"${NODE}" --max-old-space-size=2048 --no-warnings "${UNI_CLI}" build --platform app --outDir ${path.join(systemTempFolderPath, repoName + '-dist')}`const {stdout, stderr} = await execAsync(buildCommand, {env: {...childEnv}});console.error('stderr:', stderr);resolve(1)} catch (error) {console.error('Error during build:', error);reject(0)}})
}

产物是一个文件夹,把这个文件夹以zip格式压缩后,将后缀重命名为wgt即可。

做到这一步后,我就用Next.js写了一个简单的GUI,并配合一系列辅助逻辑完成了beta版的新构建平台,部署到先前的Windows服务器上提供给同事进行测试。这样做的目的主要是为了验证以上工作的正确性和稳定性,还可以从反馈意见中思考一下我对于该流程的重构设想是否正确,还有哪些点没有考虑到。

二、第二次优化-移植到Linux上并集成回Jenkins

思考

首先明确一点,这件事本身就是一个内部需求的解决方案延伸,闭门造车是不可取的。当做出一个阶段性的工作后,立刻部署demo并持续收集同事的反馈意见,及时调整,这样才能避免后续的更多问题,因为最终用户不止是我本人,还有所有参与项目的其他同事。软件的易用性和稳定性同样重要,必须在正式部署前反复地进行预先测试才能推行使用。

在测试了一天后,同事认为打包速度大大提高,但简陋的web控制台不能同时执行多个打包任务,打包期间不能刷新或离开页面也是硬伤。当时我还没有想好如何把这套代码搬到Linux上跑,因为这涉及HBuilderX的平台差异问题。将这套代码的构建核心抽出来集成回Jenkins是最佳选择,但我当时是有一些偷懒的想法的,因为这套轮子也同样提供了工程选择、Git同步的处理逻辑等,已经解决了上面的大部分痛点。但轮子毕竟是轮子,最终我还是决定放弃推行自己的平台给大家使用的想法,而是以这个平台作为测试工具,在此之上研究将HBuilderX的打包器迁移到Linux上的方法。

uniapp-cli-vite承担了大部分打包功能,按理说它作为一个node包本来是不挑系统的。但我陷入了思维上的误区,一定要迁移nodenpm再给plugins搬家。实际上只要node环境隔离得当,只迁移npm即可。而对于npm来说,Linux和Windows的npm结构差异较大,但几乎可以直接使用macOS的包。所以迁移工作我选择在macOS平台下研究。

编写脚本&部署

明确一下思路,只要在macOS下写一个接受工程目录的路径、打包产物的路径和HBuilderX plugins的路径,输出打包产物的脚本并测试成功,那么就算成功了80%了。把上面的node函数用GPT转成sh脚本,自己再微调一下:

#!/bin/bash# HBuilder目录修改此处
HBUILDER_DIR=/root/HBuilderX
NODE_ENV=production
repoDir=$1
# 导出目录修改此处
distExportDir=$2
# Nodejs修改此处
NODE=/root/HBuilderX/plugins/node/nodeUNI_INPUT_DIR="$repoDir"
VITE_ROOT_DIR="$UNI_INPUT_DIR"
UNI_HBUILDERX_PLUGINS="$HBUILDER_DIR/plugins"
UNI_CLI_CONTEXT="$UNI_HBUILDERX_PLUGINS/uniapp-cli-vite"
UNI_NPM_DIR="$UNI_HBUILDERX_PLUGINS/npm"
UNI_NODE_DIR="$UNI_HBUILDERX_PLUGINS/node"
UNI_CLI="$UNI_CLI_CONTEXT/node_modules/@dcloudio/vite-plugin-uni/bin/uni.js"export HBUILDER_DIR
export UNI_INPUT_DIR
export VITE_ROOT_DIR
export UNI_CLI_CONTEXT
export UNI_HBUILDERX_PLUGINS
export UNI_NPM_DIR
export UNI_NODE_DIR
export NODE_ENV
export NODE
export PATH="$PATH:$UNI_INPUT_DIR/node_modules/.bin"cd "$UNI_CLI_CONTEXT"
buildCommand="$NODE --max-old-space-size=2048 --no-warnings $UNI_CLI build --platform app --outDir $distExportDir/${repoDir}-dist"
eval $buildCommand
exitCode=$?
if [ $exitCode -eq 0 ]; thenecho "Build successful"exit 1
elseecho "Error during build"exit 0
fi

效果很好,先把HBuilderX的主目录打包为tar并传到Linux服务器上展开:

tar -cf ~/HbuilderX-3.9.5-darwin.tar /Applications/HBuilderX.app/Contents/HBuilderX
scp -P 22 -r ~/HbuilderX-3.9.5-darwin.tar root@192.168.1.252:/root/
tar -xf ./HbuilderX-3.9.5-darwin.tar

在macOS机器上看一下HBuilderX使用的Node版本:

myd@myddeMac-Pro ~ % /Applications/HBuilderX.app/Contents/HBuilderX/plugins/node/node -v
v16.17.0

为Linux服务器下载对应系统和架构的Node二进制包并覆盖HBuilderX所使用的node,这里以Linux-amd64-16.17.0为例:

wget https://nodejs.org/download/release/v16.17.0/node-v16.17.0-linux-x64.tar.gz
tar -xzvf ./node-v16.17.0-linux-x64.tar.gz
cp ./node-v16.17.0-linux-x64/bin/node ./HBuilderX/plugins/node
chmod +x ~/HBuilderX/plugins/node/node

更多版本可以在https://nodejs.org/download/release/查看。

公司服务器的Ubuntu 18.04缺少node 18的依赖glibc-2.28,因此需要进一步对系统环境进行修补。编译glibc-2.28

sudo apt-get install g++ make gcc bison
apt install -y gawk
cd ~
wget -c https://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz
tar -zxf glibc-2.28.tar.gz
cd glibc-2.28
mkdir glibc-build
cd glibc-build
../configure --prefix=/opt/glibc-2.28
make -j 6
make install
cd ~
rm -rf ./glibc-2.28 ./glibc-2.28.tar.gz
apt install -y patchelf
# 直装的node使用如下命令
patchelf --set-interpreter /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 --set-rpath /opt/glibc-2.28/lib/:/lib/x86_64-linux-gnu/:/usr/lib/x86_64-linux-gnu/ /usr/local/bin/node
# nvm使用如下命令
patchelf --set-interpreter /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 --set-rpath /opt/glibc-2.28/lib/:/lib/x86_64-linux-gnu/:/usr/lib/x86_64-linux-gnu/ /root/.nvm/versions/node/v18.18.2/bin/node
# 记得修补HBuilderX的node
patchelf --set-interpreter /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 --set-rpath /opt/glibc-2.28/lib/:/lib/x86_64-linux-gnu/:/usr/lib/x86_64-linux-gnu/ ~/HBuilderX/plugins/node/node

接下来修改uniapp-cli-vitepackage.json

vi /root/HBuilderX/plugins/uniapp-cli-vite/package.json

删除devDependencies节点下的@esbuild/darwin-arm64@esbuild/darwin-x64fsevents,并安装对应目标平台的esbuild

npm i -D -f @esbuild/linux-x64@0.17.19
npm i -f

领导非常支持这件事,抽出时间将上面的脚本集成回了Jenkins。我也将之前对于几个痛点的思考和解决方案提供了出来,至此整个wgt打包流程得到了巨大的优化,无论是速度还是稳定性。

三、第三次优化-封装Docker进行环境隔离

思考

在第二次优化的过程中,因为出了修补glibc这档子事,我意识到环境隔离非常重要。Ubuntu 18.04作为LTS版本,至今仍在广泛使用。这种不算特别老的系统尚且出现环境导致的兼容性问题,假如我要在一个使用musl库的发行版上部署,比如Alpine Linux或者Gentoo Linux的时候又该怎么办呢?

显然,解决这个问题的最好办法就是封装Docker镜像。

精简HBuilderX包

封装Docker镜像自然是产物体积越小越好。HBuilderX macOS版在安装若干打包所需依赖后,主目录膨胀到2个多G,这里的大多数文件都是用不上的。在macOS下复制一份HBuilderX主程序目录,开搞:

  • 只有plugins文件夹需要保留,HBuilderX主程序及其相关的文件完全用不上。删之。
  • plugins文件夹下的大部分包也用不上,比如为HBuilderX提供代码补全、语法检查之类的IDE所需包,或者项目中根本没有使用到的UTS相关包,统统删之。
  • 每删几个包,就执行一遍之前的sh构建脚本,将HBuilderX目录指向当前魔改的目录下,进行可用性测试。
  • 删到最后,只留下aboutcomplie-dart-sassnodenpmuniapp-cli-vitenode_modules这几个目录。
  • 鲁迅说过,node_modules的体积比珠穆朗玛峰还要大,结构比马里亚纳海沟还要深。提取出node_modules目录下的package.jsonpackage-lock.json,复制到plugins目录下,再将node_modules删之。
  • 根据上文的步骤,修改一下uniapp-cli-vitepackage.json

精简以后压缩一下,原来2个多G的plugins只剩7.2M。将压缩包命名为core-3.9.5.zip备用。

Docker封装前的思考和一些选择

既然要做Docker镜像,那么Docker镜像的系统就最好选个轻量点的。Alpine Linux只有50多M,采用APK包管理器,是一个非常理想的选择。然而必须注意的是,Alpine Linux的libc实现使用的是musl而非常用的glibc。但我们既然是为了封装HBuilderX的专属镜像,那nodejs版本理应和原环境一样,也就是v16.17.0以保障兼容性,但是APK包管理器只能下载latest版本的nodejsnodejs又依赖于glibc。从源码编译不太现实,所以这里使用多阶段构建的方法,先通过nodejs官方提供的node-alpine来获取可用的nodejs二进制文件,再进行后续的操作。

至于封装好的镜像如何使用,我的解决方案是启动一个小型的HTTP API服务。为了减少不必要的依赖,这个服务也使用node来写。服务器应该向镜像挂载一个项目文件夹,通过API调用进行打包。Git代码同步之类的操作全部交给Jenkins侧执行,Docker容器只负责最核心的打包部分——如无必要,勿增实体。

编写Dockerfile

以下是开发阶段的Dockerfile:

ARG NODE_VERSION=16.17.1
ARG ALPINE_VERSION=3.18
FROM node:${NODE_VERSION}-alpine AS node
ARG ALPINE_VERSION
FROM alpine:${ALPINE_VERSION}
ENV API_SERVER_URL=https://gitclone.com/github.com/hbuilderx-vanilla/api-server.git# Set China APK Manager Mirrors
RUN echo "https://mirrors.aliyun.com/alpine/v3.18/main/" > /etc/apk/repositories \&& echo "https://mirrors.aliyun.com/alpine/v3.18/community/" >> /etc/apk/repositories
RUN apk update && apk add --no-cache bash unzip wget git# Install node-16.17.1
COPY --from=node /usr/lib /usr/lib
COPY --from=node /usr/local/lib /usr/local/lib
COPY --from=node /usr/local/include /usr/local/include
COPY --from=node /usr/local/bin /usr/local/bin# Set China NPM Mirrors
RUN npm install -g yarn --force \ && npm config set registry https://registry.npmmirror.com# Inject HBuilderX 3.9.5 core
COPY core-3.9.5.zip /opt/
RUN unzip /opt/core-3.9.5.zip -d /opt/ \&& rm /opt/core-3.9.5.zip && mkdir /projects# Install HBuilderX core dependencies
COPY core-install.sh /root/
RUN chmod +x /root/core-install.sh
# Need manual run it if minimal version
# RUN /root/core-install.sh# Install and start api server
WORKDIR /root
RUN git clone ${API_SERVER_URL} && \cd api-server && \npm i
EXPOSE 3000
CMD [ "node","/root/api-server/index.js" ]

我把HTTP API服务单独做了一个Git仓库出来,这样方便后续扩充功能和进行bugfix。在开发环境下,一些反向代理和镜像源的设置是必不可少的。为了减少Docker镜像体积,我选择在镜像运行后再让用户手动安装HBuilderX的巨型npm依赖。

容器的部署和初始化

容器启动示例:

docker run -d --restart=always -v /<user_name>/<projects_folder>:/projects -p 13300:3000 --name hbuilder-vanilla flymyd114/hbuilderx-vanilla:latest
  • /<user_name>/<projects_folder>是本机的待打包工程父目录,你的所有工程均应处于该目录下,如/Users/myd/projects下有hello-uniapp文件夹。
  • 13300为建议的API端口映射点。

容器首次启动后,执行如下命令以初始化依赖:

docker exec -it <docker_id> /bin/sh
chmod +x /root/core-install.sh && /root/core-install.sh
exit

访问http://127.0.0.1:13300以检查API服务是否正确启动。

打包示例:

curl --location 'http://localhost:13300/build?project=crp-app'

产物将会在/projects/crp-app/wgt-dist中生成。

发布到Github上并编写workflows

可以看到,上面的容器已经被发布到了DockerHub。同样的,我也将仓库开源并编写了workflows以自动构建新版本的Docker镜像。以下是docker-build.yml

name: Build and Push Docker imageon:push:branches:- maintags:- 'v*'paths:- 'Dockerfile'workflow_dispatch:jobs:build-and-push:runs-on: ubuntu-lateststeps:- name: Check out the repouses: actions/checkout@v2- name: Set up Docker Buildxuses: docker/setup-buildx-action@v1- name: Log in to Docker Hubuses: docker/login-action@v1with:username: ${{ secrets.DOCKER_HUB_USERNAME }}password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}- name: Extract metadata (tags, labels) for Dockerid: metauses: docker/metadata-action@v3with:images: flymyd114/hbuilderx-vanillatags: |type=semver,pattern={{version}}- name: Build and push Docker imageuses: docker/build-push-action@v2with:context: .file: ./Dockerfilepush: truetags: ${{ steps.meta.outputs.tags }}labels: ${{ steps.meta.outputs.labels }}

当main分支上的commit被打上形如v3.9.5的Tag时即可触发workflows。当然,也可以手动触发这个构建。

注意,在Github上发布的Dockerfile应该去除镜像源和反代的部分,否则构建速度反而会变慢:

ARG NODE_VERSION=16.17.1
ARG ALPINE_VERSION=3.18
FROM node:${NODE_VERSION}-alpine AS node
FROM alpine:${ALPINE_VERSION}
ENV API_SERVER_URL=https://github.com/hbuilderx-vanilla/api-server.gitRUN apk update && \apk add --no-cache bash unzip wget git && \rm -rf /var/cache/apk/*COPY --from=node /usr/lib /usr/lib
COPY --from=node /usr/local/lib /usr/local/lib
COPY --from=node /usr/local/include /usr/local/include
COPY --from=node /usr/local/bin /usr/local/bin
RUN npm install -g yarn --forceCOPY core-3.9.8.zip /opt/
RUN unzip /opt/core-3.9.8.zip -d /opt/ && \rm /opt/core-3.9.8.zip && \mkdir /projectsCOPY core-install.sh /root/
RUN chmod +x /root/core-install.sh# Need manual run it if minimal version
# RUN /root/core-install.shWORKDIR /root
RUN git clone ${API_SERVER_URL} && \cd api-server && \npm i
EXPOSE 3000
CMD [ "node","/root/api-server/index.js" ]

后续的版本升级

公司的项目目前统一使用HBuilderX 3.9.5的基座。但随着未来的升级,我们也需要及时更新打包核心以兼容高版本基座。天下没有不散的筵席,这篇文章既是我对这项工作的一个总结归纳,也是为后来人提供一个维护的文档和应对版本改变的思路。对于plugins文件夹下的依赖来说,我们只需要在一台电脑上将HBuilderX升级到想要的版本,然后提取里面的package.json进行依赖版本的替换就好。通常来说,要更新的package.json涉及aboutuniapp-cli-vite和根目录。更新后重新将plugins放到core文件夹下打包,同步修改Dockerfile内的zip名即可。

以3.9.5升级至3.9.8举例,修改core/plugins内的如下文件:

about/package.json
uniapp-cli-vite/package.json
package.json

然后压缩core文件夹,重命名为core-3.9.8.zip。修改Dockerfile

COPY core-3.9.8.zip /opt/
RUN unzip /opt/core-3.9.8.zip -d /opt/ && \rm /opt/core-3.9.8.zip && \mkdir /projects

Github项目地址

Docker项目:https://github.com/hbuilderx-vanilla/docker

API项目:https://github.com/hbuilderx-vanilla/api-server

联系我

邮箱:flymyd@foxmail.com

或在上方项目处提issue,@flymyd

相关文章:

漫谈HBuilderX App-Jenkins热更新构建

漫谈Uniapp App热更新包-Jenkins CI/CD打包工具链的搭建 零、写在前面 HBuilderX是DCloud旗下的IDE产品&#xff0c;目前只提供了Windows和Mac版本使用。本项目组在开发阶段经常需要向测试环境提交热更新包&#xff0c;使用Jenkins进行CD是非常有必要的一步。尽管HBuilderX提…...

技术前沿丨Teranode如何实现无限扩容

​​发表时间&#xff1a;2023年9月15日 BSV区块链协会的技术团队目前正在努力开发Teranode&#xff0c;这是一款比特币节点软件&#xff0c;其最终目标是实现比特币的无限扩容。然而&#xff0c;正如BSV区块链协会网络基础设施负责人Jake Jones在2023年6月举行的伦敦区块链大会…...

世岩清上:如何制作年终工作汇报宣传片

年终工作汇报宣传片是一种以视觉和口头语言为主要表现形式的宣传手段&#xff0c;旨在向领导、同事、客户等展示一年来的工作成果和亮点。以下是制作年终工作汇报宣传片的几个关键步骤&#xff1a; 明确目的和受众&#xff1a;在制作宣传片前&#xff0c;要明确宣传片的目的和受…...

练习十一:简单卷积器的设计

简单卷积器的设计 1&#xff0c;任务目的&#xff1a;2&#xff0c;明确设计任务2.1,目前这部分代码两个文件没找到&#xff0c;见第5、6节&#xff0c;待解决中。 &#xff0c;卷积器的设计&#xff0c;RTL&#xff1a;con1.v4&#xff0c;前仿真和后仿真&#xff0c;测试信号…...

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…...

微服务实战系列之EhCache

前言 书接前文&#xff0c;继续深耕。上一篇博主对Redis进行了入门级介绍&#xff0c;大体知道了Redis可以干什么以及怎么使用它。 今日博主继续带着大家学习如何使用EhCache&#xff0c;这是一款基于Java的缓存框架。 微服务实战系列之Redis微服务实战系列之Cache微服务实战…...

SpringBoot:SpringMVC(上)

文章目录 前言一、SpringMVC是什么&#xff1f;1.1 MVC的定义&#xff1a;1.2 MVC 和 Spring MVC 的关系 二、Spring MVC 创建和连接2.1创建springmvc2.2接下来&#xff0c;创建⼀个 UserController 类&#xff0c;实现⽤户到 Spring 程序的互联互通&#xff0c;具体实现代码如…...

一文搞懂Go语言中包导入

一文搞懂Go语言中包导入 定义包 在go语言中&#xff0c;定义包的关键字为package&#xff0c;如package main等&#xff0c;在go语言中有一个约定俗成的标准&#xff0c;那就是包名与目录名把持一致。 //service目录下 package servicepackage utils 可以看到&#xff0c;我…...

Vue2学习笔记(事件处理)

一、基本使用 1.使用v-on:xxx 或 xxx 绑定事件&#xff0c;其中xxx是事件名&#xff1b;2.事件的回调需要配置在methods对象中&#xff0c;最终会在vm上&#xff1b;3.methods中配置的函数&#xff0c;不要用箭头函数&#xff01;否则this就不是vm了&#xff1b;4.methods中配…...

【2023第十二届“认证杯”数学中国数学建模国际赛】A题 太阳黑子预报完整解题思路

A题 太阳黑子预报 题目任务思路分析第一问第二问第三问 题目 太阳黑子是太阳光球上的一种现象&#xff0c;表现为比周围区域更暗的临时斑点。它们是由于磁通量集中而导致表面温度降低的区域&#xff0c;磁通量的集中抑制了对流。太阳黑子出现在活跃区域内&#xff0c;通常成对…...

Huawei FusionSphere FusionCompte FusionManager

什么是FusionSphere FusionSphere 解决方案不独立发布软件&#xff0c;由各配套部件发布&#xff0c;请参 《FusionSphere_V100R005C10U1_版本配套表_01》。 目前我们主要讨论FusionManager和FusionCompute两个组件。 什么是FusionCompte FusionCompute是华为提供的虚拟化软…...

GSLB是什么?谈谈对该技术的一点理解

GSLB是什么&#xff1f;它又称为全局负载均衡&#xff0c;是主流的负载均衡类型之一。众所周知&#xff0c;负载均衡位于服务器的前面&#xff0c;负责将客户端请求路由到所有能够满足这些请求的服务器&#xff0c;同时最大限度地提高速度和资源利用率&#xff0c;并确保无任何…...

【接口测试】POST请求提交数据的三种方式及Postman实现

1. 什么是POST请求&#xff1f; POST请求是HTPP协议中一种常用的请求方法&#xff0c;它的使用场景是向客户端向服务器提交数据&#xff0c;比如登录、注册、添加等场景。另一种常用的请求方法是GET&#xff0c;它的使用场景是向服务器获取数据。 2. POST请求提交数据的常见编…...

SpringBoot系列之集成Jedis教程

SpringBoot系列之集成Jedis教程&#xff0c;Jedis是老牌的redis客户端框架&#xff0c;提供了比较齐全的redis使用命令&#xff0c;是一款开源的Java 客户端框架&#xff0c;本文使用Jedis3.1.0加上Springboot2.0&#xff0c;配合spring-boot-starter-data-redis使用&#xff0…...

centos用什么命令可查看版本号

概述 查版本号的命令&#xff1a;1、“cat /etc/redhat-release”&#xff0c;可输出centos版本号&#xff1b;2、“cat /proc/version”、“uname -a”或“uname -r”&#xff0c;可输出内核版本号。 本教程操作环境&#xff1a;centos7.9.2009系统。 查看centos版本 [roo…...

大数据之Redis

NoSQL SQL数据库泛指关系型数据库NoSQL不拘泥于关系型数据的设计范式&#xff0c;放弃了通用的技术标准&#xff0c;为某一特定领域场景而设计 NoSQL的特点 不遵循SQL标准不支持ACID远超SQL的性能 NoSQL的适用场景 对数据高并发的读写海量数据的读写对数据高可扩展性的 N…...

【React设计】React企业级设计模式

Image Source : https://bugfender.com React是一个强大的JavaScript库&#xff0c;用于构建用户界面。其基于组件的体系结构和构建可重用组件的能力使其成为许多企业级应用程序的首选。然而&#xff0c;随着应用程序的规模和复杂性的增长&#xff0c;维护和扩展变得更加困难。…...

赴日程序员高年薪过上“躺平”生活?

日本的IT行业想要达到的高薪&#xff0c;也是需要很多资历和经验的&#xff0c;不过即使你是新卒&#xff0c;也能拿到相比国内来说让你满意的薪资。 刚入职的起薪是20-23万日元/月&#xff0c;情报信息业出身&#xff0c;技术掌握不错&#xff0c;起薪是25万-30万日元。之后经…...

Windows开启SQL Server服及1433端口

需求&#xff1a;Windows开启SQL Server服务及1433端口 目前端口没有启动 解决&#xff1a; 打开SQL Server配置管理器&#xff08;winR&#xff09; 各个sqlserver版本在textbox中输入对应的命令如下&#xff1a; SQLServerManager15.msc&#xff08;对于 SQL Server 2019 &am…...

网盘系统设计:万亿 GB 网盘如何实现秒传与限速?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 网盘&#xff0c;又称云盘&#xff0c;是提供文件托管和文件上传、下载服务的网站&#xff08;File hostingservice&#xff09;。人们通过网盘保管自己拍摄的照片、视频&#xff0c;通过网盘和他人共享文件&#xff…...

整数和浮点数在内存中的存储

文章目录 每日一言整数在内存中的存储方式浮点数在内存中的存储结语 每日一言 You just can’t beat the person who never gives up. 你无法打败那位永不放弃的人。 整数在内存中的存储方式 整数在内存中的存储方式通常采用二进制形式&#xff0c;即将整数的数值转化为二进制…...

rabbitMQ镜像队列的使用

在rabbitMQ集群中&#xff0c;默认发送消息时&#xff0c;队列默认时在一个节点上存在的。 我们以node01 node02 node03三节点集群为例&#xff0c;在node01声明队列发送消息后&#xff0c;发现&#xff1a; 测试队列只在节点node01上出现。 我们手动停止node01后&#xff0c…...

ros来保存图像和保存记录视频的方法---gmsl相机保存视频和图片

1,保存图片 rosrun image_view image_view image:=/myimg_topic这个命令只是用来查看图像的,它并不会保存图像。如果你想要保存图像,你需要使用image_saver节点,并指定保存路径。例如: 下面指令就可以了,可以用 rosrun image_view image_saver image:=/myimg_topic _fi…...

Oracle19c使用adrci清理日志文件

Oracle中通常有好多日志文件&#xff0c;遇到异常情况会产生大量日志&#xff0c;造成磁盘空间紧张。 故需要清理对应文件。包括trace文件&#xff0c;incident文件&#xff0c;listener log文件等。 19c中oracle提供了一个ADRCI的命令行工具来查看ADR中的alert日志和trace信息…...

Ubuntu之Sim2Real环境配置(坑居多)

不要一上来就复制哦&#xff0c;因为很多下面的步骤让我走了很多弯路&#xff0c;如果可能的话&#xff0c;我会重新整理再发出来 前提&#xff1a; 参考教程 Docs 创建工作空间(不用跟着操作&#xff0c;无用&#xff09; 1.创建sim2real server container 1.尝试创建sim2r…...

java中BigDecimal里面的subtract函数的意思?

在Java中&#xff0c;BigDecimal类提供了一个名为subtract()的函数&#xff0c;用于执行两个BigDecimal对象的减法操作。该函数返回一个新的BigDecimal对象&#xff0c;表示两个操作数相减的结果。 下面是BigDecimal.subtract()函数的用法示例&#xff1a; java Copy code im…...

线程变量引发的session混乱问题

最近不是在救火&#xff0c;就是在救火的路上。 也没什么特别可写的&#xff0c;今天记录下最近遇到的一个问题&#xff0c;个人觉得挺有意思&#xff0c; 待有缘人阅读 言归正传&#xff0c;售后反馈&#xff1a; 营业查询中付款方式为第三方支付的几条银行缴费&#xff0c;创…...

dockerfile与docker-compose解释及对比

Dockerfile 是一个文本文件&#xff0c;用于定义单个Docker镜像的构建过程和配置。它包含了一系列的指令&#xff0c;如FROM、RUN、COPY、CMD等&#xff0c;按照顺序执行这些指令来构建镜像。Dockerfile可以定义容器的基础镜像、安装依赖软件、拷贝文件、运行命令等操作。通过…...

数据库更换版本

目录 0.前言 1.官网下载MySQL 2.配置初始化文件my.ini 3.初始化MySQL 4.安装mysql服务并启动修改密码 5.配置环境变量​编辑 0.前言 心累&#xff0c;为了完成实验&#xff0c;必须使用8.0版本导致我更新版本的时候&#xff0c;把sqlyog干崩溃了&#xff0c;什么版本不兼…...

Unity Meta Quest 一体机开发(九):【手势追踪】通过录制抓取手势实现自定义抓取姿势

文章目录 &#x1f4d5;教程说明&#x1f4d5;录制前的准备&#x1f4d5;第一种录制方法&#xff08;Hand Grab Pose Tool 场景&#xff09;⭐在运行模式中确认录制⭐保存录制的手势&#xff0c;将物体做成 Prefab⭐在编辑阶段调整抓取手势&#x1f50d;Fingers Freedom&#x…...

个人网站毕业论文/黄页88网站推广方案

Mode Decision(模式选择)决定一个宏块以何种类型进行分割。宏块的分割类型有以下几种&#xff1a; 12345678910111213141516171819202122232425//P_Skip and B_Skip means that nothing need to be encoded for this macroblock ,// just use the mv predicted to restruct …...

专门做调查问卷的网站/seo销售是做什么的

mysql workbench 导出建表结构 导出数据&#xff0c;直接上图&#xff0c;gif简示图&#xff1b;...

代做计算机毕业设计网站/网站名查询网址

昨天去SJTU参加Google暑期实习海选。卷子发下来一看全是数据结构、算法&#xff0c;虽然这两个我都考过80几还可以。。但都忘得差不多了。 前面选择题大都是读读程序&#xff0c;只有算复杂度的问题我比较郁闷&#xff0c;因为当年就没有认真算过复杂度。。考试时候填的那些O(L…...

可以做公司宣传的网站有哪些/互联网营销师证书含金量

Log4J是Apache的一个开放源代码项目(http://logging.apache.org/log4j/docs/)&#xff0c;它是一个日志操作包。通过使用Log4J,可以指定日志信息输出的目的地&#xff0c;控制每一条日志的输出格式&#xff0c;定义日志信息的级别。所有这些功能通过一个配置文件灵活进行配置。…...

从零学建设网站/店铺在百度免费定位

目录1 增加tag1.1 当前commit增加tag1.2 给指定&#xff08;过去&#xff09;commit增加tag2 提交tag3 删除tag3.1 删除本地tag3.2 删除远程仓库tag4 查看本地tag5 fatal: tag xxx already exists1 增加tag 1.1 当前commit增加tag git add . git commit -m 提交信息之后 git …...

django网站开发源码/持啊传媒企业推广

推荐博客&#xff1a;付铭 day-01 HTML 1、HTML 基本语法 html标签 单标签 <img /> 、<img> 双标签 <html> </html> 属性 属于标签 <img src"图片的地址"><table width"100" height"200"></table> 1…...