首页
关于我
Search
1
Vue3 Hooks 的使用
1,405 阅读
2
Vue3 中 ref 标记组件使用
1,221 阅读
3
搭建博客
994 阅读
4
npm i 安装依赖 node-sass 报错
891 阅读
5
Vue2 中混入的使用
726 阅读
Web前端基础
JavaScript
jQuery
css
Vue
ElementUI
博客
Ant Design Vue
友情链接
凌哥技术分享
coder的技术博客
登录
Search
子衿
累计撰写
26
篇文章
累计收到
197
条评论
首页
栏目
Web前端基础
JavaScript
jQuery
css
Vue
ElementUI
博客
Ant Design Vue
页面
关于我
搜索到
26
篇与
芸殇
的结果
2026-10-31
服务器数据迁移
服务器迁移实战:从环境盘点到数据校验与业务切换服务器迁移不仅是复制网站目录。数据库、运行库、服务启动配置、定时任务、证书以及外部访问地址,都可能影响迁移后的业务。本文以 Linux、Nginx、PHP、MySQL 和 Supervisor 部署的业务为例,介绍允许停机时的一套迁移流程。Docker、跨系统或跨架构迁移需要调整具体方法。本文所有 IP、域名、用户名和路径均为示例,不对应真实部署。192.0.2.10、198.51.100.20 是文档示例 IP,example.com 是示例域名,不能直接用于真实连接。不要在公开文章中放入真实凭据、内网拓扑、数据库配置或完整系统备份。一、先明确迁移边界开始前,至少确认:旧、新服务器的系统版本、CPU 架构、磁盘空间与内存。需要迁移的应用、数据库、上传文件和后台服务。是否允许停机,以及新服务器是否存在需要保留的数据。域名、CDN、对象存储、邮件服务和第三方 IP 白名单。SSH 登录方式、操作权限和回退要求。“迁移全部业务”不等于把旧系统根目录覆盖到新系统。新服务器的网络、SSH、磁盘挂载、主机身份和云厂商代理通常应保留自身配置。本文采用“停机备份 → 传输恢复 → 核对数据 → 验证业务 → 切换入口”的流程。低停机迁移则需要数据库复制、增量同步和更严格的写入切换设计。二、配置专用 SSH 密钥在自己的管理电脑上生成专用密钥,按提示设置私钥口令:ssh-keygen -t ed25519 -f ~/.ssh/migration_ed25519 -C migration-example通过服务器控制台或现有可信连接,把 .pub 公钥的一整行加入两台服务器对应用户的 ~/.ssh/authorized_keys。不要把私钥上传到服务器。目标用户的 SSH 目录权限应为:chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys测试登录:ssh -i ~/.ssh/migration_ed25519 deploy@192.0.2.10 ssh -i ~/.ssh/migration_ed25519 deploy@198.51.100.20首次连接时,通过云控制台等可信渠道核对 SSH 主机指纹。不要为了省事全局关闭主机密钥检查。下文涉及系统目录的命令,应使用 sudo 或经过授权的管理员会话执行。三、只读盘点旧服务器先看系统、资源和服务:cat /etc/os-release uname -m df -hT free -h lsblk systemctl list-units --type=service --state=running --no-pager ss -lntup crontab -l再看软件版本:nginx -v php -v mysql --version command -v docker command -v supervisorctl不能只看当前正在运行的服务,还要检查:systemd 和旧式 init 启动脚本。Supervisor 配置及其托管程序。/etc/cron.d、各业务用户的 crontab 和 systemd timers。网站根目录、附件目录、软链接目标和外部挂载。MySQL 实际数据目录及配置文件。Nginx 虚拟主机、重写规则、证书和反向代理目标。FTP 账户数据库、TLS 证书及被动端口设置。应用配置中的硬编码地址、数据库账号和外部依赖。宝塔环境的软件可能位于 /www/server,网站可能位于 /www/wwwroot;其他环境应以实际配置为准。不要把包含密码的配置输出到公开日志或博客。最终整理出一份私有迁移清单,写清每个服务的文件、配置、依赖、启动方式和验收方式。四、选择迁移方法场景通常采用的方法同系统、同架构、相同软件版本选择性复制应用环境,验证动态依赖系统、架构或数据库版本不同在新服务器安装目标环境,通过受支持的导出导入迁移Docker Compose迁移 Compose、配置和持久化数据,单独设计数据库一致性备份数据量大、停机要求短预同步文件、数据库复制或增量备份,最终暂停写入后切换即使系统名称相同,动态库版本和软件编译参数也可能不同。复制可执行文件后,需要检查依赖并执行实际功能测试。MySQL 数据目录不能随意跨版本复制。使用物理备份前,要核对数据库版本、存储引擎、加密密钥、目录布局及恢复方式;不确定时优先选择兼容的逻辑导出导入。五、停止所有写入,再制作一致性备份先备份新服务器自己的配置,并在云平台创建合适的恢复点。快照能否保证数据库一致性取决于创建方式,不能替代数据库一致性设计。按应用依赖顺序停止旧服务器的写入来源,例如网站入口、任务队列和定时任务,然后停止数据库。以下服务名是示例,不能直接照搬:sudo systemctl stop example-worker.service sudo systemctl stop example-scheduler.service sudo systemctl stop nginx.service sudo systemctl stop php-fpm.service sudo systemctl stop mysqld.service停止前要先记录哪些服务原本在运行。停止后应检查进程和连接,不能仅凭停止命令没有报错,就认为没有写入。还需要排除远程数据库客户端、事件调度器和其他后台程序。方案 A:逻辑导出数据库逻辑导出需要数据库保持运行。应先停止应用写入,再导出,最后按迁移方案决定是否停止数据库:mysqldump -u backup -p \ --single-transaction --routines --events --triggers \ --databases example_db > example_db.sql--single-transaction 主要用于事务表的一致性视图,不会自动保证非事务表或并发 DDL 的一致性。导出账户权限、字符集和跨版本兼容性也需要检查。账号与授权通常应单独处理,不能假定只导出业务库就迁移了全部用户。方案 B:数据库完全停止后复制数据目录只在已确认版本和物理恢复方式兼容时采用。必须确认数据库进程已退出,复制实际数据目录以及恢复需要的配置和密钥。不要直接打包正在运行的数据库数据文件。备份应用与配置先创建仅管理员可访问的目录,再按盘点清单打包:BACKUP_DIR=/var/backups/migration-example sudo install -d -m 700 "$BACKUP_DIR" sudo tar --acls --xattrs -czpf "$BACKUP_DIR/app.tar.gz" \ -C /srv example-app sudo tar --acls --xattrs -czpf "$BACKUP_DIR/nginx-config.tar.gz" \ -C /etc nginx单独备份数据库、证书、Supervisor、服务单元、定时任务及需要的用户文件。保留原文件所有者、权限、ACL 和扩展属性,并记录恢复要求。不要无差别打包整个 /usr/local 或覆盖整个 /etc。其中可能包含云安全代理、机器身份、网络配置和不可迁移的运行状态。软件依赖应按清单安装或选择性迁移;云厂商代理通常由新实例自己维护。记录软件包清单,便于补齐依赖:# RPM 系统 rpm -qa > packages.txt # Debian 系统 dpkg-query -W > packages.txt软件包清单是盘点资料,不是可以无条件批量安装的脚本。六、传输备份并校验先在旧服务器生成哈希:cd /var/backups/migration-example sha256sum app.tar.gz nginx-config.tar.gz > SHA256SUMS使用已核验主机身份的 SSH 连接传输文件。两端都可从管理电脑访问时,可以通过管理电脑中转,也可以在已配置安全认证的前提下使用 scp 或 rsync。备份传输必须加密,目标目录也应限制访问。不要为了速度把备份放到公开下载目录。在新服务器同一备份目录中验证:sha256sum -c SHA256SUMS同一云网络内传输通常能减少公网传输开销,但必须先确认网络互通、SSH 主机身份和访问控制。如果使用临时传输密钥,要限制来源与用途,并在完成后撤销目标端授权、删除临时私钥。不要把管理电脑长期使用的私钥复制到旧服务器上。七、在新服务器恢复先安装兼容的软件和依赖,创建相应业务用户。恢复文件所有权时,核对旧、新服务器的 UID/GID,不能仅凭用户名相同就认定权限正确。先查看归档内容,再解压到暂存目录:sudo tar -tzf /var/backups/migration-example/app.tar.gz sudo install -d -m 700 /var/backups/migration-example/staging sudo tar --acls --xattrs -xzpf \ /var/backups/migration-example/app.tar.gz \ -C /var/backups/migration-example/staging检查内容和路径后,再安装到预定业务目录。配置文件应与新服务器合并,避免覆盖网络、SSH、磁盘挂载和机器身份。数据库恢复有两条对应路径:逻辑备份:启动兼容数据库,导入业务库并恢复必要授权,保持应用和任务关闭直至核对完成。停库物理备份:保持目标数据库停止,按数据库支持的物理恢复流程恢复,核对目录权限和配置后再启动。逻辑备份导入示例:mysql -u admin -p < example_db.sql复制编译好的程序后,检查依赖:ldd /path/to/trusted/application只对可信程序使用 ldd。如果出现 not found,应优先从可信软件源安装依赖。不要直接覆盖新系统的 glibc、动态加载器等核心组件。同时检查 SELinux 上下文、目录访问权限和应用可写目录,不要用全目录 chmod 777 或关闭 SELinux 来掩盖问题。八、修改与服务器地址有关的配置需要检查的常见位置包括:前端 API 地址和 WebSocket 地址。后端监听地址、回调地址和数据库连接。Nginx 的 server_name、反向代理和重定向。FTP 对外地址、第三方 IP 白名单。客户端保存的服务器地址。DNS、CDN、负载均衡和对象存储跨域配置。搜索时优先使用固定字符串,避免 IP 中的点被当成正则通配符:rg -n -F '192.0.2.10' /srv/example-app /etc/nginx搜索结果可能含有敏感配置,应在受控环境查看。逐项判断用途后再修改,不要对整个服务器执行全局替换。历史目录名、数据库名、备份记录中的旧地址不一定需要改;前端最好修改源码并重新构建。九、启动与验证先验证配置,再按依赖顺序启动:数据库 → 应用运行环境 → 后台服务 → Web 入口 → 定时任务。sudo nginx -t sudo systemctl daemon-reload sudo systemctl start mysqld.service sudo systemctl start php-fpm.service sudo systemctl start example-worker.service sudo systemctl start nginx.service不要过早开启会发邮件、扣款、调用第三方接口或执行破坏性清理的定时任务。应先确认目标地址和业务状态,避免新旧两台同时执行同一任务。1. 数据核对备份哈希一致只能证明传输文件一致,还需要检查恢复结果:网站文件数量、哈希、软链接、权限和上传目录。数据库列表、表数量、精确行数以及关键业务记录。在兼容版本和方法下比较业务表校验值。执行数据库支持的完整性检查。例如,小型 MySQL 数据库可在安排好负载后使用:mysqlcheck -u admin -p --check example_db表校验方法、锁和性能影响应按数据库版本、存储引擎评估。大库不要直接照搬可能长时间占用资源的检查命令。2. 服务与端口systemctl --failed --no-pager systemctl is-active nginx mysqld php-fpm ss -lntup supervisorctl statussystemd 显示 active 不一定说明实际业务进程正常。有些旧式服务脚本退出成功,但守护进程可能没有启动,应同时检查进程、监听端口和日志。3. 切换 DNS 前验证网站使用 --resolve,让请求直达新服务器,同时保留正确域名、Host 和 TLS SNI:curl --resolve blog.example.com:443:198.51.100.20 \ -I https://blog.example.com/不要使用 -k 作为证书验收方式。证书过期或域名不匹配,需要正确处理。首页返回 200 只是基础检查。还要测试登录、数据库查询、文件上传下载、后台任务、API、WebSocket,以及客户端实际连接。FTP 则应测试登录、TLS 和被动模式传输,不能只看 21 端口。4. 重启验证确认服务已按预期启用开机自启,在维护窗口内重启新服务器。重连后再次检查数据库、网站、后台服务、定时任务、挂载和交换空间。重启前应确保有控制台恢复入口,避免配置错误导致 SSH 无法重新连接。十、切换 DNS 和客户端地址正式切换前,应再次确认旧服务器不会产生新写入。如果预同步期间仍有写入,必须停写并做最后一次增量同步或重新导入。将需要迁移的域名 A 记录指向新 IP,检查是否存在仍指向旧服务器的 AAAA 记录,并同步处理 CDN 或负载均衡源站。不要假定修改解析控制台中的记录就一定生效:注册商实际委派的 NS 必须正确。迁移 DNS 服务商时,还需要迁移邮件、验证记录等完整配置,并协调已有 DNSSEC 的 DS 记录,防止解析验证失败。客户端使用旧 IP 时,需要更新地址;使用域名时,通常随 DNS 生效而切换。仍应考虑缓存、长连接和客户端本地配置。HTTPS 证书也要验证:可安全迁移仍有效且覆盖域名的证书,也可在验证条件具备后重新申请。证书私钥必须限制访问,并确认自动续期任务和部署钩子在新服务器有效。十一、保留回退能力切换后保留旧服务器和备份一段观察期,旧业务保持停止或隔离,避免双写。不要立即删除旧数据或释放实例。回退不是简单把 DNS 改回去:如果新服务器已经接受了写入,必须先处理新数据,否则会丢失切换后的业务记录。应事先定义停写、数据回同步、入口切回和验证步骤。确认稳定后,再按保留策略处理备份、撤销临时访问、清理迁移凭据,并安排旧实例下线。十二、如何判断迁移完成可以把完成状态分成三层:阶段验收内容数据与环境恢复文件、数据库、配置和依赖已恢复并核对服务运行验证进程、端口、日志、证书和重启自启正常业务切换验收域名与客户端已切换,真实业务操作通过,监控稳定只有首页能打开,不能代表全部业务验收完成。记录已测试的范围和未覆盖的功能,才能明确剩余风险。十三、迁移中容易踩的坑问题改进方法打包运行中的数据库文件停库物理备份,或使用数据库支持的一致性备份整体覆盖 /etc、/usr/local按清单选择性恢复,保留新主机网络、身份和云代理忽略证书和 FTP TLS 文件将证书、私钥和协议实际连接列入验收用户名相同但权限异常核对 UID/GID、ACL、SELinux 上下文服务显示 active,但端口没监听检查进程、日志和真实连接新旧两台同时执行定时任务在明确切换点迁移任务执行权只改 DNS A 记录同时检查 NS、AAAA、CDN、DNSSEC 和缓存全局替换旧 IP根据配置含义逐项修改,保留必要的历史目录与数据迁移后立刻删除旧实例保留观察期和可执行的回退方案把运维记录直接改成公开博客删除真实标识、凭据和拓扑,使用文档专用示例
2026年10月31日
1 阅读
0 评论
0 点赞
2025-06-11
Eslint 配置
ESLint 从入门到实战:作用、配置字段与 JavaScript / TypeScript / Vue 配置详解项目接入 ESLint 后,为什么还是不检查 .vue?为什么浏览器代码提示 window is not defined?为什么安装了插件,规则却没有生效?这些问题通常来自三件事:文件没有匹配到配置、解析器不理解文件语法,或者规则与运行环境没有正确设置。本文围绕这条链路解释 ESLint,并提供可独立使用的配置示例。文章以 Flat Config 为主,使用 eslint.config.mjs。示例要求 ESLint 支持 eslint/config 中的 defineConfig、globalIgnores 和 extends,插件也需要支持 Flat Config。安装前检查 Node.js、ESLint、TypeScript 和插件的兼容要求,安装后提交锁文件。一、ESLint 到底解决什么问题ESLint 是静态代码检查工具。它读取源文件,解析代码结构,再按照启用的规则报告问题;部分问题可以自动修复。例如:const count = 1; console.log(coutn);coutn 很可能是拼写错误。启用 JavaScript 的 no-undef 规则后,可以在运行前发现这个未声明变量。再如:let total = 10; console.log(total);如果后续没有重新赋值,prefer-const 可以建议改成 const。ESLint 可以帮助团队:提前发现未声明变量、重复声明、错误的控制流等代码问题。通过框架插件检查组件模板、框架约定和特定 API 用法。统一约定,例如未使用变量的处理、是否允许 debugger。在编辑器、提交检查和 CI 中执行同一套规则。但 ESLint 无法证明业务逻辑正确,也不自动检查所有运行时问题。规则有没有开启、规则能否获得类型信息,以及插件能力,都会影响检测范围。二、ESLint、TypeScript、Prettier 和测试的区别工具主要关注点例子ESLint已启用规则约束的代码质量和用法未使用变量、错误 Promise 用法、框架约定TypeScript 编译器类型系统、编译语义与类型兼容性把字符串传给只接受数字的参数Prettier代码排版换行、缩进、引号和行宽处理单元 / 集成测试输入输出和实际行为订单金额、权限判断、接口交互这些工具有部分重叠,但不能相互替代。开启 ESLint 的 TypeScript 类型感知规则,也不意味着可以省略 tsc --noEmit 或测试。对于常规项目,可以让 Prettier 处理排版,ESLint 处理代码规则,减少两个工具反复修改同一段代码的问题。三、理解检查过程:文件、解析器、规则ESLint 的工作过程可以概括为:输入文件 ↓ 判断 files / ignores,合并匹配配置 ↓ 解析器读取语法,生成代码结构及作用域信息 ↓ 运行规则;部分 TypeScript 规则进一步使用类型信息 ↓ 输出问题,或执行规则提供的自动修复这也解释了几类常见问题:文件没被匹配到:修改规则通常没有帮助。出现解析错误:要先检查解析器和语法选项。没有规则报告:可能根本没启用相关规则。类型规则报缺少类型信息:需要额外的 TypeScript 项目配置。插件、解析器和共享配置是三个不同概念:概念职责Parser,解析器让 ESLint 理解一种语法,例如 TypeScript 或 Vue 单文件组件Plugin,插件提供规则,也可能提供处理器、语言和预设配置Shareable config,共享配置将规则与选项打包,供其他配置复用安装插件不等于启用它的规则。 注册插件后,还需要继承它的预设,或者在 rules 中逐条开启规则。官方插件说明四、安装并运行第一个检查以下以 pnpm 为例,在项目根目录执行:pnpm add -D eslint @eslint/js globals三个依赖分别负责:包作用eslint命令行、配置引擎、规则执行等核心能力@eslint/jsJavaScript 官方预设,例如 recommendedglobals浏览器、Node.js 等环境的全局变量集合创建 eslint.config.mjs:import js from '@eslint/js'; import globals from 'globals'; import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ globalIgnores(['**/dist/**', '**/coverage/**']), { name: 'app/browser-js', files: ['src/**/*.{js,mjs}'], extends: [js.configs.recommended], languageOptions: { ecmaVersion: 'latest', sourceType: 'module', globals: globals.browser, }, rules: { eqeqeq: ['error', 'always'], 'prefer-const': 'error', 'no-debugger': 'error', }, }, ]);检查代码:pnpm exec eslint src自动修复支持修复的问题:pnpm exec eslint src --fix建议在工作区变更可查看、可回退的情况下执行 --fix,然后审阅 diff。不是每条规则都能自动修复,有些问题需要开发者明确判断。五、Flat Config 如何生效Flat Config 通常导出一个配置数组。每个对象可以针对一类文件设置规则,匹配同一文件的对象按顺序参与合并,后面的配置可以覆盖前面的冲突项。export default defineConfig([ { files: ['src/**/*.js'], rules: { 'no-console': 'warn' }, }, { files: ['src/dev-tools/**/*.js'], rules: { 'no-console': 'off' }, }, ]);src/main.js 使用第一条约定,src/dev-tools/debug.js 同时匹配两项,后面的配置关闭 no-console。defineConfig 是配置组织和类型提示辅助,不会自动开启检查规则。本文使用 .mjs,是为了明确配置文件采用 ESM,避免与项目 package.json 的模块类型混淆。files 与 ignores 通常相对配置位置匹配;使用 --config 或 basePath 时应注意其基准变化。运行位置、配置查找和多包仓库结构也会影响最终结果,遇到问题要对具体文件检查解析后的配置。配置文件文档六、配置字段逐项解释下面讨论的是 Flat Config。不要将 .eslintrc 的字段原样搬进来。1. name:配置块的名字{ name: 'app/browser-code', files: ['src/**/*.js'], }它用于识别配置块,排查复杂配置时比“第几个匿名对象”更容易理解。它不是文件匹配条件,也不决定优先级。2. files:配置作用于哪些文件files: ['src/**/*.{js,ts}', 'scripts/**/*.mjs']同一数组里常规字符串表示多个可匹配范围。上面的配置可作用于 src 下的 JS、TS 和 scripts 下的 MJS。没有 files 并不等于自动检查所有扩展名。增加 .ts、.vue 等文件时,应明确列入匹配范围,并配置理解其语法的解析器。3. ignores:排除配置作用范围这里最容易混淆的是全局忽略与局部忽略:globalIgnores(['**/dist/**'])用于整个配置的忽略范围。{ files: ['src/**/*.js'], ignores: ['src/generated/**'], rules: { 'no-console': 'warn' }, }表示 generated 文件不应用这个配置块,但它们仍可能被其他配置块检查。局部忽略不能当成整个项目的免检名单。目录匹配与文件匹配的细节不同;如果目的是让构建产物整体不被检查,优先显式使用 globalIgnores。4. basePath:为一个目录组织配置{ basePath: 'packages/web', files: ['src/**/*.js'], rules: { 'no-alert': 'error' }, }这里的文件模式相对 packages/web。它适合组织 monorepo 子包配置,但不是切换 Node.js 工作目录,也不等于 TypeScript 的 tsconfigRootDir。5. extends:继承配置{ files: ['src/**/*.js'], extends: [js.configs.recommended], rules: { 'no-unused-vars': 'warn' }, }先应用推荐配置,再设置项目覆盖项。extends 可以继承配置对象、配置数组,也可以引用插件配置名称;它与 .eslintrc 中字符串配置的解析方式有所区别。如果所用 ESLint 不支持这里的 extends 写法,应先检查其 API 和依赖兼容范围。6. languageOptions:代码采用什么语言语义常见子字段如下:字段作用示例ecmaVersion配置支持的 ECMAScript 语法版本及相关语言语义'latest'、2022sourceType区分模块、传统脚本和 CommonJS'module'、'script'、'commonjs'globals告诉检查器哪些标识符由环境提供,以及是否可写window: 'readonly'parser指定解析器对象tseslint.parserparserOptions向解析器传递选项JSX、TS 项目、Vue 内层解析器配置ecmaVersion: 'latest' 不会把代码编译成兼容旧浏览器的版本,也不提供 polyfill。实际运行兼容性仍由编译链与目标环境决定。languageOptions: { sourceType: 'module', globals: { ...globals.browser, __APP_VERSION__: 'readonly', }, }声明 __APP_VERSION__ 只代表它可被代码引用,不会真的创建这个变量;运行时仍要由构建工具或宿主注入。语言选项说明7. plugins:注册插件命名空间import examplePlugin from 'eslint-plugin-example'; // 示意代码:需要先安装真实存在且兼容的插件 const config = { plugins: { example: examplePlugin }, rules: { 'example/some-rule': 'error' }, };example 是命名空间,example/some-rule 是规则名。Flat Config 注册的是导入的插件对象,而不是旧版插件名称字符串数组。上面仅用于解释结构,不是可以直接安装运行的业务配置。实际规则名以插件文档为准。8. rules:规则、等级和选项rules: { 'no-debugger': 'error', 'no-console': ['warn', { allow: ['warn', 'error'] }], eqeqeq: ['error', 'always'], }等级数字写法含义off0不执行该规则warn1报告警告error2报告错误,通常使检查失败数组第一个元素是等级,其余元素由具体规则定义。不能把一个规则的配置选项套到另一个规则上。继承与覆盖还存在一个细节:只改规则等级时,可以保留已有选项。因此调试规则结果时,应查看最终配置,而不是只看最后写下的一行。规则配置说明9. settings:传给规则的共享数据settings: { 'example/project-kind': 'frontend', }这个字段提供共享数据,但必须有规则或插件读取这些数据才会产生作用。随意写 settings.alias 不会自动让所有导入检查插件理解 Vite 或 TypeScript 的路径别名。10. linterOptions:控制检查器行为linterOptions: { noInlineConfig: false, reportUnusedDisableDirectives: 'error', }noInlineConfig 控制源代码中的 ESLint 配置注释是否可以修改配置。reportUnusedDisableDirectives 用于发现不再需要的 eslint-disable 注释,避免代码已经修好,关闭规则的注释却一直遗留。11. processor:先处理非普通 JS 文件某些插件会从 Markdown 等文件中提取代码块交给 ESLint,再映射回原文件位置。处理器既可能由插件预设配置,也可以显式设置。不要为了处理 Vue 单文件组件就盲目填写 processor;常见 Vue 配置主要依赖 vue-eslint-parser,应遵循 Vue 插件文档。12. language:选择插件提供的语言一些插件支持 Markdown、JSON 等语言,可以使用 插件命名空间/语言名称 指定语言。它与 JavaScript 的 sourceType 不同,也不是通过填一个字符串就能让 ESLint 理解任意格式;必须安装并注册提供该语言的插件。七、JavaScript 项目的完整配置浏览器代码和 Node.js 工具脚本应分别设置全局变量:// eslint.config.mjs import js from '@eslint/js'; import globals from 'globals'; import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ globalIgnores(['**/dist/**', '**/coverage/**']), { name: 'app/browser', files: ['src/**/*.{js,mjs}'], extends: [js.configs.recommended], languageOptions: { sourceType: 'module', globals: globals.browser, }, linterOptions: { reportUnusedDisableDirectives: 'error', }, rules: { eqeqeq: ['error', 'always'], 'prefer-const': 'error', 'no-console': ['warn', { allow: ['warn', 'error'] }], }, }, { name: 'app/node-esm', files: ['scripts/**/*.{js,mjs}', '*.config.mjs', 'eslint.config.mjs'], extends: [js.configs.recommended], languageOptions: { sourceType: 'module', globals: globals.nodeBuiltin, }, }, { name: 'app/node-cjs', files: ['scripts/**/*.cjs'], extends: [js.configs.recommended], languageOptions: { sourceType: 'commonjs', globals: globals.node, }, }, ]);不要为了消除报错给所有文件同时加入 browser 和 node 全局变量。这样可能让浏览器代码误用 process 等环境标识符而不被发现。需要构建时注入的值,应按实际机制单独配置。八、TypeScript 基础检查安装依赖:pnpm add -D eslint @eslint/js typescript typescript-eslint globals基础配置:// eslint.config.mjs import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import globals from 'globals'; import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ globalIgnores(['**/dist/**', '**/coverage/**']), { files: ['src/**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommended, ], languageOptions: { globals: globals.browser, }, rules: { '@typescript-eslint/no-unused-vars': [ 'error', { args: 'all', argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], }, }, ]);typescript-eslint 的 recommended 预设会提供解析器及必要的规则配置,并处理部分与 TypeScript 不兼容或重复的核心规则。这份配置支持 TS/TSX 语法检查,但没有开启类型感知,也没有自动加入 React 组件或 Hooks 规则。.tsx 能解析,与是否符合框架使用约定,是两件事。TypeScript 快速入门核心规则与 TypeScript 扩展规则例如下面两个名字属于不同规则:no-unused-vars @typescript-eslint/no-unused-varsTypeScript 版本理解类型语法和 TS 代码结构。手动开启扩展规则时,通常需要关闭对应核心规则,避免冲突或重复报告;使用推荐配置时,先检查预设已经做了哪些处理,不要重复拼装。九、TypeScript 类型感知检查基础语法检查不一定能判断某个函数的返回值是不是 Promise。要使用这类规则,需要为规则提供类型信息。// eslint.config.mjs import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import globals from 'globals'; import { fileURLToPath } from 'node:url'; import { defineConfig, globalIgnores } from 'eslint/config'; const rootDir = fileURLToPath(new URL('.', import.meta.url)); export default defineConfig([ globalIgnores(['**/dist/**', '**/coverage/**']), { files: ['src/**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommendedTypeChecked, ], languageOptions: { globals: globals.browser, parserOptions: { projectService: true, tsconfigRootDir: rootDir, }, }, rules: { '@typescript-eslint/no-floating-promises': 'error', }, }, ]);字段作用recommendedTypeChecked启用推荐规则,并包含需要类型信息的检查projectService使用 TypeScript 项目服务为文件提供类型信息tsconfigRootDir指明 TS 项目相关路径处理的根目录类型感知检查需要额外工作,通常比纯语法检查更慢。应确保被检查文件属于正确的 TypeScript 项目,并与编辑器采用的 TypeScript 配置一致。类型感知配置如果出现“文件未被 project service 包含”的错误,不要直接关闭全部类型规则:先确认文件是否应该加入对应 tsconfig.json。对于构建脚本,可以单独使用不需要类型信息的配置;少量项目外文件才考虑有针对性的默认项目设置。排错说明projectService 与 parserOptions.project 不需要同时开启;使用项目服务时,应移除不必要的 project 设置。解析器选项十、Vue 3 + TypeScript 配置Vue 单文件组件同时包含 template、script、style,不能简单把 .vue 文件当普通 TypeScript 文件交给 TS 解析器。安装依赖:pnpm add -D eslint @eslint/js typescript typescript-eslint \ eslint-plugin-vue vue-eslint-parser globals以下是不启用类型感知的基础配置:// eslint.config.mjs import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import vue from 'eslint-plugin-vue'; import vueParser from 'vue-eslint-parser'; import globals from 'globals'; import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ globalIgnores(['**/dist/**', '**/coverage/**']), { files: ['src/**/*.{ts,tsx,vue}'], extends: [ js.configs.recommended, tseslint.configs.recommended, ], languageOptions: { globals: globals.browser, }, }, ...vue.configs['flat/recommended'], { files: ['src/**/*.vue'], languageOptions: { parser: vueParser, parserOptions: { parser: tseslint.parser, extraFileExtensions: ['.vue'], }, }, }, ]);关键是解析器有两层:.vue 文件 → vue-eslint-parser 理解组件结构和模板 → typescript-eslint parser 理解 script 中的 TypeScript最外层 languageOptions.parser 不能被 TS 解析器覆盖,否则 Vue 模板可能解析失败。内层解析器配置在 languageOptions.parserOptions.parser。Vue 配置说明开启 Vue 类型感知检查时,还要考虑项目服务、.vue 文件支持与框架配置的协作,不能简单把上一节所有参数复制进来。复杂项目可从框架官方提供的配置开始,再按项目调整。Vue TypeScript 配置项目recommended 可能包含模板排版规则。若项目使用 Prettier,继续配置下一节的冲突处理。十一、与 Prettier 配合安装:pnpm add -D prettier eslint-config-prettier在已有配置的末尾加入:import eslintConfigPrettier from 'eslint-config-prettier/flat'; export default defineConfig([ // 项目规则、TS 或 Vue 预设…… eslintConfigPrettier, ]);eslint-config-prettier 关闭其已知的冲突规则,本身不负责格式化,也不会自动运行 Prettier。后面若再次开启冲突规则,仍然可能出现反复修改。将格式检查单独执行:pnpm exec prettier . --check pnpm exec prettier . --writeeslint-plugin-prettier 则是另一种集成方式:把 Prettier 结果作为 ESLint 问题报告。并非必须同时安装两种方案;对大多数项目,单独执行 lint 和 format 更容易看清各自职责。配置说明十二、接入 package.json 和 CI在 package.json 中合并以下 scripts,不要覆盖项目其他字段:{ "scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", "lint:ci": "eslint . --max-warnings 0", "format": "prettier . --write", "format:check": "prettier . --check", "typecheck": "tsc --noEmit" } }Vue 项目的类型检查通常使用项目已配置的 vue-tsc 等工具,不能把 tsc --noEmit 示例当成所有框架的统一命令。CI 中通常检查而不静默修复:pnpm install --frozen-lockfile pnpm lint:ci pnpm format:check pnpm typecheckwarn 默认不会像规则错误那样使检查失败;--max-warnings 0 可以把任何警告视为未通过,因此“只是 warning”不代表 CI 一定成功。大项目可考虑 --cache,但缓存不是跳过完整验收的理由。--fix-dry-run 可用于不写入文件的修复预演,不过是否展示修复后的文本取决于输出格式,不能把它当成默认生成补丁的命令。CLI 文档十三、编辑器保存时修复以安装了 ESLint 扩展的 VS Code 为例,可以配置:{ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", "vue" ] }这只是编辑器接入,不会替代项目规则文件。显式保存时尝试修复的具体行为,还受扩展版本、工作区信任、语言模式和配置匹配影响。不要同时让多个格式化器处理同一文件,却不明确哪个负责哪类修改。命令行能运行、编辑器不工作时,应看 ESLint 扩展输出和实际工作目录。VS Code ESLint 扩展说明十四、旧版 .eslintrc 如何对应下面用于理解 .eslintrc 与 Flat Config 的对应关系,不需要同时维护两份配置。旧式配置Flat Config 中通常的处理方式.eslintrc.*eslint.config.mjs 等 Flat Config 文件env.browser / env.nodelanguageOptions.globals 中添加环境全局变量顶层 parser 字符串languageOptions.parser 导入解析器对象顶层 parserOptions放入 languageOptions.parserOptions,部分 JS 语言选项直接位于 languageOptionsplugins: ['vue']导入插件对象并注册命名空间,或使用插件预设overrides拆分为多个带 files 的配置块.eslintignore / ignorePatterns显式迁移到全局或局部 ignoresroot: trueFlat Config 不使用这个旧字段extends: ['eslint:recommended']导入 @eslint/js 后复用推荐配置迁移时需要重新检查忽略路径,不能认为 .eslintignore 会被 Flat Config 自动沿用。.eslintrc 项目中的 temp.js 等模式,也应按 Flat Config 的匹配语义决定是否写成 **/temp.js。官方迁移指南十五、怎样排查“配置没有生效”优先针对一个确切文件排查:pnpm exec eslint src/main.ts pnpm exec eslint --print-config src/main.ts pnpm exec eslint --debug src/main.ts pnpm exec eslint --inspect-config--print-config 用于看一个文件最终合并后的配置,--debug 帮助追踪加载与执行,配置检查器适合查看配置匹配关系。检查器相关组件可能需要额外安装或下载,按实际工具提示处理。调试文档症状优先检查.ts 或 .vue 不检查是否存在对应 files、解析器与预设window 或 document 未声明浏览器文件是否配置 globals.browserESM 中出现 require 使用问题是否混用了 ESM 与 CommonJS 配置插件规则找不到插件是否安装、注册名是否与规则前缀一致未使用变量重复报错是否同时启用了核心和 TS 扩展规则TS 类型规则缺少信息是否启用正确项目服务,文件是否属于项目Vue template 解析失败最外层解析器是否仍是 vue-eslint-parser某规则总被覆盖匹配到哪些配置块,后面是否重写了该规则构建产物仍被检查是否错误地把局部 ignores 当成全局忽略本地正常、CI 失败版本、锁文件、执行目录、warning 门槛是否一致十六、合理使用禁用注释临时例外可以写明规则和原因:// eslint-disable-next-line no-console -- 此命令行入口需要输出结果 console.log(result);尽量不要不带规则名地关闭整个文件所有检查。先判断是规则配置不适合这个目录、可以修正的代码问题,还是需要有理由保留的例外。对工具脚本统一允许日志,可以通过 files 配置表达,比在几十个文件中重复 disable 更容易维护。十七、团队落地时的配置顺序对已有项目,可以先从官方 recommended 起步,明确 JS、TS、Vue 的文件范围,解决解析和环境问题,再逐步增加项目规则。每增加一组规则,都应该知道它解决什么问题、误报如何处理、是否需要类型信息,以及在 CI 中采用什么门槛。不要一开始启用所有规则,再通过大量关闭规则让项目“变绿”。配置变更应和依赖锁文件一起审阅;升级预设可能改变启用的规则,即使自己的 rules 没有变化,也要重新检查差异和执行结果。ESLint 的配置最终应能回答四个问题:检查哪些文件、如何理解这些文件、执行哪些规则,以及在哪些环节阻止问题进入代码库。
2025年06月11日
0 阅读
0 评论
0 点赞
2024-04-29
Vue3 - H5项目 扫描二维码
开发环境、组件库Vue3@zxing/libraryVant注意 !!! @zxing/library插件只能运行在https环境下或localhostVue3 本地开启Https如何在Vue3 项目中开启本地 Https 可以看这篇文章 Vue3 开启Https安装npm install @zxing/library代码<script setup> import { ref, reactive, onMounted, onUnmounted } from "vue" import { BrowserMultiFormatReader } from "@zxing/library" // 我对 Vant 库的 Dialog 组件做了二次封装 import { useDialog } from "@/hooks/useDialog.ts" // ref组件 const videoRef = ref() const reader = ref() // 初始化摄像头 const init = () => { reader.value = new BrowserMultiFormatReader() reader.value.listVideoInputDevices().then(res => { // res 是当前手机的摄像头数据,是一个数组 let backDevice // 这里对res的数量作判定,如果小于等于0,则证明该设备没有摄像头 if (res.length > 0) { // 获取到后置摄像头 backDevice = findBackDevice(res) }else { useDialog({ title: "未检测到摄像头", showCancel: false, success: () => { window.history.back(); } }); } // 重置摄像头 reader.value.reset() // 获取摄像头ID let videoId = backDevice?.deviceId // 参数分别是 摄像头ID video标签 回调函数 reader.value.decodeFromVideoDevice(videoId, videoRef.value, res => { // 判断是否扫描到数据,扫描到数据会产生res if (res) { // res.text 为扫描二维码的url } }) } } const findBackDevice = (value) => { let backDevice // 对获取到的摄像头数据进行遍历 for (let i = 0; i < value.length; i++) { // 找到后置摄像头 if (value[i].label.indexOf("back") > -1) return backDevice = item } return backDevice } // 这个函数是对url的query参数进行拆分 const analyzeUrl = (url) => { let urlQueryArray = url.split('?')[1] urlQueryArray = urlQueryArray.split('&') const query = {} urlQueryArray.forEach(item => { let itemSplit = item.split('=') query[itemSplit[0]] = itemSplit[1] }) return query } onMounted(() => { initScan() }) onUnmounted(() => { reader.value.reset() }) </script> <template> <div class="scan-page"> <div class="scan-box"> <!-- 扫码所需要的video标签 --> <video ref="videoRef" class="scan-video" autoplay></video> <div class="scan-scanner"> <div class="scan-scanner-box"> <div class="scan-scanner-line"></div> <div class="scan-scanner-angle"></div> </div> </div> </div> </div> </template><style lang="less" scoped> .scan-box { position: fixed; top: 0; left: 0; height: 100%; width: 100vw; background-image: linear-gradient( 0deg, transparent 24%, rgba(32, 255, 77, 0.1) 25%, rgba(32, 255, 77, 0.1) 26%, transparent 27%, transparent 74%, rgba(32, 255, 77, 0.1) 75%, rgba(32, 255, 77, 0.1) 76%, transparent 77%, transparent ), linear-gradient( 90deg, transparent 24%, rgba(32, 255, 77, 0.1) 25%, rgba(32, 255, 77, 0.1) 26%, transparent 27%, transparent 74%, rgba(32, 255, 77, 0.1) 75%, rgba(32, 255, 77, 0.1) 76%, transparent 77%, transparent ); background-size: 3px 3px; background-position: -1px -1px; } .scan-video { height: 100vh; width: 100vw; object-fit: cover; } .scan-scanner { .scan-scanner-box { width: 213px; height: 213px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); overflow: hidden; border: 1px solid rgba(0, 255, 51, 0.2); ::after { .pseudo-content; .pseudo-content-top; right: 0; border-right-color: #00ff33; } ::before { .pseudo-content; .pseudo-content-top; left: 0; border-left-color: #00ff33; } } .scan-scanner-line { height: calc(100% - 2px); width: 100%; background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%); border-bottom: 3px solid #00ff33; transform: translateY(-100%); animation: radar-beam 2s infinite alternate; animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); animation-delay: 1.4s; ::after { .pseudo-content; .pseudo-content-bottom; right: 0; border-right-color: #00ff33; } ::before { .pseudo-content; .pseudo-content-bottom; left: 0; border-left-color: #00ff33; } } .scan-pseudo-content { content: ""; display: block; position: absolute; width: 3vw; height: 3vw; border: 2px solid transparent; } .pseudo-content-top { top: 0; border-top-color: #00ff33; } .pseudo-content-bottom { bottom: 0; border-bottom-color: #00ff33; } } @keyframes radar-beam { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } .scan-page { overflow-y: hidden; } </style>
2024年04月29日
203 阅读
0 评论
0 点赞
2024-04-29
Vue3 本地开启Https
我们日常开发中一些 API 需要运行在 HTTPS 环境下,所以需要配置本地开发环境的https访问。安装三方包npm i @vitejs/plugin-basic-ssl -D`代码 (vite.config.ts 文件中)import basicSsl from '@vitejs/plugin-basic-ssl' export default { server: { // 开启 https https: true }, plugins: [ basicSsl() ] } 这个包的作用是为你提供一个自动签名用来启动https,但是在你启动项目后,打开页面时,会提示该页面不被信任,这时候我们只需要点击 高级 -> 继续访问即可。
2024年04月29日
318 阅读
0 评论
0 点赞
2024-04-17
uniapp 开发app 与 H5 之间通信
H5 向 app 传递消息引入 uni.webview.1.5.1.js 项目是使用的 Vue3 + TS 写的 H5 页面,所以需要再 index.html 文件中引入 uni.webview.1.5.1.js 。<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.1.js"></script>使用 uni.postMessage 向app传递消息 在项目中我们使用 uni.postMessage 来向 app 传递消息。const openTripartiteMap = () = > { uni.postMessage({data: { name: '****' }}) } 如果项目是用 TS 写的,在这里可能会遇见 uni 过不去 TS 检测的问题,从而导致无法打包,所以这里我们需要用 @ts-ignore 来让 TS 忽略单行检测。@ts-ignore 需要写在需要忽略的那行代码上面。const openTripartiteMap = () = > { // @ts-ignore uni.postMessage({data: { name: '****' }}) }在 app 中接收消息 在 app 中使用 web-view 来承接 H5<web-view :src="url" @message="getMessage" @onPostMessage="getPostMessage"></web-view>getMessage(e) { console.log("@message 接收成功") if (e.detail) { console.log('根据h5传值,调用不同的方法,实现h5与app的交互', e.detail) } }, // 方法兼容 getPostMessage(e) { console.log("@onPostMessage 接收成功") if (e.detail) { console.log('根据h5传值,调用不同的方法,实现h5与app的交互', e.detail) } }
2024年04月17日
266 阅读
0 评论
1 点赞
1
2
...
6