当前位置: 首页 > 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…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...