l1n6yun's Blog

记录学习的技能和遇到的问题

最近迅雷的广告是越来越多了,每次打开都弹一堆窗口,烦都烦死了。特别是那个VIP广告,关都关不掉,真是服了。

刚好之前学到一个简单粗暴的方法——直接在 hosts 文件里把广告域名”拉黑”。原理很简单,就是把广告域名指向一个无效的IP地址,让广告加载不出来。

操作方法

找到系统的 hosts 文件,位置在:

1
C:\Windows\System32\drivers\etc\hosts

用记事本打开(需要管理员权限),在末尾添加以下内容:

1
2
3
4
# 屏蔽迅雷广告
1.2.3.4 xluser-ssl.xunlei.com
1.2.3.4 xluser2-ssl.xunlei.com
1.2.3.4 xluser3-ssl.xunlei.com

保存后重启迅雷,广告就没了。

可能的副作用

不过这个方法有个小问题——可能会影响迅雷的登录功能。毕竟这几个域名看起来像是用户相关的服务域名。

我测试了一下,不登录也能正常下载,所以对我来说问题不大。如果你需要登录账号的话,可以试试把其中某几行删掉,看看哪些是必须的。

这个小技巧虽然简单,但确实管用,希望能帮到同样被广告困扰的小伙伴。

做 PHP 开发,composer installcomposer require 基本是日常,但 Composer 的钩子脚本,估计不少同学都没怎么用过。

最近想弄一个项目模板,让其他人可以通过 composer create-project 一键安装,安装完自动把 .env.example 复制成 .env、生成密钥、提示下一步操作。折腾了一番,踩了不少坑,今天把经验整理出来,希望能帮到有同样需求的同学。

先把坑填上:type 必须是 project

这个坑我踩得很惨——模板写好了,composer create-project 却死活装不了。

问题出在 composer.jsontype 字段。想用 create-project 安装,这个字段必须是 project,填 library 的话只能 composer require

建模板其实很简单:

1
2
mkdir my-project-template && cd my-project-template
composer init

composer init 的时候注意两点:一是 Package name 要用「作者名/包名」的格式,比如 l1n6yun/my-project-template;二是 Project typeproject

生成的 composer.json 最小结构:

1
2
3
4
5
6
7
{
"name": "l1n6yun/my-project-template",
"type": "project",
"require": {
"php": ">=7.4"
}
}

然后在模板里放好 .env.exampleindex.php 这些基础文件,一个能被 create-project 识别的模板就有了。

三个钩子的区别,搞清楚就够用了

Composer 钩子有好几个,但做项目模板主要用这三个:post-root-package-installpost-autoload-dumppost-create-project-cmd

post-root-package-install:模板刚下载完

用户执行 composer create-project,Composer 把你的模板下载下来,还没开始装依赖(此时 vendor 目录还不存在),这个钩子就触发了。

最适合干的事:复制 .env.example.env

为什么?因为这时候模板文件刚到位,.env 还不存在,是复制的最佳时机,而且不用依赖任何包,执行最快。Laravel 官方也是这么干的。

1
2
3
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]

用 PHP 的 copy() 函数,Windows、Linux、Mac 通用,不用管系统命令的差异。

post-autoload-dump:vendor/autoload.php 生成后

触发时机:composer installcomposer updatecomposer dump-autoload,以及 create-project 安装依赖完成后。

简单说,依赖装好了,需要用到 vendor 里的类了,这个钩子就派上用场了。

常见用途:框架的插件自动发现、生成缓存、初始化需要加载类的操作。

1
2
3
"post-autoload-dump": [
"@php artisan package:discover --ansi"
]

注意:别用这个钩子复制 .env,这时候依赖都装完了,再复制环境文件可能会出问题。

post-create-project-cmd:全部搞定,最后一步

create-project 流程全部结束后触发,依赖装好了、自动加载器也生成了,项目完全就绪。

最适合做的事:给用户友好提示、执行最终初始化操作。

1
2
3
4
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"echo 项目安装成功!执行 php artisan serve 启动服务~"
]

