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

如何编写一个自己的web前端脚手架

脚手架简介

脚手架是创建前端项目的命令行工具,集成了常用的功能和配置,方便我们快速搭建项目,目前网络上也有很多可供选择的脚手架。

一个"简单脚手架"的构成其实非常少,即 代码模板 + 命令行工具。其中代码模板是脚手架需要生成给用户的工程代码,命令行工具的作用是提供命令行界面,根据用户输入的信息和代码模板生成工程。

代码模板根据脚手架功能的不同而不同,但是不同脚手架命令行工具相似性较大。今天就来聊一下如何编写一个脚手架。

脚手架使用方式

先看一下我们编写的脚手架的使用方式。

启动脚手架

npm create xxx
# 或者
npx create-xxx

其中npx方式可以接收更多参数:

# 查看脚手架版本
npx create-xxx -v
# 指定生成的工程名称
npx create-xxx -n appname

命令行执行命令后,会自动下载我们的create-xxx包,并执行。

命令行界面和生成代码

脚手架启动后,会出现一个交互式命令行界面,有若干个输入项或选择项等,这个根据不同脚手架的功能而不同。例如create-xxx@0.0.1的选项。

在这里插入图片描述

在用户输入和选择全部完成后,脚手架会把代码生成到本地,同时提示用户启动方式。

第一版工程结构

首先我们创建脚手架工程的第一版。

|-- create-xxx@0.0.1|-- package.json|-- src|-- appName.js|-- create.js|-- index.js|-- prompt.js|-- setFileConfig.js|-- templates|-- auto-element|-- ...|-- lite-element|-- ...|-- trad-element|-- ...

其中src是脚手架工具命令行工具部分,templates是代码模板部分。

第一版代码和说明

启动方式

首先来看一下package.json

{"name": "create-xxx","version": "0.0.1","description": "","main": "src/index.js","bin": {"create-xxx": "src/index.js"},"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"author": "jiazhen","dependencies": {"chalk": "^4","commander": "^10.0.0","inquirer": "^8.0.0","ora": "^5"}
}

注意,这里所有的依赖都是dependencies,即生产环境依赖。

这里设置了bin字段,值为工具的入口文件,可以使用该命令启动。同时设置为create-xxx,可以使用npm create xxx的方式启动。不过,使用npx会在全局自动缓存该npm包,因此启动时后面带个@latest表示最新版本更好。否则你上传了新版本,用户依然还会使用本地缓存的旧版本。

# 建议
npx create-xxx@latest

我们在第一版开发时,还不是一个完整的包,因此本地调试可以直接使用node执行:

node src/index.js

后续代码中所有的模块引入都使用的require,也就是CommonJS规范。这种规范可以让代码在Node.js中直接执行。如果使用ES Modules规范,即import,使用Node.js中直接执行会报错。

主要流程

入口文件

先看代码,首先是入口文件src/index.js

#! /usr/bin/env node
const { program } = require("commander");
const { createDir, getCmdName } = require("./appName");
const package = require("../package.json");
const { getPromptValue } = require("./prompt");
const create = require("./create");
const ora = require("ora");
const chalk = require("chalk");// 创建成功后的提示
function succConsole(configs) {console.log('');console.log(chalk.cyan(`  ${chalk.gray("$")} cd ${configs.name}`));console.log(chalk.cyan(`  ${chalk.gray("$")} npm install`));console.log(chalk.cyan(`  ${chalk.gray("$")} npm run dev`));
}// 主函数
async function main() {program.option("-n, --name <value>", "app name");program.version(package.version, "-v");program.parse();const options = program.opts();// 从命令行中获取AppNamelet name = getCmdName(options, program);// 检测名称并创建文件夹name = await createDir(name);// 获取脚手架选项const promptValue = await getPromptValue();const configs = {...promptValue,name,};const spinner = ora("工程正在创建中");// 创建工程await create(configs);console.log('');spinner.succeed("工程创建完成!");succConsole(configs);
}main();

流程说明

  1. 使用commander库获取命令行参数,主要有name和version。
  2. 检测名称并创建工程文件夹。
  3. 用户交互式的输入脚手架选项。
  4. 复制代码,并根据脚手架选项调整代码。
  5. 生成结束,输出成功提示。