一套完整配置,直接拿去用

把三个钩子组合起来,就是一套完整的脚本配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "l1n6yun/my-project-template",
"type": "project",
"require": {
"php": ">=7.4"
},
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-autoload-dump": [
"@php artisan package:discover --ansi"
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"echo 项目安装成功!"
]
}
}

最后说几个注意点

  1. 执行顺序post-root-package-installpost-autoload-dumppost-create-project-cmd,顺序不能乱,否则依赖找不到、文件生成失败都会出现。

  2. 复制 .env:用 post-root-package-install,这是官方推荐的做法。

  3. @php 是什么:Composer 的内置命令,表示用当前项目的 PHP 解释器,避免系统 PHP 版本和项目要求不一致。

  4. 本地测试:模板建好后,执行 composer create-project ./my-project-template test-project,看 .env 是否自动生成、提示信息是否正常显示。

  5. 包还没发布怎么办:如果模板还在开发阶段,没有发布到 Packagist,可以用 --repository-url 参数指定仓库地址。比如你的模板托管在 GitHub,可以这样安装:

    1
    composer create-project l1n6yun/my-project-template my-app --repository-url=https://github.com/l1n6yun/my-project-template.git

这样就能直接从 Git 仓库拉取安装,不用先发布包。本地测试也可以用这个方式,把地址换成本地路径就行。

Composer 的钩子其实不难理解,关键是搞清楚每个钩子的触发时机,按需分配操作就行。掌握这三个,项目模板的自动化初始化就搞定了,用户的安装体验也能上一个台阶。

有问题欢迎评论区交流~

有时候写 PHP 项目,需要在服务端主动发起 POST 请求——比如调用第三方接口、模拟表单提交、做个简单的采集之类的。这种需求还挺常见的,我这里整理了三种常用方式,备忘一下。

阅读全文 »

平时写代码有没有遇到过这种情况:刚提交完,发现用错账号了——公司项目用了个人邮箱,或者反过来。我就干过这事,提交完了才看到 git log 里邮箱是自己的 Gmail,尴尬得很。

Git 提交记录里的作者信息,一旦提交就写进历史了,改起来确实麻烦。但也不是不能改,今天就把几种场景的方法说清楚,遇到问题直接照抄命令就行。

先说几句注意事项,省得踩坑:

  • 改提交记录会生成新的 commit hash,相当于重写历史
  • 如果已经 push 到远程了,改完得强制推送,会覆盖远程历史
  • 团队协作的话,改之前一定要通知大家,让人家先备份一下

先看看当前提交是啥情况

改之前先确认一下现在的提交信息:

1
2
3
4
5
6
7
# 看最近5条提交的作者、邮箱
git log --pretty=format:"%h %an <%ae> %s" -5

# 看当前仓库的 git 配置
git config --list --local | grep user
# 看全局配置
git config --list --global | grep user

这样能快速定位要改的是哪条提交。

场景一:刚提交完,发现用错账号了(还没 push)

这是最常见的情况,刚 commit 完,还没 push,发现作者信息错了。这种情况最简单,一条命令搞定。

方法一:直接指定新作者

不用改配置,直接改这次提交:

1
2
# 换成你的名字和邮箱
git commit --amend --author="张三 <zhangsan@company.com>" --no-edit

--no-edit 的意思是不改提交信息,只改作者。

方法二:先改配置再同步

如果你要长期用新账号,可以先把配置改了:

1
2
3
4
5
6
7
8
9
10
# 只对当前仓库生效(推荐,不影响其他项目)
git config user.name "张三"
git config user.email "zhangsan@company.com"

# 如果所有项目都要用这个账号,加 --global
# git config --global user.name "张三"
# git config --global user.email "zhangsan@company.com"

# 然后把最近一次提交的作者更新一下
git commit --amend --reset-author --no-edit

改完再看 git log,作者信息已经变了。

场景二:之前某次提交写错了(还没 push)

错的不一定是最近一次,可能是之前某一条。这种情况要用交互式变基,精准定位那条提交。

假设要改最近 3 条提交里的某一条:

第一步:启动变基

1
2
# 查看最近3条提交
git rebase -i HEAD~3

第二步:标记要改的提交

执行后会弹出编辑器(默认 vim),显示类似这样的内容:

1
2
3
pick abc1234 第一次提交
pick def5678 第二次提交
pick ghi9012 第三次提交

每条前面是 pick,表示保留这条提交。把要改的那条前面的 pick 改成 edit(或者简写 e),然后保存退出(vim 里按 Esc,输入 :wq 回车)。

第三步:修改作者

Git 会停在你标记的那条提交上,提示你可以修改了:

1
git commit --amend --author="张三 <zhangsan@company.com>" --no-edit

第四步:继续变基

1
git rebase --continue

如果要改多条,会一条条停让你改,重复步骤三四就行。

如果变基过程中冲突了,先解决冲突,然后 git add .,再 git rebase --continue

场景三:批量改,仓库里好多提交都要换人

如果是离职员工的提交要统一改掉,或者换企业邮箱后要批量更新,一条条改太累了。这种情况用 git filter-branch 批量处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 把旧邮箱的所有提交都改成新的作者信息
git filter-branch --env-filter '
OLD_EMAIL="old@example.com"
CORRECT_NAME="张三"
CORRECT_EMAIL="zhangsan@company.com"

# 改提交者
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]; then
export GIT_COMMITTER_NAME="$CORRECT_NAME"
export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi

# 改作者
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]; then
export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' -f --tag-name-filter cat -- --branches --tags

参数说明:

  • --env-filter:通过环境变量过滤修改
  • -f:强制覆盖之前的备份
  • --tag-name-filter cat:同步更新标签
  • --branches --tags:处理所有分支和标签

更快的工具:git filter-repo

git filter-branch 处理大仓库比较慢,官方推荐用 git filter-repo

1
2
3
4
5
6
7
8
9
10
11
12
# 先安装
pip install git-filter-repo

# 批量替换
git filter-repo --commit-callback '
if commit.author_email == b"old@example.com":
commit.author_name = b"张三"
commit.author_email = b"zhangsan@company.com"
if commit.committer_email == b"old@example.com":
commit.committer_name = b"张三"
commit.committer_email = b"zhangsan@company.com"
'

已经 push 了怎么办?

前面说的都是没 push 的情况。如果已经 push 到远程了,改完本地历史后必须强制推送:

1
2
3
4
5
# 推单个分支
git push origin main --force

# 推所有分支(慎用)
git push origin --force --all

强制推送会覆盖远程历史,团队协作的话一定要提前通知,让人家先 git pull --rebase 同步一下。

验证一下改没改对

改完确认一下:

1
2
3
4
5
6
# 看最近几条提交
git log --pretty=format:"%h %an <%ae> %s" -5

# 对比本地和远程
git fetch origin
git log --oneline HEAD..origin/main

没输出就说明同步好了。

几个常见问题

Q: vim 编辑器不会用怎么办?

vim 操作:按 i 进入编辑模式,改完按 Esc,输入 :wq 回车保存退出。

想换成记事本的话:git config --global core.editor notepad

Q: 强制推送后同事拉代码报错?

让同事执行:git pull --rebase origin main,有冲突解决冲突,然后 git add .,再 git rebase --continue

Q: 改完 commit hash 变了?

正常的,hash 是根据提交内容、作者、时间算出来的,改了作者 hash 肯定变。不影响功能,让大家同步一下就行。

Q: 能只改名字不改邮箱吗?

可以:git commit --amend --author="新名字 <旧邮箱>" --no-edit

总结

改 Git 提交作者,记住几个场景:

  • 刚提交还没 push:git commit --amend,最简单
  • 之前某条提交要改:git rebase -i,精准定位
  • 批量改:git filter-branchgit filter-repo

核心原则:没 push 的随便改,push 过的要谨慎,改完记得通知团队成员同步。

以后再遇到用错账号的情况,照着上面的命令抄就行,不用慌。