注意示项

  • 脚本说明
    首先是代码的第一句#! /usr/bin/env node,这表示将用node脚本执行该命令。如果没有这一句,后面作为一个npm包被执行的时候会报错。
  • 获取版本
    版本信息我们直接使用package.json中的版本号即可。把它作为一个模块引入,直接取值。

获取工程名称

文件src/appName.js

const inquirer = require("inquirer");
const fs = require("fs");
const namePrompt = [{type: "input",name: "name",message: "请输入工程名称",default: "xxx-app",},
];// 检测名称并创建文件夹
async function createDir(appName) {while (1) {if (!appName) {const res = await inquirer.prompt(namePrompt);appName = res.name;}try {// 创建文件夹const res = fs.mkdirSync(appName);break;} catch (e) {console.log("error: 工程名称与现有文件夹重名,请重新输入!");appName = null;}}return appName;
}// 从命令行中获取AppName
function getCmdName(options, program) {// 输入了name参数优先取nameif (options.name) return options.name;// 没有name参数则使用第一个输入项if (program.args && program.args.length > 0) return program.args[0];return null;
}module.exports = {createDir,getCmdName,
};

我们把工程名称作为后面放置工程代码所创建的文件夹名称,因此这个名称特别重要。

  1. 首先我们尝试从命令行参数中获取工程名称。如果没有获取到则提示用户输入工程名称。
  2. 这里还对工程名称做了校验,校验内容是————是否与当前已有的文件重名,如果重名则提示用户重新输入。
  3. 文件名校验成功则创建文件夹。

获取脚手架选项

文件src/prompt.js

const inquirer = require("inquirer");
const promptList = [{type: "list",message: "请选择模板类型",name: "tamplate",default: "auto",choices: [{name: "自动版 (推荐首选,集成vue3生态新功能)",value: "auto",},{name: "精简版 (无多语言/多皮肤等功能)",value: "lite",},{name: "传统版 (不使用各类按需引入插件)",value: "trad",},],},{type: "input",name: "namespace",message: "请输入组件上下文,即公共基础路径",default: "/",},
];async function getPromptValue() {const res = await inquirer.prompt(promptList);return res;
}module.exports = {getPromptValue,
};

这部分非常简单,按照inquirer库的格式做好需要用户输入的内容,取得用户输入的值即可。

获取模板代码

代码模板和脚手架一起存放

文件src/create.js

const path = require("path");
const fs = require("fs");
const { setTargetConfig  } = require('./setFileConfig')// 获取模板代码
async function getTempLateCodes(configs) {// 包中的代码位置let srcPath = path.join(__dirname,"../templates",`${configs.tamplate}-element`);// 代码要放置的目标工程位置const targetPath = path.join(process.cwd(), configs.name);// 复制代码到工程中// node.js 16.7 Aug 18,2021发布fs.cpSync(srcPath, targetPath, { recursive: true });// 根据配置修改模板文件await setTargetConfig(targetPath, configs)
}// 创建工程的主函数
async function create(configs) {getTempLateCodes(configs);
}module.exports = create;

有了这些配置之后,我们就可以获取模板代码了。我这里需要根据不同的配置而使用不同的代码模板。通过查看上面的工程结构,我们看到代码模板是和脚手架代码放置在同一个npm包中的,因此直接使用fs复制文件即可。相比于分开存放,放置在通过一个npm包中复制文件速度更快。而分开存放一半需要通过网络下载代码模板,速度慢一些。

使用这种方式,需要注意两个路径:

  • npm包路径
    脚手架代码所在的位置的路径,这个路径可以用__dirname获取。
    从这个路径内获取模板代码。
  • 工作目录
    当前执行脚手架命令所在的目录。这个路径可以用process.cwd()获取。
    这个路径+工程名称就是工程的存放位置。模板代码要放置到这里。

代码模板和脚手架分开存放

代码模板是和脚手架代码放置在同一个npm包中,脚手架版本会和模板版本强绑定。如果不希望绑定,可以脚手架一个npm包,代码模板使用另外一个npm包。这样假设代码模板有多个版本,可以使用同一个脚手架安装不同版本的模板。甚至可以模板放置在git上,脚手架启动时直接去git上下载代码。

根据脚手架选项调整代码

文件src/setFileConfig.js

const path = require("path");
const fs = require("fs");
const { promisify } = require("util");// 回调转promise版本
const fsWriteFile = promisify(fs.writeFile);
const fsReadFile = promisify(fs.readFile);// 根据配置修改模板文件
async function setTargetConfig(targetPath, configs) {await Promise.all([setPackageJson(targetPath, configs),setReadme(targetPath, configs),setEnv(targetPath, configs),]);
}// 修改package.json
async function setPackageJson(targetPath, configs) {const objPath = path.join(targetPath, "package.json");const package = require(objPath);package.name = configs.name;const jsonData = JSON.stringify(package, null, 2);await fsWriteFile(objPath, jsonData);
}// 修改README.md
async function setReadme(targetPath, configs) {const objPath = path.join(targetPath, "README.md");let data = await fsReadFile(objPath, "utf8");data = data.replace("XXX-APP-NAME", configs.name);await fsWriteFile(objPath, data);
}// 修改.env
async function setEnv(targetPath, configs) {let namespace = configs.namespace;// 完善namespace数据if(!namespace.length) {namespace = '/'}if(namespace[0] !== '/') {namespace = '/' + namespace}if(namespace[namespace.length - 1] !== '/') {namespace = namespace +  '/'}// 写入文件const objPath = path.join(targetPath, ".env");let data = await fsReadFile(objPath, "utf8");// 正则中.不包含换行符,这里正好截取一行data = data.replace(/VITE_NAMESPACE.*/g,`VITE_NAMESPACE = ${namespace}`);await fsWriteFile(objPath, data);
}module.exports = {setTargetConfig,
};
  • 这里也非常简单,我们把模板代码当作普通文件去读,根据配置修改文件内容,再写入即可。
  • 我们可以把模板的可变部分使用特殊的字符串标记,方便我们使用正则查找并替换。如果修改的内容较复杂,甚至可以使用一些模板引擎。
  • 读文件是耗时操作,可以使用Promise.all一起执行多个。如果操作更复杂耗时,甚至可以考虑引入多线程技术。

打包上传

写好之后并测试完成后,我们就可以把我们的脚手架作为一个npm包发布,这样用户才能下载使用。

# 发布包
npm publish
# 删除包
npm unpublish create-xxx@0.0.1

在公共网络发布npm包需要先注册。

第二版工程结构

第一版的工程,已经可以作为一个脚手架来使用了。但是还有几个小问题可以改进:

  1. 所有依赖都是生产依赖,这意味着用户在启动脚手架时,还需要额外下载很多npm包,需要等待一段时间。
  2. 脚手架代码可以进行压缩。

针对这两个问题,我对第一版脚手架工程进行了改进,改进后的工程结构如下:

|-- create-xxx@0.0.2|-- package.json|-- bin|-- index.js|-- dist|-- index.cjs|-- src|-- appName.js|-- create.js|-- index.js|-- prompt.js|-- setFileConfig.js|-- templates|-- auto-element|-- ...|-- lite-element|-- ...|-- trad-element|-- ...|-- .eslintignore|-- .eslintrc.js|-- .gitignore|-- .npmrc|-- .prettierignore|-- .prettierrc.js|-- build.config.js

第二版的改动

使用unbuild打包

第一版代码没有经过打包直接发布,第二版如果希望去除生产环境的依赖,那么就必须进行打包。我参考create-vite,使用了unbuild作为打包工具。这是一个轻量级的,基于rollup的工具。
看一下打包配置build.config.js:

import { defineBuildConfig } from 'unbuild'export default defineBuildConfig({// 入口文件entries: ['src/index'],clean: true,// 生成ts声明文件declaration: false,// 警告是否会引发报错failOnWarn: false,// rollup配置rollup: {// 生成cjsemitCJS: true,inlineDependencies: true,esbuild: {// 压缩代码minify: true,},resolve: {exportConditions: ['node'],},},
})

打包和压缩代码都是unbuild完成的。打包之后会生成一个dist文件夹,里面有index.cjsindex.mjs。我们使用的是cjs,即CommonJS规范的文件。

更新依赖

来看一下package.json

{"name": "create-xxx","version": "0.0.2","description": "xxx脚手架","main": "bin/index.js","bin": {"create-xxx": "bin/index.js"},"scripts": {"build": "unbuild","lint": "eslint src --fix","pretty": "prettier --write ."},"author": "jiazhen","devDependencies": {"commander": "^10.0.0","unbuild": "^1.1.2","prompts": "^2.4.2","eslint": "^8.30.0","prettier": "^2.8.1","eslint-config-prettier": "^8.5.0","eslint-define-config": "^1.12.0","eslint-plugin-prettier": "^4.2.1"},"files": ["templates","dist/index.cjs","bin/index.js"]
}

可以看到,没有了生产依赖,全部换成开发依赖了。而且功能代码直接使用的依赖只剩下两个必须的:commanderpromptspackage.json还有其它改动,后面再说。

为啥要换依赖?因为我们要打包后要成为一个CommonJS规范,无任何依赖的Node.js可执行文件,但是很多包并不支持这种打包方式。原因可能有很多,可能是使用了某些浏览器才有的属性,可能引入了不能打包的依赖等等。就算同一个npm包,也可能部分版本支持,部分版本不支持。

比如我们使用的prompts,就在文档里说明了依赖非常少:

Simple: prompts has no big dependencies nor is it broken into a dozen tiny modules that only work well together.

关于依赖的选用,可以参考对应npm包的文档说明、issues等,也可以参考其它实现该方式的脚手架。

换了有些依赖,部分功能需要稍微修改一下,比如从inquirer换为prompts,就需要改部分配置。

注意:对于不支持的依赖,我出现过打包成功,但是dist中出现reuqire('string_decoder/')这种不存在的依赖的报错。

更换库引入方式

我试过rollup系列的打包工具,包括vite,rollup和unbuild,对于require,即CommonJS规范引入的依赖不处理,即依然作为一个外部依赖,而不是直接打包进成果物。可能是这些工具不支持,也可能是由于我没配置对的原因,后续我再研究一下。目前先全部转为ES Modules规范。需要改动的地方很少,大致只有引入和导出的方式。

// CommonJS规范 示例
const { program } = require("commander");
const { createDir, getCmdName } = require("./appName");
const package = require("../package.json");
const { getPromptValue } = require("./prompt");
const create = require("./create");
const ora = require("ora");
const chalk = require("chalk");module.exports = {createDir,getCmdName,
};// ES Modules规范 示例
import { program } from 'commander'
import { createDir, getCmdName } from './appName'
import packageData from '../package.json'
import { getPromptValue } from './prompt'
import create from './create'export { createDir, getCmdName }

Node.js本身自带的库可以保持原样,依然使用CommonJS规范。因为只要安装了Node.js(不安装啥也用不了),就可以直接使用这些工具,不需要被打包。而且这些工具部分是使用C++写的,也不能打包到前端成果中。

// 部分Node.js本身自带的库
const path = require('path')
const fs = require('fs')
const { promisify } = require('util')

固定入口文件

通过上面的改动,我们已经可以生成一个包含依赖的文件dist/index.cjs了。但是我们注意到,即使我们在入口文件src/index.js中写了#! /usr/bin/env node,生成的文件中也不包含这一句。因为这一句被当作注释删掉了。

因此我们再新建一个文件bin/index.js来引入:

#!/usr/bin/env node
require('../dist/index.cjs')

注意这个文件不需要被打包,而是固定的,直接引入打包后的文件。这里执行的环境是Node.js,因此需要使用CommonJS规范。

更新package.json,这个文件直接作为bin中的启动入口文件。

其它改动

  • npm publish时仅仅上传必须的文件,比如bin/index.jsdist/index.cjstemplates模板文件夹。在package.json中使用files指定。上传的内容越少,执行时下载的速度越快。
  • 加入Eslint和Prettier,方便进行语法检查和代码格式化。可以看到对应的依赖和命令。
  • 使用npm配置文件.npmrc,上传时不用切换仓库。

参考

这个脚手架的实现有参考create-vite和其它脚手架等,在这里表示感谢~

  • create-vite vite项目脚手架
    https://github.com/vitejs/vite/tree/main/packages/create-vite
  • Prompts
    https://github.com/terkelg/prompts
  • unbuild
    https://github.com/unjs/unbuild

相关文章:

如何编写一个自己的web前端脚手架

脚手架简介 脚手架是创建前端项目的命令行工具&#xff0c;集成了常用的功能和配置&#xff0c;方便我们快速搭建项目&#xff0c;目前网络上也有很多可供选择的脚手架。 一个"简单脚手架"的构成其实非常少&#xff0c;即 代码模板 命令行工具。其中代码模板是脚手…...

计算机网络第1章(概述)

文章目录1.1、计算机网络在信息时代的作用1.2、因特网概述1、网络、互连网&#xff08;互联网&#xff09;和因特网2、因特网发展的三个阶段3、因特网的标准化工作4、因特网的组成1.3 三种交换方式1、电路交换&#xff08;Circuit Switching&#xff09;2、分组交换&#xff08…...

grid布局

一、概述 CSS Grid 布局是 CSS 中最强大的布局系统。与 flexbox 的一维布局系统不同&#xff0c;CSS Grid 布局是一个二维布局系统&#xff0c;也就意味着它可以同时处理列和行。通过将 CSS 规则应用于 父元素 (成为 Grid Container 网格容器)和其 子元素&#xff08;成为 Gri…...

博客平台打造出色的个人资料管理与展示:实用技巧与代码示例

个人资料管理与展示是博客平台的重要功能之一。本文将通过设计思路、技术实现和代码示例&#xff0c;详细讲解如何构建出色的个人资料管理与展示功能。结合CodeInsight平台的实践案例&#xff0c;帮助您深入了解个人资料管理与展示的设计原则和技术实现。 一、设计思路 在设计…...

【genius_platform软件平台开发】第九十三讲:串口通信(485通信)

485通信1. 485通信1.1 termios结构1.2 头文件1.3 函数讲解1.3.1 tcgetattr1.3.2 tcsetattr1.4 示例工程1.5 参考文献1.5.1 stty命令1.5.2 命令格式1.5.2 microcom命令1.5.2.1介绍1.5.2.2指令1.5.3 echo命令1.5.3.1 语法1.5.3.2 选项列表1.5.3.3 使用示例1.5.3.4 e cho > 输出…...

JavaScript动画相关讲解

JavaScript是一种非常流行的脚本语言&#xff0c;广泛应用于Web开发、游戏开发、移动应用开发等领域。在Web开发中&#xff0c;动画效果是非常重要的一部分&#xff0c;可以提高网站的用户体验和吸引力。JavaScript提供了一些基本的动画函数&#xff0c;但是这些函数往往不能满…...

InnoSetup制作安装包(EXE)

功能描述 1.666666.war为项目war包&#xff0c;666666.bat为启动war包脚本&#xff0c;通过InnoSetup将它们打包到安装包666666.exe 2.666666.exe安装包安装过程中将666666.bat注册为自启动服务&#xff0c;安装结束自动执行脚本启动项目666666.war --------------------------…...

CASE WHEN函数语句多条件下使用详解

目录 CASE 的两种格式&#xff1a; 简单CASE函数 和 CASE搜索函数 同时配合 SUM 以及 COUNT 方法的使用 ① SUM函数 ② COUNT函数 CASE WHEN函数语句&#xff0c;实现简单CASE函数和CASE搜索函数两种格式。同时配合 SUM以及COUNT方法的使用 CASE 的两种格式&#xff1a; 简…...

2.31、守护进程(2)

2.31、守护进程&#xff08;2&#xff09;1.守护进程的创建步骤2.什么情况下子进程不会继承父进程的组ID3.哪些操作会导致子进程的组ID发生改变4.kill怎么杀掉守护进程的实现守护进程1.守护进程的创建步骤 执行一个 fork()&#xff0c;之后父进程退出&#xff0c;子进程继续执…...

记录上传文件异常 /tmp/tomcat... (No space left on device)

一&#xff0c;问题描述 用postman调用上传接口&#xff0c;基本每两次调用会有一次报错&#xff0c;如下 {"timestamp": "2023-04-11T03:00:15.4690000","status": 500,"error": "Internal Server Error","exceptio…...

无向连通图中长度为 n 的循环

给定一个无向连通图和一个数字 n,计算图中长度为 n 的循环总数。长度为 n 的圈简单地表示该圈包含 n 个顶点和 n 条边。我们必须计算所有存在的此类循环。 示例: 输入:n = 4 输出:总周期数 = 3 解释 :遵循 3 个独特的循环0 -> 1 -> 2 -> 3 -> 0 0 -> 1 …...

打造出ChatGPT的,是怎样一群人?

震惊世界的ChatGPT&#xff0c;要多少人才能开发出来&#xff1f;几百&#xff0c;还是几千&#xff1f; 答案是&#xff1a;87个人。 老实说&#xff0c;刚看到这个数字真是惊到我了&#xff0c;印象里&#xff0c;之前看媒体报道各大巨头人工智能人才储备时&#xff0c;动辄…...

数据结构——栈与队列相关题目

数据结构——栈与队列相关题目232. 用栈实现队列思路225. 用队列实现栈1.两个队列实现栈2.一个队列实现栈20. 有效的括号思路1047. 删除字符串中的所有相邻重复项思路155. 最小栈150. 逆波兰表达式求值思路239. 滑动窗口最大值单调队列347. 前 K 个高频元素思路232. 用栈实现队…...

Redhat6.7离线安装rabbitmq

一、下载资源文件&#xff08;.rpm文件&#xff09; 链接: https://pan.baidu.com/s/1j2Ze_Jjm0oMrP-r95PPCtA?pwdv3is 提取码: v3is 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 创建rabbit文件夹Mkdir rabbit 三、通过ftp上传文件 四、安装erlang环境 …...

EasyCVR平台基于GB28181协议的语音对讲配置操作教程

EasyCVR基于云边端协同&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可支持海量视频的轻量化接入与汇聚管理&#xff0c;可提供视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、平台级联等功能…...

谷歌发布Self-Debug方法,让大模型学会自己修bug,一次性生成正确代码

文 | 智商掉了一地你有没有想过&#xff0c;让一台计算机诊断和修复自己生成的错误代码&#xff1f;一篇最新的研究论文介绍了一种名为 Self-Debugging 的技术&#xff0c;通过在生成的代码中添加自解释的信息&#xff0c;让计算机像一个可以自己修复代码的程序员一样调试自己的…...

行为型模式-模板方法

行为型模式-模板方法 模板方法(Template Method)解决算法框架问题描述适用环境优点:缺点:违反原则:代码实现模板方法(Template Method) 解决算法框架问题 描述 定义了一个算法的骨架,并将某些步骤延迟到子类中进行实现,从而使得算法的具体实现能够在子类中自由变化…...

正则表达式识别日期

正则表达式识别日期 正则表达式识别各种格式的日期 import redef extract_dates(text):# 正则表达式&#xff0c;用于识别常见的日期格式date_pattern r"""(?P<date>(?P<year_only>\d{4}(?![\d年]))| # …...

如何设计一个秒杀架构设计?

文章目录 1. 秒杀业务的特点2. 总体思路2.1 削峰限流安全保护页面优化,动静分离异步处理热点分离2.2 Nginx的设计细节2.3 页面优化细节降低交互的压力安全控制2.4 Redis集群的应用分布式悲观锁(参考redis悲观锁的代码)异步处理订单2.5 消息队列限流2.6 数据库设计2.7 答题验…...

Elasticsearch:配置选项

Elasticsearch 带有大量的设置和配置&#xff0c;甚至可能让专家工程师感到困惑。 尽管它使用约定优于配置范例并且大部分时间使用默认值&#xff0c;但在将应用程序投入生产之前自定义配置是必不可少的。 在这里&#xff0c;我们将介绍属于不同类别的一些属性&#xff0c;并讨…...

Tesseract OCR 安装与中文+英文识别实现

一、下载 https://digi.bib.uni-mannheim.de/tesseract/ 下载&#xff0c;尽量选择时间靠前的&#xff08;识别更好些&#xff09;。符合你的运行机&#xff08;我的是windows64&#xff09; 持续点击下一步安装&#xff0c;安装你认可的路径即可&#xff0c;没必要配置环境变…...

数据库只更新特定字段的两种方式(先读后写 vs. 动态组织 SQL)-golang SQLx 实现代码(动态组织 SQL)

文章目录 数据库只更新特定字段的两种方式&#xff08;先读后写 vs. 动态组织 SQL&#xff09;go语言例子使用GORM的示例&#xff08;最常用的Go ORM库&#xff09;使用SQLx的两种更新方式实现golang SQLx 实现代码&#xff08;动态组织 SQL&#xff09; 数据库只更新特定字段的…...

AI书签管理工具开发全记录(八):Ai创建书签功能实现

文章目录 AI书签管理工具开发全记录&#xff08;八&#xff09;&#xff1a;AI智能创建书签功能深度解析前言 &#x1f4dd;1. AI功能设计思路 &#x1f9e0;1.1 传统书签创建的痛点1.2 AI解决方案设计 2. 后端API实现 ⚙️2.1 新增url相关工具方法2.1 创建后端api2.2 创建crea…...

【C语言】讲解 程序分配的区域(新手)

目录 代码区 数据区 堆区 栈区 常量区 重点比较一下堆区与 栈区 总结&#xff1a; 前言&#xff1a; C语言程序的内存分配区域是理解其运行机制的重要部分。根据提供的多条证据&#xff0c;我们可以总结出C语言程序在运行时主要涉及以下五个关键内存区域&#xff1a; 代…...

HTML Day03

Day03 0. 引言1. CSS1.1 CSS的3种使用方法1.2 内联样式1.3 内部样式表1.4 外部CSS文件 2. 图像3. 表格3.1单元格间距和单元格边框 4. 列表4.1 有序表格的不同类型4.2 不同类型的无序表格4.3 嵌套列表 5. 区块6. 布局6.1 div布局6.2 表格布局 0. 引言 HELLO ^ _ ^大家好&#xf…...

大模型运维过程中常见的一些操作

1. 模型部署与环境配置 基础设施准备&#xff1a;部署 GPU 集群、TPU 等专用硬件&#xff0c;配置分布式计算环境&#xff08;如 Kubernetes&#xff09;。推理服务搭建&#xff1a;使用 Triton Inference Server、TensorFlow Serving 等框架部署模型&#xff0c;优化批处理和…...

飞致云开源社区月度动态报告(2025年5月)

自2023年6月起&#xff0c;中国领先的开源软件公司飞致云以月度为单位发布《飞致云开源社区月度动态报告》&#xff0c;旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况&#xff0c;以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源运营数据概览&…...

基于大模型预测带状疱疹(无并发症)诊疗方案的研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 二、带状疱疹概述 2.1 病因与发病机制 2.2 流行病学特征 2.3 临床表现与诊断标准 三、大模型技术原理及应用于带状疱疹预测的可行性 3.1 大模型技术简介 3.2 应用可行性分析 四、大模型预测带状疱疹的具体方…...

Windows最快速打开各项系统设置大全

目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找&#xff08;最慢&#xff09; 2.2 方法二cmd命令&#xff08;慢&#xff09; 2.3 方法三快捷键&#xff08;快&#xff09; 2.4 方法四搜索栏&#xff08;快&#xff09; 2.5 方法五任务栏&#xff08;最快&am…...

[特殊字符] 超强 Web React版 PDF 阅读器!支持分页、缩放、旋转、全屏、懒加载、缩略图!

在现代 Web 项目中&#xff0c;PDF 浏览是一个常见需求&#xff1a;从政务公文到合同协议&#xff0c;PDF 文件无处不在。但很多方案要么体验不佳&#xff0c;要么集成复杂。今天&#xff0c;我给大家带来一个开箱即用、功能全面的 PDF 预览组件 —— [PDFView](https://www.np…...