在软件开发过程中,代码质量是一个永恒的话题。随着项目规模的扩大和团队成员的增加,如何保证代码质量成为一个挑战。SonarQube 作为一款开源的代码质量管理平台,能够帮助团队在开发过程中持续检测代码质量问题,是 DevOps 和 CI/CD 流程中不可或缺的工具。

SonarQube

什么是 SonarQube

SonarQube 是一个开源的代码质量管理平台,用于管理源代码的质量。它支持多种编程语言,包括 Java、C#、JavaScript、TypeScript、Python、PHP、Go 等 29+ 种语言。

SonarQube 通过静态代码分析,从以下七个维度检测代码质量:

  1. 可靠性 - 检测 Bug 和潜在的错误
  2. 安全性 - 发现安全漏洞和安全隐患
  3. 可维护性 - 检测代码异味(Code Smells)
  4. 覆盖率 - 单元测试覆盖率分析
  5. 重复度 - 检测重复代码块
  6. 复杂度 - 分析代码复杂度
  7. 文档 - 检查代码注释情况

SonarQube 架构

SonarQube 主要由以下几个组件组成:

1
2
3
4
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│ Scanner │ ───► │ SonarQube │ ───► │ Database │
│ (代码扫描器) │ │ Server │ │ (数据库) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
  • Scanner:代码扫描器,负责分析源代码并将结果发送到 SonarQube Server
  • Server:SonarQube 服务器,负责处理分析结果、生成报告、提供 Web 界面
  • Database:存储配置信息、项目快照、分析结果等数据

安装部署

使用 Docker 快速部署

最简单的方式是使用 Docker Compose 部署 SonarQube:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: "3"

services:
sonarqube:
image: sonarqube:community
depends_on:
- db
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
ports:
- "9000:9000"

db:
image: postgres:15-alpine
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
POSTGRES_DB: sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data

volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:

启动服务:

1
$ docker-compose up -d

访问 http://localhost:9000 即可打开 SonarQube Web 界面,默认账号密码为 admin/admin

系统要求

SonarQube 对系统有一定要求,特别是 Elasticsearch 组件:

1
2
3
4
5
# Linux 系统需要设置以下参数
$ sysctl -w vm.max_map_count=262144
$ sysctl -w fs.file-max=65536
$ ulimit -n 65536
$ ulimit -u 4096

可以将这些配置写入 /etc/sysctl.conf 文件使其永久生效。

代码扫描

安装 Scanner

SonarQube 提供了多种 Scanner 工具:

  • SonarScanner CLI - 通用命令行扫描器
  • SonarScanner for Maven - Maven 项目专用
  • SonarScanner for Gradle - Gradle 项目专用
  • SonarScanner for .NET - .NET 项目专用

安装 SonarScanner CLI

1
2
3
4
5
6
7
# 下载并解压
$ wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
$ unzip sonar-scanner-cli-5.0.1.3006-linux.zip
$ mv sonar-scanner-5.0.1.3006-linux /opt/sonar-scanner

# 配置环境变量
$ export PATH=$PATH:/opt/sonar-scanner/bin

创建配置文件

在项目根目录创建 sonar-project.properties 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 项目基本配置
sonar.projectKey=my-project
sonar.projectName=My Project
sonar.projectVersion=1.0

# 源代码路径
sonar.sources=src
sonar.tests=tests

# 语言配置
sonar.language=php

# 排除不需要扫描的文件
sonar.exclusions=**/vendor/**,**/node_modules/**,**/tests/**

# 编码
sonar.sourceEncoding=UTF-8

执行扫描

1
2
3
4
5
# 登录 SonarQube 创建项目并获取 token
# 然后执行扫描
$ sonar-scanner \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=your_token_here

Maven 项目扫描

对于 Maven 项目,可以直接使用插件:

1
2
3
4
5
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
</plugin>

执行扫描:

1
2
3
$ mvn clean verify sonar:sonar \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=your_token_here

质量门禁

质量门禁(Quality Gate)是 SonarQube 的核心功能之一,用于定义代码质量的标准。只有通过质量门禁的代码才能被合并或部署。

默认质量门禁

SonarQube 提供了默认的质量门禁标准:

指标 条件
新代码覆盖率 >= 80%
新代码重复度 <= 3%
新代码中的 Bug = 0
新代码中的漏洞 = 0
新代码中的代码异味 = 0

自定义质量门禁

可以根据项目实际情况自定义质量门禁:

  1. 进入「Quality Gates」页面
  2. 点击「Create」创建新的质量门禁
  3. 添加条件,如:
    • Coverage < 50%
    • Duplicated Lines (%) > 5%
    • Maintainability Rating != A
  4. 将质量门禁设置为项目默认

代码规则与配置

SonarQube 内置了大量代码规则,涵盖多个方面:

规则类型

  • Bug - 可能导致程序错误的代码
  • Vulnerability - 安全漏洞
  • Code Smell - 代码异味,影响可维护性
  • Security Hotspot - 需要人工审查的安全敏感代码

质量配置

质量配置(Quality Profile)是规则的集合。可以:

  1. 继承内置配置并修改
  2. 激活或停用特定规则
  3. 调整规则严重级别(Blocker、Critical、Major、Minor、Info)
  4. 排除特定规则
1
2
3
4
# 示例:排除特定规则
sonar.issue.ignore.multicriteria=e1
sonar.issue.ignore.multicriteria.e1.ruleKey=php:S1234
sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.php

与 CI/CD 集成

Jenkins 集成

安装 SonarQube Scanner for Jenkins 插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Jenkinsfile
pipeline {
agent any

stages {
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('My SonarQube Server') {
sh 'mvn clean verify sonar:sonar'
}
}
}

stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}

GitLab CI/CD 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .gitlab-ci.yml
sonarqube-check:
image: maven:3.8.5-openjdk-17
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: "0"
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- mvn verify sonar:sonar -Dsonar.projectKey=${CI_PROJECT_NAME}
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_COMMIT_BRANCH == 'main'

GitHub Actions 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# .github/workflows/sonarqube.yml
name: SonarQube Scan
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]

jobs:
sonarqube:
name: SonarQube
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

分支分析与 PR 装饰

分支分析

SonarQube Community 版本支持主分支分析,Developer 版本及以上支持多分支分析:

1
2
3
$ sonar-scanner \
-Dsonar.branch.name=feature-123 \
-Dsonar.branch.target=main

PR 装饰

Pull Request 装饰功能可以在 PR 中直接显示代码质量问题:

1
2
3
4
5
$ sonar-scanner \
-Dsonar.pullrequest.key=123 \
-Dsonar.pullrequest.branch=feature-123 \
-Dsonar.pullrequest.base=main \
-Dsonar.pullrequest.github.repository=myorg/myrepo

常用 API

SonarQube 提供了丰富的 REST API:

1
2
3
4
5
6
7
8
9
10
11
# 获取项目列表
$ curl -u token: http://localhost:9000/api/projects/search

# 获取项目质量门禁状态
$ curl -u token: http://localhost:9000/api/qualitygates/project_status?projectKey=my-project

# 获取问题列表
$ curl -u token: "http://localhost:9000/api/issues/search?componentKeys=my-project"

# 获取度量数据
$ curl -u token: "http://localhost:9000/api/measures/component?component=my-project&metricKeys=ncloc,coverage,bugs,vulnerabilities,code_smells"

最佳实践

1. 聚焦新代码

SonarQube 的「New Code」功能帮助你聚焦于新增和修改的代码,避免被历史债务淹没:

1
2
# 配置新代码周期
sonar.newCode.referenceBranch=main

2. 设置合理的质量门禁

不要过于激进,根据项目实际情况设置可达成的目标,逐步提升标准。

3. 定期清理技术债务

定期查看技术债务报告,安排时间修复高优先级问题。

4. 排除生成代码

自动生成的代码通常不需要扫描:

1
sonar.exclusions=**/generated/**,**/*_generated.php

5. 使用增量分析

对于大型项目,启用增量分析可以加快扫描速度:

1
sonar.analysis.cache.enabled=true

常见问题

1. 内存不足

如果扫描过程中出现内存不足错误,可以增加 Scanner 的内存:

1
$ export SONAR_SCANNER_OPTS="-Xmx2048m"

2. 编码问题

确保项目使用 UTF-8 编码:

1
sonar.sourceEncoding=UTF-8

3. 排除第三方代码

1
sonar.exclusions=**/vendor/**,**/node_modules/**,**/dist/**

版本对比

功能 Community Developer Enterprise Data Center
价格 免费 €150k/年 €250k/年 联系销售
语言支持 15+ 29+ 29+ 29+
分支分析 主分支 多分支 多分支 多分支
PR 装饰
安全报告 基础 完整 完整 完整
高可用

  1. 在地址栏输入 about:debugging
  2. 点击 This Firefox
  3. 找到目标扩展 → 点击 Inspect(调试)
  4. 在新打开的开发者工具中,切换到 Network 面板,即可看到该扩展发起的请求

upload successful

最近在搞一个 Vue3 + Vite 的项目,想着给项目加上 ESLint,结果发现 ESLint 现在推荐用新的 flat config 格式了,跟之前那种 .eslintrc.js 的写法完全不一样。折腾了一阵子终于搞定,记录一下配置过程,希望能帮到同样遇到这个问题的小伙伴。

安装依赖

首先当然是装 ESLint 和相关插件啦:

1
npm install -D eslint @eslint/js eslint-plugin-vue

这里简单说一下装的几个包都是干啥的:

  • eslint - ESLint 核心,不用说
  • eslint/js - JavaScript 官方推荐的规则集
  • eslint-plugin-vue - Vue 官方的 ESLint 插件,专门检查 Vue 组件的

写配置文件

ESLint 现在推荐使用 flat config 格式,配置文件名是 eslint.config.js,写法跟以前差别挺大的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'

export default [
{
name: 'ignores',
ignores: [
'dist/**',
'node_modules/**',
]
},
js.configs.recommended,
...pluginVue.configs['flat/recommended'],
{
languageOptions: {
globals: {
localStorage: 'readonly',
sessionStorage: 'readonly',
window: 'readonly',
document: 'readonly',
console: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
fetch: 'readonly',
URL: 'readonly',
process: 'readonly'
}
},
rules: {
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'warn',
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': 'off',
'no-debugger': 'warn'
}
}
]

这里有几个坑说一下:

第一个坑:一定要在配置里声明浏览器全局变量!不然 ESLint 会报 localStorage is not defined 之类的错误。上面配置里的 globals 就是干这个的。

第二个坑ignores 要放在第一个配置对象里单独写,不能跟其他配置混在一起。

自定义规则说明

  • vue/multi-word-component-names: 'off' - 这个规则要求组件名必须是多单词的,我习惯用单词组件名,干脆关掉了
  • vue/no-v-html: 'warn' - 用 v-html 有 XSS 风险,改成 warn 提醒自己注意
  • no-unused-vars - 未使用变量改成警告,argsIgnorePattern: '^_' 表示以 _ 开头的参数可以忽略(有时候回调函数的参数不用但又必须写在前面)

配置 npm 脚本

package.json 里加上:

1
2
3
4
5
6
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}

这样就可以用 npm run lint 检查代码,用 npm run lint:fix 自动修复能修的问题。

集成到 Vite

光有命令行检查还不够,开发的时候能实时看到问题更方便。装个 vite-plugin-eslint:

1
npm install -D vite-plugin-eslint

然后在 vite.config.js 里加上:

1
2
3
4
5
6
7
8
import eslint from 'vite-plugin-eslint'

export default defineConfig({
plugins: [
vue(),
eslint(),
],
})

这样 Vite 开发服务器启动后,代码有问题会直接在页面上弹出来,再也不用手动跑 lint 命令了。

一些常见问题

中文全角空格报错

有时候复制代码会带上中文全角空格,ESLint 会报 no-irregular-whitespace。这个一般 IDE 能看出来,注意一下就行。

未使用的导入

no-unused-vars 这个规则经常会报,比如:

1
2
3
4
5
// 报错:nextTick 未使用
import { nextTick, ref } from 'vue'

// 改成这样
import { ref } from 'vue'

如果有些变量确实暂时不用但想留着,可以在变量名前加个下划线 _,因为上面配置了 argsIgnorePattern: '^_'

小结

ESLint 配置这东西,一开始看着挺麻烦的,配好之后真的能避免很多低级错误。尤其是多人协作的项目,统一代码风格太重要了。flat config 虽然写法变了,但配置逻辑其实更清晰了,习惯了就好。

如果这篇对你有帮助,欢迎留言交流~

最近在写项目的时候用到了 Element Plus 的下拉菜单组件 el-dropdown,本来挺好用的,但是每次鼠标悬浮或者点击的时候,总会冒出来一个黑色的边框,看着特别别扭。

这个边框其实是浏览器自带的焦点样式,就是那种 tab 切换时候会显示的轮廓线。虽然从可访问性的角度来说这是个好东西,但有时候跟设计稿对不上,还是得把它干掉。

怎么解决呢?

其实很简单,就是用 CSS 把 outline 去掉就行。但是因为 el-dropdown 内部的样式是 scoped 的,所以得用样式穿透才能生效。

Vue 3 的写法

1
2
3
:deep(.el-tooltip__trigger:focus) {
outline: none;
}

这里要注意一下,el-dropdown 内部触发下拉的元素有个 el-tooltip__trigger 类,我们就是要把这个类上的焦点样式去掉。

Vue 2 的写法

如果你还在用 Vue 2 的话,写法稍微有点不一样:

1
2
3
::v-deep .el-tooltip__trigger:focus-visible {
outline: unset;
}

一点小知识

顺便说一下 outlineborder 的区别吧,刚开始学 CSS 的时候老搞混:

  • outline 不占空间,画在元素外面,不影响布局
  • border 占空间,会影响元素的尺寸

所以这个焦点边框才会”凭空出现”,不会把页面撑开或者挤跑其他元素。

完整代码

放一段完整的代码,方便直接复制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<el-dropdown>
<span class="dropdown-link">
下拉菜单
<el-icon><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>选项一</el-dropdown-item>
<el-dropdown-item>选项二</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>

<style scoped>
:deep(.el-tooltip__trigger:focus) {
outline: none;
}
</style>

最后

这个小问题当时还是让我找了一会,因为一开始不知道要穿透的类名是 el-tooltip__trigger,一直在 el-dropdown 那里瞎折腾。希望能帮到同样遇到这个问题的小伙伴~

最近做项目要发邮件,什么注册确认、密码重置、订单通知,一堆功能都离不开邮件。PHP 自带的 mail() 函数?说实话,用过的都知道有多坑——配置麻烦、兼容性差、容易被当垃圾邮件。最后还是老老实实用 PHPMailer,踩了一些坑,今天把经验整理一下。

为什么是 PHPMailer

PHPMailer 是 PHP 里最老牌的邮件发送库,功能齐全:

  • 支持 SMTP 认证,TLS/SSL 加密
  • HTML 邮件、附件、嵌入式图片都能搞定
  • 抄送、密送、回复地址这些不在话下

环境要求:PHP 5.5 ~ 8.3,需要开启 mbstringopenssl 扩展。

安装

推荐用 Composer,省心:

1
composer require phpmailer/phpmailer

不想用 Composer 也行,去 GitHub 下载源码,手动引入文件。但都 2026 年了,还是 Composer 吧。

邮箱服务器配置

发邮件得有 SMTP 服务器,常用的 163 和 QQ 邮箱配置如下:

邮箱 SMTP 服务器 SSL 端口
163 smtp.163.com 465 或 994
QQ smtp.qq.com 465 或 587

重要提醒:163、QQ 这些邮箱默认关闭 SMTP 服务,得去邮箱设置里手动开启,然后生成一个「授权码」。后面代码里的密码填的是授权码,不是你的登录密码!

基础用法

直接上代码,能跑的才是好教程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
// 服务器配置
$mail->CharSet = "UTF-8";
$mail->isSMTP();
$mail->Host = 'smtp.163.com';
$mail->SMTPAuth = true;
$mail->Username = 'your@163.com'; // 发件邮箱
$mail->Password = '授权码'; // 授权码,不是密码!
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;

// 收发件人
$mail->setFrom('your@163.com', '发件人名字');
$mail->addAddress('receiver@example.com', '收件人名字');

// 邮件内容
$mail->isHTML(true);
$mail->Subject = '邮件标题';
$mail->Body = '<h1>这是 HTML 邮件内容</h1>';

$mail->send();
echo '发送成功';
} catch (Exception $e) {
echo "发送失败: {$mail->ErrorInfo}";
}

发送附件

加附件一行代码的事:

1
2
$mail->addAttachment('/path/to/file.zip');           // 普通附件
$mail->addAttachment('/path/to/image.jpg', '图片.jpg'); // 重命名附件

注意路径要写对,相对路径、绝对路径都行,但要确保文件存在。

嵌入图片到邮件正文

想在邮件正文里显示图片,有两种方式:一种是用 <img src="cid:图片id"> 配合 addEmbeddedImage,另一种是直接 base64 编码嵌入。推荐前者:

1
2
$mail->Body = '<h1>你好</h1><img src="cid:logo">';
$mail->addEmbeddedImage('images/logo.png', 'logo');

常见坑和解决方案

1. SMTP connect failed

最常见的错误。原因可能是:

  • SMTP 服务没开启(去邮箱设置里开)
  • 端口被防火墙拦截(换个端口试试:25、465、587)
  • 服务器地址写错了

2. 认证失败

大概率是密码填错了。163、QQ 邮箱要用授权码,不是登录密码。授权码在邮箱设置里生成。

3. 中文乱码

编码问题,加上这句:

1
$mail->CharSet = "UTF-8";

4. SSL 连接失败

检查 PHP 有没有开启 openssl 扩展,php.ini 里把 extension=openssl 前面的分号去掉,重启服务。

5. 邮件进了垃圾箱

原因很多:发件人和 SMTP 认证邮箱不一致、邮件内容太像广告、域名没有配置 SPF 记录。生产环境建议用专业邮件服务,比如 SendGrid、Mailgun,比自己搭 SMTP 靠谱。

调试技巧

开发阶段遇到问题,打开调试模式:

1
$mail->SMTPDebug = 2;  // 级别 0-4,数字越大越详细

调试完记得关掉,生产环境开调试会暴露敏感信息。

一个完整的例子

把上面的拼起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
$mail->CharSet = "UTF-8";
$mail->SMTPDebug = 0; // 生产环境用 0
$mail->isSMTP();
$mail->Host = 'smtp.163.com';
$mail->SMTPAuth = true;
$mail->Username = 'your@163.com';
$mail->Password = 'your-authorization-code';
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;

$mail->setFrom('your@163.com', 'l1n6yun');
$mail->addAddress('receiver@example.com');
$mail->addReplyTo('your@163.com', 'Reply');

// 附件
$mail->addAttachment('files/report.pdf');

$mail->isHTML(true);
$mail->Subject = '测试邮件';
$mail->Body = '<h2>邮件发送成功!</h2><p>这是一封测试邮件。</p>';
$mail->AltBody = '邮件发送成功!这是一封测试邮件。'; // 纯文本备用

$mail->send();
echo '邮件发送成功';
} catch (Exception $e) {
echo "邮件发送失败: {$mail->ErrorInfo}";
}

PHPMailer 用起来其实不难,主要是 SMTP 配置容易踩坑。记住几个要点:授权码、端口、编码、调试模式,基本就能搞定大部分场景了。

有问题欢迎评论区交流~

最近有个需求,要定时截取几个网页的页面截图,还得把动态加载的内容也截进去。一开始用 PHP 的 file_get_contents 和 cURL 抓,结果发现那些内容是 JavaScript 渲染出来的,抓回来全是空白。折腾半天,最后找到了 chrome-php 这个库,用 PHP 直接控制 Chrome 浏览器,问题迎刃而解。

阅读全文 »
0%