Chemmy's Blog

chengming0916@outlook.com

解决SuperMicro主板风扇转速过低告警

现象

系统启动后风扇忽高忽低,进入IPMI后台可以看到,看到传感器日志里大量的告警

告警

造成此问题的原因是风扇转速过低,触发了超微的风扇转速允许的下限,从而强制满速运转,而在满速后主板又很快发现没有问题,且此时温度较低,风扇开始降速,直到降速到下限以下,重复此过程。

在进入IPMI后台管理界面后风扇速度有四种智能模式可调

  • Standard: zone0和zone1 风速为50%

  • Optimal: 风速为30%

  • Full: 风速为100%

  • Heavy IO: zone0 为50%,zone1 为75%

解决方法

1. 在服务器上安装IPMItool

1
2
apt install ipmitool # Ubuntu/Debian指令
yum install ipmitool # CentOS指令

2. 设置风扇转速

1
2
3
# 风扇名可以看告警里边的对应风扇名
ipmitool sensor thresh FAN1 lower 100 125 125
ipmitool sensor thresh FANA lower 100 125 125

3. Windows 下远程操作IPMI

ipmitool windows 版 下载地址

1
2
ipmitool -H [IPMI网口IP地址] -U [IMPI账户] -P [IPMI密码] sensor thresh FAN1 lower 100 125 125
ipmitool -H [IPMI网口IP地址] -U [IMPI账户] -P [IPMI密码] sensor thresh FANA lower 100 125 125

问题

如果你运行上面的命令后,风扇转速回落后马上又返回原样,这表明服务器的自动调速覆盖了你手动设置的转速。你需要切换服务器风扇策略为全速(Full Speed),在这个策略下服务器不会使用自动调节转速,因此也不会覆盖你手动设置的转速。

运行下面的命令切换到全速模式(也可以进入IPMI界面调整):

1
2
ipmitool -H [IPMI网口IP地址] -U [IMPI账户] -P [IPMI密码] 0x30 0x45 0x01 0x01
# 最后一个0x01表示全速模式。如果为0x00则表示标准(Standard);0x02表示最优(Optimal)

参考

1.解决超微 SuperMicro 主板风扇反复高低转速问题 - 哔哩哔哩 (bilibili.com)

超微服务器Supermicro X9/X10/X11设置风扇转速 - 辰宸的备忘录 (licc.tech)

ipmitool常用命令详解_ipmitool lan set_owlcity123的博客-CSDN博客

服务端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[common]
# TCP通信端口
bind_port = 7000

#UDP通信端口
bind_udp_port = 7001

# 最大连接数
max_pool_count = 50

# 仪表板界面配置
dashboard_port=7500
dashboard_user=admin
dashboard_pwd=admin

# 允许使用的端口号,可以指定范围也可以用‘,’分割
allow_ports = 18081-18090,8080

服务端开机自启配置

1
2
3
sudo vim /etc/systemd/system/frps.service
sudo systemctl enable frps.service
sudo systemctl start frps.service

启动文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description = Frp Server Service
After = network.target

[Service]
Type = simple
User = nobody
Restart = on-failure
RestartSec = 5s
ExecStart = /usr/local/bin/frps -c /usr/local/etc/frp/frps.ini

[Install]
WantedBy = multi-user.target

客户端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[common]
#替换IP地址为服务端IP
server_addr=0.0.0.0
server_port=7000

# windows远程桌面
[rdp]
type=tcp
# 映射IP
local_ip=127.0.0.1
# 映射端口(本地)
local_port=3389
# 远程端口(服务器)注意端口要在允许端口内切未被占用
remote_port=18087

准备工作

制作启动盘

官方镜像
Minimal CD Stage
国内加速
清华大学开源软件镜像站
中国科技大学开源镜像站

使用Rufus制作启动U盘.

image-20240829112011985

连接网络

Linux配置网络及SSH配置

分区规划

参考 Linux硬盘分区

挂载分区

1
2
3
4
5
6
7
8
mkdir /mnt/gentoo
mount /dev/sdx? /mnt/gentoo
mkdir /mnt/gentoo/home
mount /dev/sdx? /mnt/gentoo/home
mkdir /mnt/gentoo/boot
mount /dev/sdx? /mnt/gentoo/boot
mkdir /mnt/gentoo/boot/efi
mount /dev/sdx? /mnt/gentoo/boot/efi

配置Portage

释放stage

1
2
3
4
5
6
7
8
# 使用命令行浏览器下载stage
links http://www.gentoo.org/main/en/mirrors.xml

# 发送stage3
scp stage3-amd64-*.tar.xz root@192.168.0.2:/mnt/gentoo
cd /mnt/gentoo
# 释放stage3
tar xpvf stage3-*.tar.bz2 --xattrs-include='*.*' --numeric-owner

挂载系统必要环境

1
2
3
4
5
6
7
mount --types proc /proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
#mount --make-rslave /mnt/gentoo/sys (不使用systemd,所以注释掉)
mount --rbind /dev /mnt/gentoo/dev
#mount --make-rslave /mnt/gentoo/dev (不使用systemd,所以注释掉)
mount --rbind /run /mnt/gentoo/run
#mount --make-slave /mnt/gentoo/run (不使用systemd,所以注释掉)

复制DNS

1
cp --dereference /etc/resolv.conf /mnt/gentoo/etc/

配置软件源

常规源

选择中国源,这一步是非必须的,提供的/etc/portage/make.conf里已经有中国的所有源了

1
mirrorselect -i -o >> /mnt/gentoo/etc/portage/make.conf   
创建主仓库
1
2
3
4
5
6
7
8
9
10
11
mkdir -p -v /mnt/gentoo/etc/portage/repos.conf

cp -v /mnt/gentoo/usr/share/portage/config/repos.conf /mnt/gentoo/etc/portage/repos.conf/gentoo.conf

# 加入中国源
nano -w /mnt/gentoo/etc/portage/repos.conf/gentoo.conf:

## 源地址
sync-uri = rsync://mirrors.tuna.tsinghua.edu.cn/gentoo-portage/
#sync-uri = rsync://rsync.mirrors.ustc.edu.cn/gentoo-portage/
#sync-uri = rsync://mirrors.yun-idc.com/gentoo-portage/
二进制源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改二进制源地址为国内源
nano -w /etc/portage/binrepos.conf/gentoobinhost.conf

# 原有内容
# ---
# These settings were set by the catalyst build script that automatically
# built this stage.
# Please consider using a local mirror.

[gentoobinhost]
priority = 1
# sync-uri = https://distfiles.gentoo.org/releases/amd64/binpackages/23.0/x86-64
sync-uri = https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/binpackages/23.0/x86-64/
# sync-uri = https://mirrors.ustc.edu.cn/gentoo/releases/amd64/binpackages/23.0/x86-64/

如果启用二进制源需要在USE中添加 getbinpkg binpkg-request-signature

生成fstab

1
genfstab -U /mnt/gentoo >> /mnt/gentoo/etc/fstab

生成的fstab格式如下

1
2
3
UUID=......      /boot/efi      vfat      noauto,defaults,noatime,umask=0077                               0 2
UUID=...... / xfs defaults,noatime 0 1
UUID=...... /home xfs noatime,discard

系统配置

进入新系统环境

从现在开始,所有的动作将立即在新 Gentoo Linux 环境里生效。

1
2
3
4
chroot /mnt/gentoo /bin/bash
env-update
source /etc/profile
export PS1="(chroot) ${PS1}" # 切换提示符,避免混淆

同步stage

1
emerge-webrsync

^注意: gentoo handbook上提到可以使用emerge -rsync升级软件包数据库到最近2小时的最新版,这是没有必要的,而且下载的速度会极其慢,所以不推荐这样做。单用emerge-webrsync就可以同步数据库到最近3~4天内的最新版了。

设置profile

1
2
eselect profile list     #查看profile予设值
eselect profile set X #这里先保持选择默认值,即“default/linux/amd64/17.1 (stable)”

检测cpu指令集

1
2
3
4
5
6
7
# 安装cpuid2cpuflags
emerge --ask app-portage/cpuid2cpuflags

# 查看CPU指令集
cpuid2cpuflags

echo "*/* $(cpuid2cpuflags)" > /etc/portage/package.use/00cpu-flags

安装CCache(可选,加速编译)

1
2
3
4
emerge --ask ccache 
mkdir -p /var/cache/ccache
chown root:portage /var/cache/ccache -R
chmod 2775 /var/cache/ccache -R

在portage/make.conf中添加

1
2
3
4
FEATURES="ccache -test" 
CCACHE_DIR="/var/cache/ccache"

USE="... ${FEATURES}" # 在USE中添加${FEATURES}

安装Aria2(可选,加快包下载)

1
emerge --ask net-misc/aria2

在portage/make.conf中添加配置

1
2
3
4
5
6
7
DISTDIR="/var/cache/distfiles"
FETCHCOMMAND="/usr/bin/aria2c -d \${DISTDIR} -o \${FILE} \
--allow-overwrite=true --max-tries=5 --max-file-not-found=2 \
--max-concurrent-downloads=5 --connect-timeout=5 --timeout=5 \
--split=5 --min-split-size=2M --lowest-speed-limit=20K \
--max-connection-per-server=9 --uri-selector=feedback \${URI}"
RESUMECOMMAND="${FETCHCOMMAND}"

配置编译选项

1
nano /mnt/gentoo/etc/portage/make.conf

^注意: 设置编译标志 -march=native (如果你知道自己处理器的代号,就用自己的处理器代号替换这里的native 比如我的是skylake,如果不确定就使用native)
^注意: 设置 MAKEOPTS=”-j8” 来定义安装软件时并行编译的数量 这个数字等于你的CPU线程数(也称为逻辑CPU数)参考MAKEOPTS WiKi

完整的配置文件如下(转自Gentoo安装流程分享(step by step),第一篇之基本系统的安装,修改了下注释格式,删除不用的部分)

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# These settings were set by the catalyst build script that automatically
# built this stage.
# Please consult /usr/share/portage/config/make.conf.example for a more
# detailed example.
# GCC编译配置 -O3代表优化级别,如果采用更高的-Ofast可能会导致部分软件包编译错误,
# -march=native代表为本机cpu进行编译,如果是交叉编译需要去掉
COMMON_FLAGS="-march=skylake -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"

# 源代码包构建时传递给`make`的参数
# 同时编译的线程数,根据cpu线程数和内存大小/2中较小的
MAKEOPTS="-j8"

# 系统上托管的主软件包存储库,其默认值为 `/var/db/repos/gentoo`
PORTDIR="/var/db/repos/gentoo"

# Portage存储下载的源代码归档的位置,默认为新安装的`/var/cache/distfiles`
DISTDIR="/var/cache/distfiles"

#Portage临时文件的位置,默认为`/var/tmp`
# 如果内存足够大(8G、16G),那么建议把编译程序时存放临时中间文件的目录设置
# 为内存的tmpfs(/tmp目录),以减少编译时对硬盘的大量读写、延长硬盘使用寿命、
# 并加快编译速度;但如果你的内存较小(<=4G),那么建议把此项注释掉,否则很多
# 程序会因内存容量不足而导致编译失败
PORTAGE_TMPDIR="/tmp"

# NOTE: This stage was built with the bindist Use flag enabled

# This sets the language of build output to English.
# Please keep this setting intact when reporting bugs.
LC_MESSAGES=C

# 同步镜像
GENTOO_MIRRORS="https://mirrors.tuna.tsinghua.edu.cn/gentoo"
# 备选
# GENTOO_MIRRORS="https://mirrors.ustc.edu.cn/gentoo/"
# GENTOO_MIRRORS="https://mirrors.aliyun.com/gentoo/"
# GENTOO_MIRRORS="https://mirrors.cloud.tencent.com/gentoo/"
# GENTOO_MIRRORS="https://mirrors.huaweicloud.com/gentoo/"

# emerge的默认选项
EMERGE_DEFAULT_OPTS="--keep-going --with-bdeps=y --quiet --ask --verbose"

# 每次安装完包之后自动清理
AUTO_CLEAN="yes"

# 指定软件包的可用性和稳定性级别。
# 如果更喜欢最新那这里用~amd64(接受安装和更新处于测试阶段的软件包)
ACCEPT_KEYWORDS="amd64"
# 接受所有许可证的软件
ACCEPT_LICENSE="*"

# 语言设置
L10N="en-US zh-CN en zh"
LINGUAS="en_US zh_CN en zh"

# intel集成显卡和nvidia显卡(不使用novueau)
VIDEO_CARDS="intel i965 iris nvidia"

# intel声卡
ALSA_CARDS="hda_intel"

# 输入设备 非笔记本去除后面的synaptics
INPUT_DEVICES="libinput synaptics"

# 设置GRUB版本
GRUB_PLATFORMS="efi-64"

# 使用ccache来大大提高重新编译时的速度,安装ccache后解除注释
# CCACHE="parallel-fetch ccache"
# ccache使用的目录
# CCACHE_DIR="/var/cache/ccache"

# 使用aria2提高下载速度(不设置也无大碍,设置的话一定要注意指令拼写正确),
# 安装aria2后解除注释
# FETCHCOMMAND="/usr/bin/aria2c -d \${DISTDIR} -o \${FILE} \
# --allow-overwrite=true --max-tries=5 --max-file-not-found=2 \
# --max-concurrent-downloads=5 --connect-timeout=5 --timeout=5 \
# --split=5 --min-split-size=2M --lowest-speed-limit=20K \
# --max-connection-per-server=9 --uri-selector=feedback \${URI}"
# RESUMECOMMAND="${FETCHCOMMAND}"

# USE变量
# 用户希望在系统中启用的Portage特性列表,影响Portage的行为。
# 由于这是一个增量变量,可以在不直接覆盖通过 Gentoo profile
# 实现的FEATURES值的情况下添加FEATURES值。
FEATURES=""

# gnome和kde及其相关组件
DESK_ENV="-gnome -gnome-shell -gnome-keyring -nautilus -kde icu"

# 不使用systemd plymouth consolekit 只使用elogind
# 旧教程会使用consolekit,elogind是consolekit未来的替代品
FUCKSV="-systemd -bindist -mdev elogind -oss -grub -plymouth -consolekit"

# 对于音频相关软件使用pulseaudio alsa jack特性
AUDIO="alsa jack pulseaudio"

SOFTWARE="sudo client git openmp minizip udev blkid efi hwdb smack \
acpi ccache dbus policykit udisks cjk emoji -test"

# 网络相关
NET="network networkmanager connection-sharing wifi http2 dhclient \
-dhcpcd policykit nftables"

# 图形相关
VIDEO="X vulkan layers glamor nvidia gallium"

# 定义需要的USE变量
USE="${DESK_ENV} ${FUCKSV} ${AUDIO} ${NET} ${VIDEO} ${SOFTWARE}"

# 二进制包保存路径
# PKGDIR="/var/cache/binpkgs"

# 使用二进制软件包,加入USE生效
# BIN_PKG="getbinpkg"

# emerge时用到的代理 需要代理时候自行设置
# http_proxy="http://127.0.0.1:8889"
# https_proxy="http://127.0.0.1:8889"

永久禁用nouveau驱动模块

强烈要求你禁用Nouveau驱动!!能省掉以后很多莫名其妙的麻烦!

1
2
3
4
5
6
7
mkdir /etc/modprobe.d/
nano -w /etc/modprobe.d/blacklist.conf

# 写入以下内容
blacklist nouveau
blacklist lbm-nouveau
options nouveau modeset=0

即便在编译内核前就已经设置内核禁用Nouveau驱动了,但是内核安装时还是会默认把nouveau驱动作为内核模块自动加载。启用了nouveau驱动模块的内核会出现各式各样的莫名其妙的数不清的问题,所以为了避免以后出现这些问题,必须禁用nouveau模块。

内核配置和编译

安装内核源码

1
2
3
4
5
emerge --ask sys-kernel/gentoo-sources

# 如果安装多个版本内核时执行
eselect kernel list # 查看内核列表
eselect kernel set 1 # 选择内核版本

某些驱动程序在工作之前需要在系统上安装其他固件。这通常是网络接口的情况,尤其是无线网络接口。此外,在使用开源驱动程序时,来自AMD,Nvidia和Intel等供应商的现代视频芯片通常需要外部固件文件。大多数固件都封装在sys-kernel / linux-firmware中:

1
emerge --ask --quiet sys-kernel/linux-firmware

除了独立显卡硬件和网络接口之外,CPU 可能也需要固件更新。通常这种固件被称为微码(microcode)。有时需要更新版本的微码来修补 CPU 硬件中的不稳定性、安全问题或其他复杂的错误。

AMD CPU 的微码更新在前面提到的 sys-kernel/linux-firmware 软件包内分发。Intel CPU 的微码可以在 sys-firmware/intel-microcode 包中找到,并且需要单独安装

1
2
# Intel CPU 执行
emerge --ask sys-firmware/intel-microcode

三种方法安装内核

安装系统时可选择安装二进制内核,系统安装完后再配置编译内核,参考 配置Linux内核 - Gentoo Wiki

  1. 全自动安装

    当为基于 amd64 的系统安装和编译内核时,Gentoo 推荐使用 sys-kernel/gentoo-sources 软件包

    1
    emerge --ask sys-kernel/installkernel
  2. 混合安装(推荐方式)

    生成内核配置文件

    将genkernel的默认内核配置文件“generated-config”复制过来,里面已经设置好了绝大部分应用场景以及绝大部分硬件驱动的配置,非常方便,值得借过来使用,只需要在自己手动配置内核的时候将其加载,在其基础上做一点点轻微的修改或完全不修改都可以,对内核新手极其友好!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    emerge --ask sys-kernel/genkernel

    # 以genkernel的配置文件为基础进行自定义配置
    cp /usr/share/genkernel/arch/x86_64/generated-config /usr/src/linux/

    # 备份
    cp /usr/src/linux/generated-config /usr/src/linux/generated-config.bak

    # 编译
    genkernel --mountboot --install all

    ^注意如果想在以后支持jack低延迟实时音频组件(Jack-Audio-Connection-Kit),则需要vim generated-config,手动设置“CONFIG_CGROUPS=y”、“CONFIG_CGROUP_SCHED=y”、“CONFIG_RT_GROUP_SCHED=y”,然后重新make menuconfig载入保存generated-config一遍,接下来再编译内核。
    ^注意: 使用nvidia显卡闭源驱动,需要将内核配置中“CONFIG_I2C_NVIDIA_GPU”这一项禁用,否则会和官方nvidia-drivers冲突!!!

  3. 全手动安装

    1
    2
    3
    4
    5
    emerge sys-apps/pciutils

    cd /usr/src/linux
    # 配置内核
    make menuconfig

![[Gentoo安装/IMG-20241210170128644.png]]
有些内核选项是必须的,必须编译到内核中,而不是作为模块加载。*表示包括到内核中,M表示作为模块加载,[]只有包括到内核中和排除在外两种选项,<>则有包括到内核中、排除在外和以模块加载三种选项。下面这些选项都必须以*方式编译到内核中。

devtmpfs支持。

1
2
3
4
Device Drivers --->
Generic Driver Options --->
[*] Maintain a devtmpfs filesystem to mount at /dev
[*] Automount devtmpfs at /dev, after the kernel mounted the rootfs

SCSI磁盘支持。

1
2
3
Device Drivers --->
SCSI device support --->
<*> SCSI disk support

选择支持的文件系统。因为ESP分区用的FAT32格式化的,根目录用的XFS格式化的,所以这里这两项(FAT32也就是VFAT)必须包括到内核中,虚拟内存和proc文件系统也是必选的。其实这里还可以取消掉不需要的文件系统,但是对于新手不建议取消任何自己不明白的东西,很容易弄的最后内核没办法启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File systems --->
< > Second extended fs support
< > The Extended 3 (ext3) filesystem
<*> The Extended 4 (ext4) filesystem
< > Reiserfs support
< > JFS filesystem support
< > XFS filesystem support
< > Btrfs filesystem support
DOS/FAT/NT Filesystems --->
<*> MSDOS fs support
<*> VFAT (Windows-95) fs support

Pseudo Filesystems --->
[*] /proc file system support
[*] Tmpfs virtual memory file system support (former shm fs)

如果处理器是多核的,还需要开启SMP(对称多处理器支持)。

1
2
Processor type and features  --->
[*] Symmetric multi-processing support

USB也必须启用

1
2
3
4
5
6
7
8
9
10
11
Device Drivers --->
HID support --->
-*- HID bus support
<*> Generic HID driver
[*] Battery level reporting for HID devices
USB HID support --->
<*> USB HID transport layer
[*] USB support --->
<*> xHCI HCD (USB 3.0) support
<*> EHCI HCD (USB 2.0) support
<*> OHCI HCD (USB 1.1) support

系统体系相关的内核配置

因为选择了multlib,所以32和64位的程序都会安装。为了支持32位程序,必须启用32位程序模拟功能。这里其实倒是不用怎么改,默认已经都选上了。

1
2
3
4
5
6
7
8
9
10
11
12
Processor type and features  --->
[*] Machine Check / overheating reporting
[*] Intel MCE Features
[*] AMD MCE Features
Processor family (AMD-Opteron/Athlon64) --->
( ) Opteron/Athlon64/Hammer/K8
( ) Intel P4 / older Netburst based Xeon
( ) Core 2/newer Xeon
( ) Intel Atom
(*) Generic-x86-64
Binary Emulations --->
[*] IA32 Emulation

启用GPT支持,因为前面我用的GPT分区表,EFI启动方式,所以这两项也必须启用。

1
2
3
4
-*- Enable the block layer --->
Partition Types --->
[*] Advanced partition selection
[*] EFI GUID Partition support

EFI的支持。

1
2
3
4
5
6
7
8
Processor type and features  --->
[*] EFI runtime service support
[*] EFI stub support
[*] EFI mixed-mode support

Firmware Drivers --->
EFI (Extensible Firmware Interface) Support --->
<*> EFI Variable Support via sysfs
1
2
3
4
# 编译内核
make -j12 #(CPU核心数根据机器cpu调整)
make modules_install
make install

使用二进制内核

1
2
# 安装二进制内核
emerge --ask sys-kernel/gentoo-kernel-bin

可选:生成一个initramfs

在某些情况中需要建立一个initramfs——一个基于内存的初始化文件系统。最觉的原因是当重要的文件系统位置(如/usr/或/var/)在分离的分区。通过一个initramfs,这些分区可以使用initramfs里面的工具来完成挂载。

用dracut生成内核的initramfs,快速且方便,新手友好

1
2
3
4
5
emerge --ask sys-kernel/dracut

cd /boot

dracut --hostonly

或者使用genkernel生成内核的initramfs

1
2
cp /usr/src/linux/generated-config /etc/kernels/kernel-config-<内核版本号>-gentoo-x86_64
genkernel --install initramfs

系统环境配置

配置主机名

1
2
3
4
#nano -w /etc/conf.d/hostname

echo "HOSTNAME" > /etc/hostname

配置系统时区

1
2
3
4
5
6
7
8
ls /usr/share/zoneinfo
echo "Asia/Shanghai" > /etc/timezone

# 解决时间差8小时问题(双系统时会遇到Windows时间不对)
sudo rm /etc/localtime
sudo ln -sv /usr/share/zoneinfo/Universal /etc/localtime

sudo emerge --config sys-libs/timezone-data

配置编码

1
2
3
4
5
6
7
8
nano -w /etc/locale.gen 		#将以下几项取消注释,如果没有手动输入

en_US ISO-8859-1
en_US.UTF-8 UTF-8
zh_CN GBK
zh_CN.UTF-8 UTF-8

locale-gen # 更新

设置系统locale

1
2
3
4
5
6
7
8
#查看可用系统时区和地区配置
eselect locale list

#这里只能选择“en-US.utf8”!!假如设置成了中文后,整个系统的终端命令行会乱码!!!
eselect locale set X

# 更新环境
env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

配置sudo自动补全

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo emerge --ask app-shells/bash-completion

# 添加 bash-completion 全局 USE 标记
sudo vim /etc/protage/make.conf
USE="... bash-completion"
sudo emerge --avuDN world

# 启用bash-completion的功能
sudo eselect bashcomp enable base
# sudo bashcomp-config enable base

# 查看哪些命令支持bash-completion
sudo eselect bashcomp list

安装网络工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 无线
emerge --ask net-wireless/iw
emerge --ask net-wireless/wpa_supplicant

# 有线
emerge --ask net-misc/netifrc # openrc 自带
# emerge --ask net-misc/systemd-networkd

# PPPoE环境
emerge --ask net-dialup/ppp

# 如果使用GUI可跳过
# 要在引导时激活网络接口,需要将它们添加到默认运行级别
# 首先使用 ifconfig 查看网络接口名称
cd /etc/init.d/
ln -s net.lo net.eno1 # 此处网卡名称需要和实际网卡名对应
rc-update add net.eno1 default

![[Gentoo安装/IMG-20241210170128726.png]]
安装配置networkmanager

最方便支持多种联网方式的工具是NetworkManager,基本满足所有需求,但同时它的依赖有点多。如果使用桌面环境的话建议安装。

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
nano -w /etc/portage/make.conf:
USE=“networkmanager connection-sharing dhclient policykit ppp wifi -dhcpcd”

emerge net-misc/networkmanager

nano -w /etc/dhcp/dhclient.conf:
send host-name "Gentoo" #your hostname

nano -w /etc/NetworkManager/NetworkManager.conf
[connectivity]
uri=http://nmcheck.gnome.org/check_network_status.txt


nano -w /etc/NetworkManager/NetworkManager.conf
[main]
plugins=keyfile
dns=dnsmasq
hostname-mode=none

rc-update del dhcpcd
rc-update add NetworkManager default
# systemctl disable dhcpcd
# systemctl enable networkmanager

#gpasswd -a <你的桌面使用用户名> plugdev #没有这一步,用户将不能使用networkmanager,也就不能上网。不过先跳过这一步,在设置系统用户的时候再做。

nano -w /etc/dnsmasq.conf:
server=114.114.114.114

安装必要的工具

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
# 系统日志
emerge --ask app-admin/sysklogd
rc-update add sysklogd default
# systemctl enable sysklogd
# systemctl start sysklogd

# 计划任务管理
emerge --ask sys-process/cronie
rc-update add cronie default
# systemctl enable cronie
# systemctl start cronie

# 文件索引
emerge --ask sys-apps/mlocate

# 电源管理
emerge --ask sys-power/acpid
rc-update add acpid default
# systemctl enable acpid
# systemctl start acpid

# CPU温度管理
emerge sys-power/thermald
rc-update add thermald default
# systemctl enable thermald
# systemctl start thermald

# 设备管理工具
emerge --ask virtual/udev
rc-update add udev sysinit
# systemctl enable udev
# systemctl start udev

配置系统用户

安装sudo

1
2
3
emerge app-admin/sudo

nano -w /etc/sudoers

%wheel ALL=(ALL) ALL 这一行去掉注释,如果希望执行sudu不需要密码则取消注释%wheel ALL=(ALL) NOPASSWD:ALL

添加用户

1
2
3
groupadd sudo 
useradd -m -G users,wheel,usb,portage,video,audio,sudo -s /bin/bash [用户名]
chmod 700 /home/[用户名] -R

设置密码

1
2
3
4
# 设置root密码
passwd root

passwd {用户名}

添加操作系统启动项

/etc/portage/make.conf中添加grub配置

1
GRUB_PLATFORMS="efi-64"

安装grub2

1
2
emerge --ask sys-boot/grub:2
emerge --ask sys-boot/os-prober # 用于识别其他分区的系统(双系统)

grub安装到硬盘并生成开机启动项

1
2
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Gentoo
grub-mkconfig -o /boot/grub/grub.cfg
1
mount -o remount,rw /sys/firmware/efi/efivarsos-prober

如果出现No space left on device,请运行以下命令,之后再重复上述步骤

1
2
mount -t efivarfs efivarfs /sys/firmware/efi/efivars
rm /sys/firmware/efi/efivars/dump-*

grub默认配置添加自定义配置,可提高intel cpu的稳定性和性能

1
2
3
4
nano -w /etc/default/grub:
GRUB_CMDLINE_LINUX_DEFAULT="intel_idle.max_cstate=0 processor.max_cstate=1"

grub-mkconfig -o /boot/grub/grub.cfg

清理

1
2
3
4
5
6
7
8
9
10
11
12
rm /stage3-*.tag.ge
# 退出chroot
exit

# 卸载
umount -lR /mnt/gentoo

# 重启
reboot

# 成功开机并进入系统后
grub-mkconfig -o /boot/grub/grub.cfg

如果开机不正常参考挂载文件系统(不要执行分区和mkfs操作),可重新chroot进入系统修改错误的配置。

桌面环境

安装基础环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 从 x11-base/xorg-drivers-21.1 开始,x11-base/xorg-drivers更改 [USE 标志]设置
# 这将弃用x11-drivers/xf86-video-intel驱动程序,以支持内置的通用模式设置DDX驱动程序
# 具有video_cards_i915USE 标志集将继续安装 Intel DDX 驱动程序。
emerge --ask x11-base/xorg-drivers

# 英伟达显卡
emerge --ask x11-drivers/nvidia-drivers

# 安装xorg-server
emerge --ask x11-base/xorg-server

# 安装双显卡设置工具
emerge --ask x11-apps/xrandr

# 让nvidia自动设置双显卡prime配置
sudo rm /etc/X11/xorg.conf
sudo nvidia-xconfig --prime

# 安装完更新当前系统组件环境
env-update && source /etc/profile

^注意: 以后每次重新编译安装内核kernel后,均须要运行一遍“emerge @module-rebuild”,重新编译安装nvidia驱动模块加载到内核之中,否则nvidia驱动无法加载!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lsmod | grep nvidia
sudo rmmod nvidia
sudo modprobe nvidia

lsmod|grep nvidia

sudo vim /etc/modules-load.d/nvidia.conf:
nvidia

sudo vim /etc/modprobe.d/nvidia-drm.conf:
options nvidia-drm modeset=1

sudo rc-update add modules boot
sudo reboot

LightDM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装LightDM,使用KDE可忽略
sudo emerge --ask gui-libs/display-manager-init
sudo emerge --ask x11-misc/lightdm

# 如果不安装桌面管理器需要加入环境,登录后自动启动桌面
echo "XSESSION=\"awesome\"" > /etc/env.d/90xsession
env-update && source /etc/profile

# openrc
nano -w /etc/conf.d/display-manager
DISPLAYMANAGER="lightdm"

# 设置默认开机启动
rc-update add display-manager default
# 设置dubs默认开机启动,虽然display-manager也会启动它,但有时候会出现奇怪的问题
rc-update add dbus default

# 手动启动
rc-service dbus start
rc-service display-manager start

# systemd
# systemctl enable lightdm.service

KDE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 安装KDE桌面可忽略上边LightDM,同时需要删除USE中`-kde`
sudo emerge --ask x11-misc/sddm
sudo emerge --ask kde-plasma/plasma-meta

# 安装Dock
# 安装完打开latte-dock后会自动设置为开机自启动
sudo emerge --ask kde-misc/latte-dock

# 修改登陆管理器配置文件
nano -w /etc/conf.d/display-manager

# --- xdm内容
DISPLAYMANAGER="sddm"

#---

# 添加SDDM开机启动
sudo rc-update add xdm default
# 启动SDDM
sudo rc-service xdm start

# Systemd
# sudo systemctl enable xdm
# sudo systemctl start xdm

Awesome

1
2
3
4
5
6
7
8
9
10
11
12
13
# awesome 平铺式桌面
emerge --ask x11-wm/awesome

# 测试
mkdir -p ~/.config/awesome/
cp /etc/xdg/awesome/rc.lua ~/.config/awesome/rc.lua
awesome -k

# 壁纸支持
emerge --ask media-gfx/feh

# 在~/.config/awesome/theme/theme中添加一下内容
theme.wallpaper_cmd = { "wesetbg -f .config/awesome/themes/awesome-wallpaper.png" }

Mate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 选择profile
eselect profile list
eselect profile set 0 # 选择default/linux/amd64/23.0/desktop
# 更新
emerge -auvDU @world

# Meta 桌面
emerge --ask mate-base/mate

# 修改xsession配置
nano -w /etc/env.d/90xsession
#--- 90xsession内容
XSESSION="Mate"

#---

# 修改LightDM配置(需要安装LightDM)
nano -w /etc/conf.d/display-manager
#--- display-manager内容
DISPLAYMANAGER="lightdm"

Xfce

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
40
41
42
43
44
45
46
47
48

# xfce4桌面
emerge --ask xfce-base/xfce4-meta

# 测试桌面启动指令
startxfce4 # 启动Xfce桌面

# Pulseaudio音量控制
emerge --ask xfce-extra/xfce4-volumed-pulse

# 蓝牙音乐播放组件
emerge --ask xfce-extra/xfce4-pulseaudio-plugin

# 显示所有正在运行的程序的列表,以及每个程序占用的CPU和内存消耗。
emerge --ask xfce-extra/xfce4-taskmanager

# 监视和管理电源使用情况的应用程序。 这对笔记本电脑特别重要!
# 电源管理器允许用户调节屏幕亮度,选择最大性能或节电模式,
# 并在盖子关闭或按下按钮时设置休眠,暂停和关闭操作
emerge --ask xfce-extra/xfce4-power-manager

# 适合笔记本电脑用户。 它显示电池百分比,剩余时间,电源(交流或电池),风扇状态,警告,
# 甚至可以配置为在特定功率级别执行命令。 此功能可用于在电池电量几乎耗尽时将笔记本电脑置于休眠模式。
xfce-extra/xfce4-battery-plugin

# 添加几个窗口管理器主题
emerge --ask x11-themes/xfwm4-themes

# 一个X11终端,比准系统更可配置和有用 xterm
emerge --ask x11-terms/xfce4-terminal

# Xfce的默认图形文件管理器。
emerge --ask xfce-base/thunar

# 允许用户从Thunar内预览某些类型的文件,例如图像和字体。
emerge --ask xfce-extra/tumbler

# manages自动挂载可移动介质和驱动器。
emerge --ask xfce-extra/thunar-volman

# 嵌入面板的一个小命令行。 它比打开终端运行命令更快。
emerge --ask xfce-extra/xfce4-verve-plugin

# 提供一种方便的方法,只需点击鼠标即可安装/etc/fstab中列出的设备。
emerge --ask xfce-extra/xfce4-mount-plugin

# 允许用户监视硬件传感器,例如CPU温度,风扇RPM,硬盘驱动器温度,主板电压等。
emerge --ask xfce-extra/xfce4-sensors-plugin

音频控制

1
2
emerge --ask alsa-utils
emerge --ask alsa-plugins

中文字体

1
2
3
4
5
6
7
8
emerge --ask media-fonts/arphicfonts
emerge --ask media-fonts/noto-cjk
emerge --ask media-fonts/source-han-sans
emerge --ask media-fonts/wqy-microhei
emerge --ask media-fonts/wqy-zenhei

eselect fontconfig list
eselect fontconfig enable X X X # 选择所有wqy开头的项

输入法

1
2
3
4
5
6
7
8
9
10
11
12
# 输入法主题框架
# 其中, app-i18n/fcitx:5 是 fcitx 的主程序
#     app-i18n/fcitx-configtool:5 是它的配置工具
#     app-i18n/fcitx-qt:5 用于支持在 qt 程序上使用它
#     app-i18n/fcitx-gtk:5 用于支持在 gtk 程序上使用它
emerge -vj app-i18n/fcitx:5 app-i18n/fcitx-configtool:5 app-i18n/fcitx-qt:5 app-i18n/fcitx-gtk:5

# 安装完成后再用户的~/.xsession文件内添加
export XMODIFIERS="@im=fcitx"
export QT_IM_MODULE=fcitx
export GTK_IM_MODULE=fcitx
export SDL_IM_MODULE=fcitx

常用命令

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
# 使用常规(基于源)更新
# --ask(-a)控制Portage显示要更新的软件列表,并提供是否更新选择
# --verbose(-v)在屏幕上输出完整的文件列表
# --update(-u)更新包的最佳版本
# --deep(-D)更新系统中的每个软件包
# --newuse(-N)USE标记变更后,要使用Portage检查USE标记的变动是否导致需要安装新的软件或将现有的软件包重新编译
sudo emerge --ask --verbose --update --deep --newuse @world

# 等价简写
emerge -avuDN @world

emerge -av --deepclean

#gentookit包里的一个软件,用来检查系统的依赖是否都满足,自动安装缺失的依赖
revdep-rebuild

# 使用二进制包更新系统
emerge --ask --verbose --update --deep --changed-use --getbinpkg @world

# 告诉 Portage 不要对一些指定的包或分类创建二进制包
emerge -uDN @world --buildpkg --buildpkg-exclude "virtual/* sys-kernel/*-sources"

# 合并use标记
etc-update
-3 # 自动合并

# 刷新环境变量
source /etc/profile

# 清理旧版本的内核
emerge --prune sys-kernel/gentoo-kernel sys-kernel/gentoo-kernel-bin

emerge 使用二进制包选项说明

选项 说明
–usepkg (-k) 尝试使用本地可用的 packages 目录中的二进制包。如果未找到二进制包,将执行常规(基于源)安装。
–usepkgonly (-K) 类似 –usepkg (-k) ,但如果找不到二进制包,则失败。
–getbinpkg (-g) 从远程二进制包主机下载二进制包。如果未找到二进制包,将执行常规(基于源)安装。
–getbinpkgonly (-G) 类似于 –getbinpkg (-g) ,但如果无法下载二进制包,则会失败

USE变量说明 官方文档

USE是Gentoo为用户提供的最具威力的变量之一。很多程序通过它可以选择编译或者不编译某些可选的支持。例如,一些程 序可以在编译时加入对gtk或是对qt的支持。其它的程序可以在编译时加入或不加入对于SLL的支持。有些程序甚至可以在编译时加入对 framebuffer的支持(svgalib)以取代X11(X服务器)。
大多数的发行版会使用尽可能多的支持特性编译它们的软件包,这既增加了软件的大小也减慢了启动时间,而这些还没有算上可能会涉及到的大量依赖性问题。Gentoo可以让你自己定义软件编译的选项,而这正是USE要做的事。
在USE变量里可以定义关键字,它被用来对应相应的编译选项。例如,ssl将会把ssl支持编译到程序中以支持它。-X会移除其对于X服务器的支持(注意前面的减号)。gnome gtk -kde -qt4将会以支持GNOME(和GTK)但不支持KDE(和Qt)的方式编译软件,使系统为GNOME做完全调整(如果架构支持)。
默认的USE设置全放在了系统所使用的Gentoo配置文件的make.defaults文件中。Gentoo对它的配置文件们使用了一个(复杂的)继承系统,在这个阶段我们不去深入。最简单的检查当前活动的USE标记的办法是运行emerge –info并选择以USE开头的那一行:

1
emerge --info |grep ^USE

![[Gentoo安装/IMG-20241210170128821.png]]
可以在系统的/usr/portage/profiles/use.desc中找到可用的USE标记的完整描述。

1
less /usr/share/portage/profile/use.desc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
### 常用软件

```bash
sudo emerge --ask media-video/mpv

# 电子邮件客户端
sudo emerge mail-client/thunderbird

# 视频播放器
sudo emerge media-video/mplayer

# 音乐播放器
sudo emerge media-sound/exaile

# 虚拟机
sudo emerge app-emulation/virt-manager
sudo emerge app-emulation/virtualbox

# VIM
sudo emerge app-editors/vim

参考文章

Gentoo AMD64 Handbook - Gentoo Wiki

开始使用gentoo linux——gentoo安装笔记(上)

开始使用gentoo linux——gentoo安装笔记(下)

Gentoo安装流程分享(step by step),第二篇之KDE Plasma桌面的安装配置 - 知乎 (zhihu.com)

gentoo linux配置intel和nvidia双显卡电脑,使用prime方案 - 简书 (jianshu.com)

OpenIddict是一个ASP.NET Core身份验证库,可帮助您添加OpenID Connect和OAuth 2.0支持到ASP.NET Core应用程序中。下面是OpenIddict使用教程的步骤:

  1. 安装OpenIddict,在项目中添加OpenIddict.Core和OpenIddict.EntityFrameworkCore Nuget包。
  2. 配置OpenIddict,在Startup.cs文件中添加OpenIddict服务的配置。您可以选择使用内存或EFCore进行配置。以下是使用EF Core进行配置的示例:
    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
    services.AddDbContext<ApplicationDbContext>(options =>
    {
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    options.UseOpenIddict();
    });

    services.AddCustomOpenIddictApplication();
    services.AddCustomOpenIddictAuthorization();
    services.AddCustomOpenIddictScope();
    services.AddCustomOpenIddictToken();
    services.AddCustomOpenIddictValidation();
    services.AddCustomOpenIddictUser();

    services.AddOpenIddict()
    .AddCore(options =>
    {
    options.UseEntityFrameworkCore()
    .UseDbContext<ApplicationDbContext>()
    .ReplaceDefaultEntities<ApplicationDbContext>();
    })
    .AddServer(options =>
    {
    options.UseMvc();
    options.EnableAuthorizationEndpoint("/connect/authorize")
    .EnableLogoutEndpoint("/connect/logout")
    .EnableTokenEndpoint("/connect/token")
    .EnableUserinfoEndpoint("/connect/userinfo");
    options.RegisterScopes("openid", "profile", "email", "offline_access");

    options.AllowImplicitFlow();
    options.DisableHttpsRequirement();

    options.AddSigningCertificate(File.ReadAllBytes(Configuration["Auth:Certificates:Path"]),
    Configuration["Auth:Certificates:Password"]);

    options.DisableAccessTokenEncryption();
    options.SetAccessTokenLifetime(TimeSpan.FromHours(6));
    });
  3. 添加授权策略,在Startup.cs文件添加需要的授权策略。以下是一个例子:
    1
    2
    3
    4
    5
    services.AddAuthorization(options =>
    {
    options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    options.AddPolicy("AdministratorOnly", policy => policy.RequireRole("Administrator"));
    });
  4. 在您的应用程序中使用OpenIddict,您可以使用OpenIddict来实现您的OAuth 2.0或OpenID Connect需求。以下是一些常见的用例:

4.1 登录页面

使用OpenIddict进行身份验证,您可以使用如下代码在您的控制器中。您可以使用请求重定向到触发OpenID Connect流:

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
40
41
42
43
44
45
[HttpGet("~/login")]
public IActionResult Login()
{
var request = HttpContext.GetOpenIddictServerRequest();

return View(new LoginViewModel
{
Nonce = RandomNumberGenerator.GetInt32(),
ReturnUrl = request.RedirectUri,
Ticket = request.GetOpenIddictServerTransactionId(),
});
}

[HttpPost("~/login")]
public IActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
ModelState.AddModelError("Username", "Username or password is incorrect.");
}
else if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError("Email", "You must have a confirmed email to log in.");
}
else if (!await _userManager.CheckPasswordAsync(user, model.Password))
{
ModelState.AddModelError("Username", "Username or password is incorrect.");
}
else
{
// 创建一个新的身份验证票据.
var ticket = await CreateTicketAsync(user);

return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
}

ViewData["returnUrl"] = model.ReturnUrl;
ViewData["nonce"] = model.Nonce;
ViewData["transactionId"] = model.Ticket;
return View(model);
}

4.2 注册页面

您还可以使用OpenIddict来实现您的注册页面。以下是一个例子:

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
40
41
42
43
44
45
[HttpGet("~/register")]
public IActionResult Register()
{
return View();
}

[HttpPost("~/register")]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
};

var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var code = await _userManager.GenerateEmailConfirmationAsync(user);
var callbackUrl= Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);

await _emailSender.SendEmailAsync(model.Email, "Confirm your email",
$"Please confirm your account by clicking this link: {callbackUrl}");

return RedirectToAction(nameof(RegisterConfirmation));
}

foreach (var error in result.Errors)
{
ModelState.AddModelError("Email", error.Description);
}
}

return View(model);
}

[HttpGet("~/register/confirmation")]
public IActionResult RegisterConfirmation()
{
return View();
}

4.3 访问受保护的资源

最后,您可以使用OpenIddict来实现访问受保护资源的身份验证和授权。以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[HttpGet("~/manager")]
[Authorize(Roles = "Manager")]
public IActionResult ManagerDashboard()
{
return View();
}

[HttpGet("~/employee")]
[Authorize(Policy = "EmployeeOnly")]
public IActionResult EmployeeDashboard()
{
return View();
}

[HttpGet("~/administrator")]
[Authorize(Policy = "AdministratorOnly")]
public IActionResult AdministratorDashboard()
{
return View();
}
  1. 通过OpenIddict实现Token刷新

当访问受保护的API时,您可以使用OpenIddict来实现使用token刷新。以下是实现Token刷新的一个示例方法:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[HttpPost("~/api/token/refresh")]
public async Task<IActionResult> Refresh([FromForm]string refreshToken)
{
var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme);

if (info == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
}

var principal = info.Principal;

var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
}

// 确保刷新令牌没有被撤销.
if (!await _tokenManager.ValidateAsync(
principal.GetId(),
principal.GetClaim(OpenIddictConstants.Claims.JwtId)))
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
}

// 从数据库得到客户端应用程序详细信息
var application = await _applicationManager.FindByClientIdAsync(
principal.GetClaim(OpenIddictConstants.Claims.ClientId));
if (application == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The client application associated with this token is no longer valid."
});
}

var identity = await _userManager.CreateIdentityAsync(user, principal.GetScopes());

var ticket = await CreateTicketAsync(application, identity, principal);

return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
  1. 通过OpenIddict实现密码恢复流程OpenIddict还可以实现忘记密码流程的重置密码,以下是一个简单的示例:
    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    [HttpPost("~/forgot-password")]
    [AllowAnonymous]
    public async Task<IActionResult> ForgotPassword([FromForm] string email)
    {
    var user = await _userManager.FindByEmailAsync(email);
    if (user == null)
    {
    // 不要显示用户不存在,懂的都懂~
    return Ok();
    }

    var code = await _userManager.GeneratePasswordResetTokenAsync(user);
    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

    var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Reque

    await _emailSender.SendEmailAsync(
    email,
    "Password Reset",
    $"Please reset your password by clicking here: <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>link</a>.");

    return Ok();
    }

    [HttpGet("~/reset-password")]
    [AllowAnonymous]
    public IActionResult ResetPassword(string code = null, string userId = null)
    {
    return View(new ResetPasswordViewModel { Code = code, UserId = userId });
    }

    [HttpPost("~/reset-password")]
    [AllowAnonymous]
    public async Task<IActionResult> ResetPassword([FromForm] ResetPasswordViewModel model)
    {
    if (!ModelState.IsValid)
    {
    return View(model);
    }

    var user = await _userManager.FindByIdAsync(model.UserId);
    if (user == null)
    {
    // 不要显示用户不存在
    return View("ResetPasswordConfirmation");
    }

    var decodedCode = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Code));
    var result = await _userManager.ResetPasswordAsync(user, decodedCode, model.Password);
    if (result.Succeeded)
    {
    return RedirectToAction(nameof(ResetPasswordConfirmation));
    }

    foreach (var error in result.Errors)
    {
    ModelState.AddModelError(string.Empty, error.Description);
    }

    return View(model);
    }

    [HttpGet("~/reset-password-confirmation")]
    [AllowAnonymous]
    public IActionResult ResetPasswordConfirmation()
    {
    return View();
    }
  2. 使用OpenIddict实现自定义Token发布方案

OpenIddict支持自定义Token发布方案,以适应各种需求。在以下示例中,我们将实现自定义发布方案来控制Token的过期时间:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class CustomTokenEndpointHandler : OpenIddictServerHandler<OpenIddictServerOptions>
{
public CustomTokenEndpointHandler(IServiceProvider services)
: base(services)
{
}

public override async Task HandleAsync([NotNull] OpenIddictServerHandleContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

// 从数据库检索客户机应用程序.
var application = await context.HttpContext.GetOpenIddictServerApplicationAsync();
if (application == null)
{
throw new InvalidOperationException("The client application cannot be retrieved.");
}

// 从授权服务器设置检索用户主体.
var principal = context.HttpContext.User;

// 确保允许应用程序使用指定的授权类型。
if (!await ValidateClientRedirectUriAsync(application, context.Request))
{
throw new InvalidOperationException("The grant type is not allowed for this application.");
}

//注意:这个自定义令牌终端点总是忽略“scopes”参数,并根据授予的scopes/roles自动定义声明。
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);

// 根据请求的自定义授权类型自定义令牌生命周期.
if (string.Equals(context.Request.GrantType, "urn:custom_grant", StringComparison.OrdinalIgnoreCase))
{
// Set the token expiration to 1 hour.
ticket.Properties.ExpiresUtc = context.Options.SystemClock.UtcNow.AddHours(1);
}
else
{
// 将令牌过期时间设置为默认持续时间(5分钟)
ticket.Properties.ExpiresUtc = context.Options.SystemClock.UtcNow.Add(
context.Options.AccessTokenLifetime ?? TimeSpan.FromMinutes(5));
}

context.Logger.LogInformation("The custom token request was successfully processed.");

await context.HttpContext.SignInAsync(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);

// 将响应标记为已处理,以跳过管道的其余部分.
context.HandleRequest();
}
}

您需要将其添加到OpenIddict配置中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services.AddOpenIddict()
.AddCore(options =>
{
// ...
})
.AddServer(options =>{
// ...

options.Handlers.Add(new CustomTokenEndpointHandler(services));

// ...
})
.AddValidation(options =>
{
// ...
});

此时,您可以使用 urn:custom_grant 授权类型来发出过期时间为1小时的Token,这可以通过以下方式完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5000/connect/token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "urn:custom_grant",
["client_id"] = "your_client_id",
["client_secret"] = "your_client_secret",
["scope"] = "your_scopes_separated_by_spaces"
});

var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();

总结

本文介绍了如何使用OpenIddict创建一个基本的身份验证和授权服务器。当然,在实现身份验证和授权服务器时有很多细节需要考虑,例如维护安全性、处理错误、管理用户和客户端应用程序等。希望这篇文章对您有所帮助!

Neovim 配置美化完整流程


环境说明

项目 说明
操作系统 Ubuntu 20.04 / macOS
终端 Windows Terminal + WSL2 / iTerm2
Neovim版本 v0.7+
配置需求 流畅的 GitHub 连接(用于拉取插件)

一、配置文件整体结构

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
40
41
42
43
44
.
├── init.lua # 配置入口文件
└── lua
├── autocmds.lua # 自动命令
├── basic.lua # 基础配置
├── colorscheme.lua # 主题配置
├── keybindings.lua # 快捷键设置
├── lsp
│ ├── cmp.lua # 代码补全配置
│ ├── config # 各语言服务器配置
│ │ ├── bash.lua
│ │ ├── css.lua
│ │ ├── html.lua
│ │ ├── json.lua
│ │ ├── lua.lua
│ │ ├── markdown.lua
│ │ ├── pyright.lua
│ │ ├── rust.lua
│ │ └── ts.lua
│ ├── formatter.lua # 代码格式化
│ ├── null-ls.lua # 格式化/诊断
│ ├── setup.lua # LSP 初始化
│ └── ui.lua # UI 美化
├── plugin-config
│ ├── bufferline.lua # 顶部标签页
│ ├── comment.lua # 注释插件
│ ├── dashboard.lua # 启动页面
│ ├── gitsigns.lua # Git 增强
│ ├── indent-blankline.lua # 缩进线
│ ├── lualine.lua # 底部状态栏
│ ├── nvim-autopairs.lua # 自动括号
│ ├── nvim-tree.lua # 文件浏览器
│ ├── nvim-treesitter.lua # 语法高亮
│ ├── project.lua # 项目管理
│ ├── surround.lua # 成对编辑
│ ├── telescope.lua # 模糊搜索
│ ├── toggleterm.lua # 终端
│ ├── vimspector.lua # 调试
│ └── which-key.lua # 快捷键提示
├── plugins.lua # 插件管理
└── utils
├── fix-yank.lua
├── global.lua
└── im-select.lua

文件说明

文件 说明
init.lua 整个配置的入口文件,负责引用所有其他模块
basic.lua 基础配置,对默认配置的重置
colorscheme.lua 主题皮肤配置
keybindings.lua 快捷键设置,所有插件的快捷键
plugins.lua 插件安装管理
lsp/ 内置 LSP 功能配置,包括编程语言与语法提示
plugin-config/ 第三方插件的独立配置文件
utils/ 常见问题的修改,包括输入法切换等

二、安装 Neovim

2.1 卸载旧版本(可选)

1
sudo apt-get remove neovim

2.2 Ubuntu 安装

1
2
3
4
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt-get update
sudo apt-get install neovim

2.3 macOS 安装

1
brew install neovim

2.4 验证版本

1
2
nvim --version
# 确保版本为 0.7 及以上

2.5 配置别名

1
2
3
4
5
6
# ~/.bashrc
alias vim='nvim'
alias vi='nvim'
alias v='nvim'

source ~/.bashrc

三、配置 Nerd Fonts

3.1 下载字体

官网:Nerd Fonts

下载 FiraCodeNerdFont-Regular.ttf,双击安装即可。

3.2 配置终端字体

在终端设置中选择安装的 Nerd Font 字体。


四、配置入口 init.lua

~/.config/nvim/init.lua

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
-- 基础设置
require('basic')

-- 快捷键映射
require("keybindings")

-- Packer 插件管理
require("plugins")

-- 主题设置
require("colorscheme")

-- 插件配置
require("plugin-config.nvim-tree")
require("plugin-config.bufferline")
require("plugin-config.lualine")
require("plugin-config.dashboard")
require("plugin-config.project")
require("plugin-config.nvim-treesitter")

-- 内置 LSP
require("lsp.setup")
require("lsp.cmp")
require("lsp.ui")
require("lsp.null-ls")

五、基础配置 basic.lua

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
-- utf8
vim.g.encoding = "UTF-8"
vim.o.fileencoding = 'utf-8'

-- jkhl 移动时光标周围保留8行
vim.o.scrolloff = 8
vim.o.sidescrolloff = 8

-- 使用相对行号
vim.wo.number = true
vim.wo.relativenumber = true

-- 高亮所在行
vim.wo.cursorline = true

-- 显示左侧图标指示列
vim.wo.signcolumn = "yes"

-- 右侧参考线
vim.wo.colorcolumn = "80"

-- 缩进2个空格等于一个Tab
vim.o.tabstop = 2
vim.bo.tabstop = 2
vim.o.softtabstop = 2
vim.o.shiftround = true
vim.o.shiftwidth = 2
vim.bo.shiftwidth = 2

-- 空格替代tab
vim.o.expandtab = true
vim.bo.expandtab = true

-- 新行对齐当前行
vim.o.autoindent = true
vim.bo.autoindent = true
vim.o.smartindent = true

-- 搜索大小写
vim.o.ignorecase = true
vim.o.smartcase = true
vim.o.hlsearch = false
vim.o.incsearch = true

-- 命令行高度
vim.o.cmdheight = 2

-- 文件修改自动加载
vim.o.autoread = true
vim.bo.autoread = true

-- 禁止折行
vim.wo.wrap = false

-- 光标移动
vim.o.whichwrap = '<,>,[,]'

-- 允许隐藏buffer
vim.o.hidden = true

-- 鼠标支持
vim.o.mouse = "a"

-- 禁止备份文件
vim.o.backup = false
vim.o.writebackup = false
vim.o.swapfile = false

-- 更新间隔
vim.o.updatetime = 300
vim.o.timeoutlen = 500

-- 分屏位置
vim.o.splitbelow = true
vim.o.splitright = true

-- 自动补全
vim.g.completeopt = "menu,menuone,noselect,noinsert"

-- 样式
vim.o.background = "dark"
vim.o.termguicolors = true
vim.opt.termguicolors = true

-- 不可见字符
vim.o.list = true
vim.o.listchars = "space:·"

-- 补全增强
vim.o.wildmenu = true
vim.o.shortmess = vim.o.shortmess .. 'c'
vim.o.pumheight = 10
vim.o.showtabline = 2
vim.o.showmode = false

-- 复制粘贴联通系统粘贴板
vim.o.clipboard = "unnamedplus"

配置项说明

配置 说明
vim.g.{name} 全局变量
vim.b.{name} 缓冲区变量
vim.w.{name} 窗口变量
vim.bo.{option} buffer-local 选项
vim.wo.{option} window-local 选项
vim.opt 通用选项设置

六、快捷键设置 keybindings.lua

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
vim.g.mapleader = " "
vim.g.maplocalleader = " "

local map = vim.api.nvim_set_keymap
local opt = { noremap = true, silent = true }

-- 取消 s 默认功能
map("n", "s", "", opt)

-- windows 分屏快捷键
map("n", "sv", ":vsp<CR>", opt)
map("n", "sh", ":sp<CR>", opt)
map("n", "sc", "<C-w>c", opt)
map("n", "so", "<C-w>o", opt)

-- Alt + hjkl 窗口之间跳转
map("n", "<A-h>", "<C-w>h", opt)
map("n", "<A-j>", "<C-w>j", opt)
map("n", "<A-k>", "<C-w>k", opt)
map("n", "<A-l>", "<C-w>l", opt)

-- 左右比例控制
map("n", "<C-Left>", ":vertical resize -2<CR>", opt)
map("n", "<C-Right>", ":vertical resize +2<CR>", opt)
map("n", "s,", ":vertical resize -20<CR>", opt)
map("n", "s.", ":vertical resize +20<CR>", opt)

-- 上下比例
map("n", "sj", ":resize +10<CR>", opt)
map("n", "sk", ":resize -10<CR>", opt)

-- Terminal相关
map("n", "<leader>t", ":sp | terminal<CR>", opt)
map("n", "<leader>vt", ":vsp | terminal<CR>", opt)
map("t", "<Esc>", "<C-\\><C-n>", opt)
map("t", "<A-h>", "<C-\\><C-N><C-w>h", opt)
map("t", "<A-j>", "<C-\\><C-N><C-w>j", opt)
map("t", "<A-k>", "<C-\\><C-N><C-w>k", opt)
map("t", "<A-l>", "<C-\\><C-N><C-w>l", opt)

-- visual模式下缩进
map("v", "<", "<gv", opt)
map("v", ">", ">gv", opt)

-- 上下移动选中文本
map("v", "J", ":move '>+1<CR>gv-gv", opt)
map("v", "K", ":move '<-2<CR>gv-gv", opt)

-- 上下滚动
map("n", "<C-j>", "4j", opt)
map("n", "<C-k>", "4k", opt)
map("n", "<C-u>", "9k", opt)
map("n", "<C-d>", "9j", opt)

-- 粘贴不复制
map("v", "p", '"_dP', opt)

-- 退出
map("n", "q", ":q<CR>", opt)
map("n", "qq", ":q!<CR>", opt)
map("n", "Q", ":qa!<CR>", opt)

-- insert 模式跳转
map("i", "<C-h>", "<ESC>I", opt)
map("i", "<C-l>", "<ESC>A", opt)

-- 代码注释插件
pluginKeys.comment = {
toggler = { line = "gcc", block = "gbc" },
opleader = { line = "gc", block = "gb" },
}

-- nvim-cmp 自动补全
pluginKeys.cmp = function(cmp)
return {
["<A-.>"] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }),
["<A-,>"] = cmp.mapping({ i = cmp.mapping.abort(), c = cmp.mapping.close() }),
["<C-k>"] = cmp.mapping.select_prev_item(),
["<C-j>"] = cmp.mapping.select_next_item(),
["<CR>"] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }),
["<C-u>"] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i", "c" }),
["<C-d>"] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i", "c" }),
}
end

-- LSP 快捷键
pluginKeys.mapLSP = function(mapbuf)
mapbuf("n", "<leader>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", opt)
mapbuf("n", "<leader>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>", opt)
mapbuf("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", opt)
mapbuf("n", "gh", "<cmd>lua vim.lsp.buf.hover()<CR>", opt)
mapbuf("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", opt)
mapbuf("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", opt)
mapbuf("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", opt)
mapbuf("n", "gp", "<cmd>lua vim.diagnostic.open_float()<CR>", opt)
mapbuf("n", "gk", "<cmd>lua vim.diagnostic.goto_prev()<CR>", opt)
mapbuf("n", "gj", "<cmd>lua vim.diagnostic.goto_next()<CR>", opt)
mapbuf("n", "<leader>f", "<cmd>lua vim.lsp.buf.format()<CR>", opt)
end

-- TypeScript 快捷键
pluginKeys.mapTsLSP = function(mapbuf)
mapbuf("n", "gs", ":TSLspOrganize<CR>", opt)
mapbuf("n", "gr", ":TSLspRenameFile<CR>", opt)
mapbuf("n", "gi", ":TSLspImportAll<CR>", opt)
end

-- nvim-tree 快捷键
pluginKeys.nvimTreeList = {
{ key = { "<CR>", "o", "<2-LeftMouse>" }, action = "edit" },
{ key = "v", action = "vsplit" },
{ key = "h", action = "split" },
{ key = "i", action = "toggle_custom" },
{ key = ".", action = "toggle_dotfiles" },
{ key = "<F5>", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "r", action = "rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "s", action = "system_open" },
}

七、插件配置

7.1 安装 Packer

1
git clone --depth 1 https://github.com/wbthomason/packer.nvim ~/.local/share/nvim/site/pack/packer/start/packer.nvim

7.2 plugins.lua

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
local packer = require("packer")
packer.startup(function(use)
-- Packer 可以管理自己本身
use 'wbthomason/packer.nvim'

-- -------------------- colorschemes --------------------
use("folke/tokyonight.nvim")

-- -------------------- 文件浏览器 --------------------
use({ "kyazdani42/nvim-tree.lua", requires = "kyazdani42/nvim-web-devicons" })

-- -------------------- 顶部标签页 --------------------
use({ "akinsho/bufferline.nvim", requires = { "kyazdani42/nvim-web-devicons", "moll/vim-bbye" } })

-- -------------------- 底部状态栏 --------------------
use({ "nvim-lualine/lualine.nvim", requires = "kyazdani42/nvim-web-devicons" })
use("arkav/lualine-lsp-progress")

-- -------------------- 模糊搜索 --------------------
use { 'nvim-telescope/telescope.nvim', requires = { "nvim-lua/plenary.nvim" } }
use("ahmedkhalf/project.nvim")

-- -------------------- 启动页面 --------------------
use("glepnir/dashboard-nvim")

-- -------------------- 语法高亮 --------------------
use({ "nvim-treesitter/nvim-treesitter", run = ":TSUpdate" })

-- -------------------- LSP --------------------
use("williamboman/mason.nvim")
use({ "williamboman/mason-lspconfig.nvim" })
use("neovim/nvim-lspconfig")
use("jose-elias-alvarez/null-ls.nvim")

-- -------------------- 代码补全 --------------------
use("hrsh7th/nvim-cmp")
use("hrsh7th/vim-vsnip")
use("hrsh7th/cmp-vsnip")
use("hrsh7th/cmp-nvim-lsp")
use("hrsh7th/cmp-buffer")
use("hrsh7th/cmp-path")
use("hrsh7th/cmp-cmdline")
use("rafamadriz/friendly-snippets")

-- -------------------- UI 美化 --------------------
use("tami5/lspsaga.nvim")

-- -------------------- 调试 --------------------
use("CRAG666/code_runner.nvim")
use("windwp/nvim-autopairs")
use("numToStr/Comment.nvim")
use("ur4ltz/surround.nvim")

-- -------------------- TypeScript 增强 --------------------
use({ "jose-elias-alvarez/nvim-lsp-ts-utils", requires = "nvim-lua/plenary.nvim" })
use("b0o/schemastore.nvim")

-- -------------------- Rust 增强 --------------------
use("simrat39/rust-tools.nvim")
end)

7.3 Packer 命令

命令 说明
:PackerCompile 重新生成编译的加载文件
:PackerClean 清除不用的插件
:PackerInstall 安装缺失的插件
:PackerUpdate 更新并安装插件
:PackerSync 更新 + 编译
:PackerLoad 立刻加载 opt 插件

7.4 自动安装

1
2
3
4
5
6
7
-- lua/plugins.lua 末尾添加
pcall(vim.cmd, [[
augroup packer_user_config
autocmd!
autocmd BufWritePost plugins.lua source <afile> | PackerSync
augroup end
]])

八、主题配置 colorscheme.lua

1
2
3
4
5
6
local colorscheme = "tokyonight"
local status_ok, _ = pcall(vim.cmd, "colorscheme " .. colorscheme)
if not status_ok then
vim.notify("colorscheme " .. colorscheme .. " 没有找到!")
return
end

推荐主题

  • tokyonight
  • monokai.nvim
  • dracula
  • nord

九、插件配置详解

9.1 nvim-tree.lua

lua/plugin-config/nvim-tree.lua

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
local status, nvim_tree = pcall(require, "nvim-tree")
if not status then return end

nvim_tree.setup({
git = { enable = false },
update_cwd = true,
update_focused_file = { enable = true, update_cwd = true },
filters = { dotfiles = true, custom = { 'node_modules' } },
view = {
width = 40,
side = 'left',
hide_root_folder = false,
number = false,
relativenumber = false,
signcolumn = 'yes',
},
actions = {
open_file = {
resize_window = true,
quit_on_open = true,
},
},
system_open = { cmd = 'wsl-open' },
})

vim.cmd([[
autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
]])

9.2 bufferline.lua

lua/plugin-config/bufferline.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local status, bufferline = pcall(require, "bufferline")
if not status then return end

bufferline.setup({
options = {
close_command = "Bdelete! %d",
right_mouse_command = "Bdelete! %d",
offsets = { { filetype = "NvimTree", text = "File Explorer", highlight = "Directory" } },
diagnostics = "nvim_lsp",
diagnostics_indicator = function(count, level, diagnostics_dict, context)
local s = " "
for e, n in pairs(diagnostics_dict) do
local sym = e == "error" and " " or (e == "warning" and " " or "")
s = s .. n .. sym
end
return s
end,
},
})

快捷键:

1
2
3
4
5
6
map("n", "<C-h>", ":BufferLineCyclePrev<CR>", opt)
map("n", "<C-l>", ":BufferLineCycleNext<CR>", opt)
map("n", "<C-w>", ":Bdelete!<CR>", opt)
map("n", "<leader>bl", ":BufferLineCloseRight<CR>", opt)
map("n", "<leader>bh", ":BufferLineCloseLeft<CR>", opt)
map("n", "<leader>bc", ":BufferLinePickClose<CR>", opt)

9.3 lualine.lua

lua/plugin-config/lualine.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local status, lualine = pcall(require, "lualine")
if not status then return end

lualine.setup({
options = {
theme = "tokyonight",
component_separators = { left = "|", right = "|" },
section_separators = { left = " ", right = "" },
},
extensions = { "nvim-tree", "toggleterm" },
sections = {
lualine_c = {
"filename",
{ "lsp_progress", spinner_symbols = { " ", " ", " ", " ", " ", " " } },
},
lualine_x = { "filesize", "fileformat", "encoding", "filetype" },
},
})

9.4 nvim-treesitter.lua

lua/plugin-config/nvim-treesitter.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local status, treesitter = pcall(require, "nvim-treesitter.configs")
if not status then return end

treesitter.setup({
ensure_installed = { "json", "html", "css", "vim", "lua", "javascript", "typescript", "tsx", "rust", "c", "cpp" },
highlight = { enable = true, additional_vim_regex_highlighting = false },
incremental_selection = {
enable = true,
keymaps = {
init_selection = "<CR>",
node_incremental = "<CR>",
node_decremental = "<BS>",
scope_incremental = "<TAB>",
},
},
indent = { enable = true },
})

vim.opt.foldmethod = "expr"
vim.opt.foldexpr = "nvim_treesitter#foldexpr()"
vim.opt.foldlevel = 99

9.5 dashboard.lua

lua/plugin-config/dashboard.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local status, db = pcall(require, "dashboard")
if not status then return end

db.setup({
theme = 'doom',
config = {
header = {
[[ ]],
[[██████╗ ███████╗████████╗████████╗███╗ ███╗██████╗ ]],
[[██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝████╗ ████║██╔══██╗]],
[[██████╔╝█████╗ ██║ ██║ ██╔████╔██║██████╔╝]],
[[██╔══██╗██╔══╝ ██║ ██║ ██║╚██╔╝██║██╔═══╝ ]],
[[██║ ██║███████╗ ██║ ██║ ██║ ╚═╝ ██║██║ ]],
[[╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ]],
},
center = {
{ icon = " ", desc = "Projects", action = "Telescope projects" },
{ icon = " ", desc = "Recently files", action = "Telescope oldfiles" },
{ icon = " ", desc = "Edit keybindings", action = "edit ~/.config/nvim/lua/keybindings.lua" },
{ icon = " ", desc = "Edit Projects", action = "edit ~/.local/share/nvim/project_nvim/project_history" },
},
footer = {},
},
})

9.6 nvim-autopairs.lua

lua/plugin-config/nvim-autopairs.lua

1
2
3
4
5
6
7
8
9
10
11
local status, autopairs = pcall(require, "nvim-autopairs")
if not status then return end

autopairs.setup({
check_ts = true,
ts_config = { lua = { "string" }, javascript = { "template_string" }, java = false },
})

local cmp_autopairs = require("nvim-autopairs.completion.cmp")
local cmp = require("cmp")
cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done())

9.7 comment.lua

lua/plugin-config/comment.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local status, comment = pcall(require, "Comment")
if not status then return end

comment.setup({
padding = true,
sticky = true,
toggler = { line = "gcc", block = "gbc" },
opleader = { line = "gc", block = "gb" },
extra = { above = "gcO", below = "gc", eol = "gcA" },
mappings = {
extra = false,
},
})

vim.api.nvim_set_keymap("n", "<leader>/", "<cmd>lua require('Comment').toggle()<CR>", { noremap = true })
vim.api.nvim_set_keymap("v", "<leader>/", "<cmd>lua require('Comment').toggle()<CR>", { noremap = true })

9.8 surround.lua

lua/plugin-config/surround.lua

1
2
3
4
local status, surround = pcall(require, "surround")
if not status then return end

surround.setup({ style_separator_prefix = " " })

快捷键:

快捷键 说明
ds + ( ) } " 删除成对符号
cs + ( ) } " 修改成对符号
ys + 动作 + 符号 添加成对符号

9.9 telescope.lua

lua/plugin-config/telescope.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local status, telescope = pcall(require, "telescope")
if not status then return end

telescope.setup({
defaults = {
initial_mode = "insert",
selection_strategy = "reset",
color_devkit = true,
file_sorter = sorters.get_fuzzy_sorter,
file_ignore_patterns = { "node_modules", ".git", "dist", "target", "__pycache__" },
generic_sorter = sorters.get_fuzzy_sorter,
winblend = 0,
border = {},
borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
color_devkit = true,
path_display = { "shorten" },
set_env = { ["COLORTERM"] = "truecolor" },
file_previewer = previewers.vim_buffer_cat.new,
grep_previewer = previewers.vim_buffer_vimgrep.new,
qflist_previewer = previewers.vim_buffer_qflist.new,
},
extensions_list = { "themes", "terms", "projects" },
})

快捷键:

1
2
3
4
5
6
7
8
9
map("n", "<C-p>", "<cmd>Telescope projects<CR>", opt)
map("n", "<C-f>", "<cmd>Telescope live_grep<CR>", opt)
map("n", "<C-s>", "<cmd>Telescope grep_string<CR>", opt)
map("n", "<leader>ff", "<cmd>Telescope find_files<CR>", opt)
map("n", "<leader>fb", "<cmd>Telescope buffers<CR>", opt)
map("n", "<leader>fh", "<cmd>Telescope help_tags<CR>", opt)
map("n", "<leader>fo", "<cmd>Telescope oldfiles<CR>", opt)
map("n", "<leader>fc", "<cmd>Telescope commands<CR>", opt)
map("n", "<leader>fp", "<cmd>Telescope project<CR>", opt)

9.10 toggleterm.lua

lua/plugin-config/toggleterm.lua

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
local status, toggleterm = pcall(require, "toggleterm")
if not status then return end

toggleterm.setup({
size = 12,
open_mapping = [[<F5>]],
hide_numbers = true,
shade_filetypes = {},
shade_terminals = true,
shading_factor = 2,
start_in_insert = true,
insert_mappings = true,
persist_size = false,
direction = "float",
close_on_exit = true,
shell = vim.o.shell,
float_opts = { border = "curved", winblend = 3, highlights = { border = "Normal", background = "Normal" } },
})

function _G.set_terminal_keymaps()
vim.api.nvim_buf_set_keymap(0, "t", "<esc>", [[<C-\><C-n>]], { noremap = true })
vim.api.nvim_buf_set_keymap(0, "t", "<A-h>", [[<C-\><C-n><C-w>h]], { noremap = true })
vim.api.nvim_buf_set_keymap(0, "t", "<A-j>", [[<C-\><C-n><C-w>j]], { noremap = true })
vim.api.nvim_buf_set_keymap(0, "t", "<A-k>", [[<C-\><C-n><C-w>k]], { noremap = true })
vim.api.nvim_buf_set_keymap(0, "t", "<A-l>", [[<C-\><C-n><C-w>l]], { noremap = true })
end

vim.cmd("autocmd! TermOpen term://* lua set_terminal_keymaps()")

local Terminal = require("toggleterm.terminal").Terminal
local htop = Terminal:new({ cmd = "htop", hidden = true, direction = "float" })
function _HTOP_HIDE() htop:toggle() end
vim.api.nvim_set_keymap("n", "<F4>", "<cmd>lua _HTOP_HIDE()<CR>", { silent = true })

9.11 lspsaga.nvim

lua/lsp/ui.lua

1
2
3
4
5
6
7
8
local status, saga = pcall(require, "lspsaga")
if not status then return end

saga.init_lsp_saga({
loader = {
load_charsets_async = true,
},
})

9.12 project.lua

lua/plugin-config/project.lua

1
2
3
4
5
6
7
8
9
10
11
12
local status, project = pcall(require, "project_nvim")
if not status then return end

project.setup({
detection_methods = { "lsp", "pattern" },
patterns = { ".git", "package.json", "CMakeLists.txt", "Makefile" },
})

local status_telescope, _ = pcall(require, "telescope._extensions")
if status_telescope then
pcall(require("telescope").load_extension, "project")
end

9.13 gitsigns.lua

lua/plugin-config/gitsigns.lua

1
2
3
4
local status, gitsigns = pcall(require, "gitsigns")
if not status then return end

gitsigns.setup()

快捷键:

快捷键 说明
]c 跳至下一个差异
[c 跳至上一个差异
<leader>gs Git 显示

9.14 indent-blankline.lua

lua/plugin-config/indent-blankline.lua

1
2
3
4
5
6
7
8
9
local status, indent = pcall(require, "indent_blankline")
if not status then return end

indent.setup({
space_char_blankline = " ",
show_current_context = true,
show_current_context_start = true,
context_pattern_backup = {},
})

十、LSP 配置

10.1 setup.lua

lua/lsp/setup.lua

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
local lspconfig = require("lspconfig")
local protocol = require("vim.lsp.protocol")
local protocol_callbacks = protocol.callbacks
local method_is_available = vim.lsp.handlers["textDocument/hover"]

vim.lsp.handlers["textDocument/hover"] = function(_, result, method, ...)
if vim.tbl_isempty(result or {}) then
return method_is_available(_, result, method, ...)
end
return protocol_callbacks.hover(_, result, method, ...)
end

protocol.symbol_name_provider = {
work_done_progress = false,
}

vim.cmd([[
function! LspSymbol(name, icon) abort
let l:symbols = {
\ 'Text': '🔤',
\ 'Method': '🍔',
\ 'Function': '🍞',
\ 'Variable': '🍜',
\ 'Interface': '🍙',
\ 'File': '🍚',
\ 'Module': '🍛',
\ 'Property': '🍣',
\ 'Field': '🍤',
\ 'Enum': '🍥',
\ 'Keyword': '🍣',
\ 'Constant': '🍣',
\ 'Class': '🍣',
\ 'Struct': '🍣',
\ 'Event': '🍣',
\ 'Operator': '🍣',
\ 'Ref': '🍣',
\ 'TypeParameter': '🍣',
\ 'Parameter': '🍣',
\ 'StaticMethod': '🍣',
\ 'Namespace': '🍣',
\ }
return get(l:symbols, a:name, '📖')
endfunction

augroup LspSymbol_highlight
autocmd!
autocmd WinEnter * silent! lua if vim.tbl_isempty(vim.lsp.buf_get_active_clients()) == false then vim.cmd('highlight LspSymbol guifg=#b4d51c')| endif
augroup END
]])

local list = {
"pyright",
"rust_analyzer",
"tsserver",
"gopls",
"jsonls",
"html",
"cssls",
"julials",
"bashls",
"dockerls",
"yamlls",
"vimls",
"cmake",
"lemminx",
}

for _, server in pairs(list) do
lspconfig[server].setup({
flags = { debounce_text_changes = 500 },
capabilities = require("lsp.cmp").capabilities,
on_attach = function(client, bufnr)
require("lsp.ui").on_attach(client, bufnr)
end,
})
end

10.2 cmp.lua

lua/lsp/cmp.lua

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
local lsp_status_ok, cmp_config = pcall(require, "copilot_cmp.config")
if not lsp_status_ok then
local status_cmp, cmp = pcall(require, "cmp")
if not status_cmp then
return
end

local status_cmp_lsp, cmp_lsp = pcall(require, "cmp_nvim_lsp")
if not status_cmp_lsp then
return
end

local snip_status_ok, luasnip = pcall(require, "luasnip")
if not snip_status_ok then
return
end

local border = {
{ "╭", "LspSagaBorderTitle" },
{ "─", "LspSagaBorderTitle" },
{ "╮", "LspSagaBorderTitle" },
{ "│", "LspSagaBorderTitle" },
{ "╯", "LspSagaBorderTitle" },
{ "─", "LspSagaBorderTitle" },
{ "╰", "LspSagaBorderTitle" },
{ "│", "LspSagaBorderTitle" },
}

local options = {
window = {
completion = {
border = border,
},
documentation = {
border = border,
},
},
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
mapping = require("keybindings").pluginKeys.cmp(cmp),
sources = cmp.config.sources({
{ name = "nvim_lsp", priority = 1000 },
{ name = "vsnip", priority = 750 },
{ name = "buffer", priority = 500, max_item_count = 10 },
{ name = "path", priority = 250 },
}),
formatting = {
format = function(entry, vim_item)
local lsp_source_icons = {
vscode = " ",
nvimg = " ",
}
vim_item.kind = string.format("%s %s", lsp_source_icons[entry.source.name] or " ", vim_item.kind)
vim_item.menu = ({
nvim_lsp = "[LSP]",
vsnip = "[SNIP]",
buffer = "[BUF]",
path = "[PATH]",
})[entry.source.name]
vim_item.icons = string.format("%s %s", lsp_source_icons[entry.source.name] or " ", vim_item.kind)
return vim_item
end,
},
confirm_opts = {
behavior = cmp.ConfirmBehavior.Replace,
select = false,
},
}

cmp.setup("n", { "<leader>rn", "LspSource" })
cmp.setup(options)

cmp.setup.cmdline("/", {
mapping = cmp.mapping.preset.cmdline(),
sources = {
{ name = "buffer" },
},
})

cmp.setup.cmdline(":", {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = "path" },
}, {
{ name = "cmdline" },
}),
})
end

10.3 formatter.lua

lua/lsp/formatter.lua

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
local status, null_ls = pcall(require, "null-ls")
if not status then
return
end

local formatting = null_ls.builtins.formatting
local diagnostics = null_ls.builtins.diagnostics

null_ls.setup({
debug = false,
sources = {
formatting.prettier.with({ extra_filetypes = { "toml", "tsx", "jsx" } }),
formatting.black.with({ extra_args = { "--fast" } }),
formatting.stylua,
formatting.shfmt,
formatting.codespell,
diagnostics.eslint,
formatting.eslint,
},
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({ async = true, bufnr = bufnr })
end,
})
end
end,
})

十一、常见问题

11.1 Neovim 输入法切换

安装 im-select

1
npm install -g im-select

配置 lua/utils/im-select.lua

1
2
3
-- 自动切换输入法
vim.g.im_select_enable = 1
vim.g.im_select_default = 1

11.2 WSL2 打开 Windows 文件管理器

安装 wsl-open

1
npm i -g wsl-open

11.3 禁用自动备份

1
2
3
4
-- ~/.config/nvim/init.lua
vim.o.backup = false
vim.o.writebackup = false
vim.o.swapfile = false

11.4 配置查找与帮助

命令 说明
:h {subject} 查看帮助
:h <C-d> 列出所有主题
:h <option> 查看选项含义
:checkhealth 检查健康状态

一、前言

Gitea Actions 是 Gitea 1.19+ 内置的 CI/CD 组件,兼容 GitHub Actions 语法与生态,基于 Docker 可实现一键部署、环境隔离、运维极简,适合自建代码托管与自动化流水线。

本文全程使用 Docker + Docker Compose 部署,命令可直接复制执行。


二、环境要求

  • 系统:Linux x86_64

  • 依赖:Docker、Docker Compose

  • 内存:≥4GB

  • 端口:3000(Web)、222(Git SSH)


三、Docker Compose 部署 Gitea

1. 创建部署目录

1
mkdir -p /opt/gitea && cd /opt/gitea

2. 编写 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: "3"

services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: always
ports:
- "3000:3000"
- "222:22"
volumes:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- GITEA__actions__ENABLED=true

3. 启动 Gitea

1
docker compose up -d

4. 初始化配置

访问 http://服务器IP:3000,按页面提示完成初始化。


四、Docker 部署 Act Runner

1. 创建 Runner 数据目录

1
2
mkdir -p /opt/gitea/act-runner
chmod 777 /opt/gitea/act-runner

2. 获取 Runner 注册令牌

  1. 管理员登录 Gitea

  2. 管理后台 → Actions → Runners

  3. 点击「注册新 Runner」,复制令牌

3. 注册 Runner

1
2
3
4
5
6
7
8
docker run --rm \
-v /opt/gitea/act-runner:/data \
gitea/act_runner:latest \
act_runner register \
--instance http://服务器IP:3000 \
--token 你的注册令牌 \
--labels ubuntu-latest:docker://node:16-bullseye \
--no-interactive

4. 启动 Runner 容器

1
2
3
4
5
6
7
docker run -d \
--name act-runner \
--restart always \
-v /opt/gitea/act-runner:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
gitea/act_runner:latest \
act_runner daemon

五、仓库启用 Actions

  1. 进入目标仓库 → 设置

  2. 勾选「启用 Actions」

  3. 保存设置


六、创建 CI/CD 工作流

在仓库创建 .gitea/workflows/demo.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: Gitea Actions Demo
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 本次任务由 ${{ gitea.event_name }} 事件触发。"
- run: echo "🐧 任务正在 Gitea 提供的 ${{ runner.os }} 服务器上运行!"
- name: 检出代码
uses: actions/checkout@v3
- name: 列出文件
run: |
ls -la ${{ gitea.workspace }}
- run: echo "✅ 工作流执行完成。"

七、触发流水线与查看日志

  1. 提交并推送工作流文件
1
2
3
git add .gitea/workflows/demo.yaml
git commit -m "add ci workflow"
git push
  1. 进入仓库 → Actions 查看运行状态与实时日志

八、常用运维命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 重启 Gitea
docker compose restart gitea

# 重启 Runner
docker restart act-runner

# 查看 Gitea 日志
docker logs -f gitea

# 查看 Runner 日志
docker logs -f act-runner

# 停止服务
docker compose down
docker stop act-runner

九、总结

  • Docker 部署 Gitea 开箱即用,通过环境变量可直接开启 Actions 功能,无需复杂配置,极大降低部署门槛;

  • Act Runner 以 Docker 容器形式运行,通过挂载 /var/run/docker.sock 实现容器内任务执行,保证环境隔离性与兼容性;

  • Gitea Actions 高度兼容 GitHub Actions 工作流语法与生态,无需额外学习新知识点,可直接复用现有 GitHub Actions 插件与配置;

  • 实现代码托管与 CI/CD 一体化,流程简洁、运维便捷,非常适合个人及小团队自建轻量型 DevOps 平台

Beego

官网 https://beego.me

github https://github.com/astaxie/beego

优点:

  • 很全很简单
  • 不仅追求性能,同样追求开发效率,解放程序员的生产力
  • 社区良好,中文开发者很多,找资料很方便
  • 代码文档化做的很优秀

缺点:

  • 比较臃肿,因为提供了很多支持,当遇到坑时需要花很多时间查源码解决问题
  • 模块众多,这既是优点也是缺点

Echo

官网 https://echo.labstack.com

github https://github.com/labstack/echo

优点:

  • 路由性能高
  • 更轻量级的web开发框架

缺点:

  • 调试不方便,报错信息不友好
  • 路由性能虽高,但是路由实现的算法底层不支持路由排序,会引起路由冲突

Gin

官网 https://gin-gonic.github.io/gin

github https://github.com/gin-gonic/gin

优点:

  • 封装比较好,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json

缺点:

  • 封装比较好,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json

Iris

官网 https://iris-go.com

github https://github.com/kataras/iris

优点:

  • 是社区驱动的Go语言Web 框架,支持http2,完备 MVC 支持。
  • 极简主义风格
  • 社区活跃度和文档支持都非常到位

缺点:

  • 不够稳定,社区里有人反馈:最新的release版本是alpha版非常不稳定
  • 支持Iris的人很多,但是目前仍然不如Gin和Echo多

GB/T 35134-2017 物联网智能家居 设备描述方法

GB/T 35143-2017 物联网智能家居 数据和设备编码

GB/T 35317-2017 公安物联网系统信息安全等级保护要求

GB/T 35318-2017 公安物联网感知终端安全防护技术要求

GB/T 35319-2017 物联网 系统接口要求

GB/T 35419-2017 物联网标识体系 Ecode在一维条码中的存储

GB/T 35420-2017 物联网标识体系 Ecode在二维码中的存储

GB/T 35421-2017 物联网标识体系 Ecode在射频标签中的存储

GB/T 35422-2017 物联网标识体系 Ecode的注册与管理

GB/T 35423-2017 物联网标识体系 Ecode在NFC标签中的存储

GB/T 35592-2017 公安物联网感知终端接入安全技术要求

GB/T 35136-2017 智能家居自动控制设备通用技术要求

GB/T 35255-2017 LED公共照明智能系统接口应用层通信协议

GB/T 35291-2017 信息安全技术 智能密码钥匙应用接口规范

GB/T 30269.502-2017 信息技术 传感器网络 第502部分:标识:传感节点标识符解析

GB/T 30269.602-2017 信息技术 传感器网络 第602部分:信息安全:低速率无线传感器网络网络层和应用支持子层安全规范

GB/T 30269.801-2017 信息技术 传感器网络 第801部分:测试:通用要求

GB/T 30269.803-2017 信息技术 传感器网络 第803部分:测试:低速无线传感器网络网络层和应用支持子层

GB/T 35129-2017 面向食品制造业的射频识别系统 环境适应性要求

GB/T 35130-2017 面向食品制造业的射频识别系统 射频标签信息与编码规范

GB/T 35135-2017 面向食品制造业的射频识别系统 应用要求

GB/T 17626.6-2017 电磁兼容 试验和测量技术 射频场感应的传导骚扰抗扰度

GB/T 35290-2017 信息安全技术 射频识别(RFID)系统通用安全技术要求

GB/T 35120-2017 制造过程物联的数字化模型信息交换规范

GB/T 35122-2017 制造过程物联的数字化模型信息表达规范

GB/T 35128-2017 集团企业经营管理信息化核心构件

GB/T 34966.1-2017 卫星导航增强信息互联网传输 第1部分:播发体制

GB/T 34966.2-2017 卫星导航增强信息互联网传输 第2部分:接口要求

GB/T 34966.3-2017 卫星导航增强信息互联网传输 第3部分:数据传输格式

GB/T 35403.1-2017 国家物品编码与基础信息通用规范 第1部分:总体框架

GB/T 35589-2017 信息技术 大数据 技术参考模型

第一部分:10种核心计算机视觉算法及其应用

计算机视觉算法是让机器“看懂”世界的关键。以下是10种最核心的算法及其典型应用场景。

算法类别 核心描述 典型应用场景
1. 卷积神经网络 (CNN) 专为图像设计的深度学习模型,通过卷积层自动提取特征。 图像分类(ImageNet)、人脸识别、医疗影像分析。
2. 边缘检测 识别图像中像素强度发生显著变化的边界。常用算法:Canny, Sobel。 目标轮廓提取、图像分割、自动驾驶中的车道线检测。
3. Haar级联分类器 基于Haar特征和级联结构的快速检测算法,采用滑动窗口。 实时人脸检测、监控系统中的行人检测。
4. 霍夫变换 用于检测图像中特定几何形状(如直线、圆)的算法。 车道线检测、交通标志(圆形)识别。
5. 光流法 计算视频序列中像素点的运动矢量场。常用算法:Lucas-Kanade。 视频稳定、运动目标跟踪、行为分析。
6. 特征点检测与匹配 检测并描述图像中的关键点(如SIFT, SURF, ORB),用于匹配不同图像。 图像拼接(全景图)、物体识别、增强现实(AR)。
7. 图像分割 将图像划分为具有相似属性的区域。方法包括:聚类、GrabCut、全卷积网络(FCN)。 医学图像分析(器官分割)、背景虚化/更换、自动驾驶场景理解。
8. 生成对抗网络 (GAN) 由生成器和判别器组成,通过对抗训练生成逼真数据。 图像生成、老照片修复、艺术风格迁移。
9. 目标检测 定位并识别图像中的多个物体,输出边界框和类别。代表模型:YOLO, Faster R-CNN, SSD。 安防监控、自动驾驶(车辆/行人检测)、零售商品识别。
10. 深度卷积生成对抗网络 (DCGAN) 结合CNN与GAN的架构,用于生成更高质量的图像。 高分辨率图像合成、数字艺术创作、数据增强。

代码示例:使用OpenCV运行YOLO进行目标检测

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cv2
import numpy as np

# 1. 加载YOLO模型
net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
classes = []
with open("coco.names", "r") as f:
classes = [line.strip() for line in f.readlines()]
layer_names = net.getLayerNames()
output_layers = [layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()]

# 2. 读取并预处理图像
img = cv2.imread("image.jpg")
height, width, channels = img.shape
blob = cv2.dnn.blobFromImage(img, 0.00392, (416, 416), (0, 0, 0), True, crop=False)
net.setInput(blob)

# 3. 前向传播,获取检测结果
outs = net.forward(output_layers)

# 4. 解析结果并绘制边界框
class_ids, confidences, boxes = [], [], []
for out in outs:
for detection in out:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.5: # 置信度阈值
# 计算边界框坐标
center_x, center_y = int(detection[0]*width), int(detection[1]*height)
w, h = int(detection[2]*width), int(detection[3]*height)
x, y = int(center_x - w/2), int(center_y - h/2)
boxes.append([x, y, w, h])
confidences.append(float(confidence))
class_ids.append(class_id)

# 5. 非极大值抑制 (NMS) 去除重叠框
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)

# 6. 在图像上绘制最终检测结果
font = cv2.FONT_HERSHEY_PLAIN
colors = np.random.uniform(0, 255, size=(len(classes), 3))
for i in range(len(boxes)):
if i in indexes:
x, y, w, h = boxes[i]
label = str(classes[class_ids[i]])
color = colors[class_ids[i]]
cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)
cv2.putText(img, label, (x, y+30), font, 2, color, 2)

cv2.imshow("Detection Result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

第二部分:16个顶级计算机视觉开发库

选择合适的工具库能极大提升开发效率。以下是当前最流行和强大的计算机视觉库。

库名称 主要特点与描述 适用场景与语言
1. OpenCV 最古老、最全面的开源计算机视觉库,提供2500+种优化算法。 通用:图像处理、目标检测、人脸识别等。支持 C++, Python, Java
2. Scikit-Image 基于NumPy的图像处理库,是Scikit-Learn的扩展,API设计简洁。 学术/研究:图像滤波、分割、特征提取。主要支持 Python
3. Pillow (PIL Fork) Python图像处理标准库,专注于图像文件的打开、操作和保存。 基础图像操作:格式转换、缩放、裁剪、绘制。支持 Python
4. TorchVision PyTorch的官方视觉库,提供数据集、模型架构和图像变换工具。 深度学习:与PyTorch无缝集成,用于训练和部署CV模型。支持 Python
5. MMCV OpenMMLab项目的基础库,为计算机视觉研究提供统一底层支持。 研究框架基础:被MMDetection, MMPose等项目依赖。支持 Python
6. YOLO (Ultralytics) 以速度著称的实时目标检测框架,社区活跃,版本迭代快(已至v8)。 实时检测:需要高帧率的目标检测应用。支持 Python
7. TensorFlow Google开发的端到端机器学习平台,拥有庞大的模型生态(TensorFlow Hub)。 生产与部署:大型模型训练、跨平台部署(TF Lite, TF.js)。支持多语言。
8. Keras 高级神经网络API,以易用性著称,现已成为TensorFlow的官方高阶API。 快速原型:让初学者和研究者能快速搭建和实验模型。支持 Python
9. MATLAB 商业数学软件,提供强大的计算机视觉工具箱和Simulink仿真环境。 工业与学术:算法开发、仿真、系统设计。付费,支持自身语言。
10. NVIDIA CUDA-X NVIDIA的GPU加速库集合,为视觉计算提供底层硬件加速。 高性能计算:需要极致速度的图像/视频处理、深度学习推理。
11. OpenVINO Intel开发的工具包,用于优化和部署视觉AI推理,支持边缘设备。 边缘计算与推理优化:在Intel硬件上高效运行训练好的模型。
12. PyTorch 由Facebook主导的深度学习框架,以动态图和灵活性深受研究人员喜爱。 研究与实验:需要灵活调整模型结构的研究项目。支持 Python
13. Caffe 早期流行的深度学习框架,以速度和模块化设计闻名,尤其适合视觉任务。 传统CV项目与部署:一些老项目仍在使用,部署较成熟。支持 C++/Python
14. Detectron2 Facebook AI Research (FAIR) 开发的基于PyTorch的目标检测与分割库。 前沿研究:实现Mask R-CNN, DensePose等先进模型。支持 Python
15. SimpleCV 一个让计算机视觉变得简单的框架,旨在降低OpenCV等库的使用门槛。 教育与入门:适合初学者理解概念,无需深入底层细节。支持 Python
16. Kornia 基于PyTorch的可微分计算机视觉库,将传统CV操作集成到深度学习流程中。 可微分视觉:需要在深度学习模型中嵌入传统CV算子的研究。

选择建议

  • 初学者/快速上手:从 OpenCV (Python接口)Keras 开始。
  • 学术研究/模型实验PyTorch + TorchVisionDetectron2 是主流。
  • 工业部署/生产环境TensorFlowOpenVINOTensorRT 更成熟。
  • 实时目标检测YOLO 系列是首选。

第三部分:21个经典开源计算机视觉数据集

高质量的数据集是训练和评估模型的基础。以下按字母顺序整理了21个经典数据集。

数据集名称 简介与规模 主要任务与类别
1. COVID-19 X-Ray 包含6500张胸部X光片,517例COVID-19病例,带有像素级肺部分割掩码。 医学图像分析:肺炎分类、肺部区域分割。
2. CIFAR-10 / CIFAR-100 小图像数据集。CIFAR-10: 6万张32x32图,10类。CIFAR-100: 100类,每类600张。 图像分类:物体与动物分类,常用于模型基准测试。
3. ImageNet 超大规模数据集,超过1400万张手工标注图像,涵盖2万多个类别。 图像分类/目标检测:计算机视觉研究的基石。
4. Kinetics-700 大型视频数据集,65万个10秒视频片段,涵盖700个人类动作类别。 视频动作识别:人与物、人与人的交互行为。
5. MNIST 经典手写数字数据集,6万训练+1万测试,28x28灰度图。 图像分类(入门):数字识别,堪称“CV界的Hello World”。
6. LSUN 大规模场景理解数据集,近百万图像,对应10个场景和20个物体类别。 场景分类/目标检测:室内外场景与物体。
7. IMDB-Wiki 最大的人脸数据集之一,52万张名人图像,标注了姓名、性别、年龄。 人脸识别/属性分析:身份、年龄、性别识别。
8. MS COCO 微软发布的大规模数据集,33万张图像,包含目标检测、分割、字幕等丰富标注。 目标检测/实例分割/图像描述:多任务基准数据集。
9. Labeled Faces in the Wild 专注于无约束条件下的人脸识别,包含1.3万张人脸图像,标注身份。 人脸验证/识别:测试模型在真实场景下的性能。
10. Cityscapes 城市街道场景数据集,50个城市,5000帧精细标注,2万帧粗略标注。 语义分割/实例分割:自动驾驶场景理解。
11. LabelMe-12-50k 包含5万张图像,12个物体类别,图像为中心物体或随机区域。 目标识别:通用物体识别。
12. Places / Places2 大规模场景分类数据集。Places: 250万图,205类。Places2: 180万图,365类。 场景识别:室内外场景分类。
13. Visual Genome 图像理解数据集,10.8万张图像,包含物体、属性、关系等密集标注。 视觉推理/场景图生成:超越检测的深层理解。
14. Stanford Dogs 狗品种识别数据集,2万余张图像,涵盖120个品种。 细粒度图像分类:犬种识别。
15. Stanford Cars 汽车型号数据集,1.6万张图像,196个类别(汽车型号)。 细粒度图像分类:汽车型号识别。
16. Cat Dataset 猫脸关键点数据集,超过9000张猫脸图像,标注9个关键点(眼、耳、嘴)。 关键点检测:动物面部特征点定位。
17. CelebFaces (CelebA) 名人脸部属性数据集,超过20万张图像,每张标注40种属性和5个关键点。 人脸属性识别/ landmark检测
18. Face Mask Detection 口罩佩戴检测数据集,853张图像,3类(戴好/未戴/错误佩戴)及边界框。 目标检测:特定场景下的安全检测。
19. Fire and Smoke Dataset 火灾与烟雾检测数据集,7000+张高清图像,涵盖多种真实场景。 异常检测/目标检测:安防与灾害预警。
20. FloodNet Dataset 飓风灾害评估数据集,2343张无人机高清图像,带有灾害损坏的语义标注。 语义分割/灾害评估:灾后分析。
21. Cat Dataset (注:原文重复,可能指另一个猫相关数据集)

数据集使用指引

  • 入门学习MNIST, CIFAR-10 复杂度低,适合练手。
  • 通用目标检测/分割MS COCO, PASCAL VOC 是行业标准。
  • 人脸相关CelebA, LFW 分别适用于属性识别和验证。
  • 自动驾驶Cityscapes, KITTI 提供街景数据。
  • 视频理解Kinetics 是动作识别的主流数据集。

总结

计算机视觉领域资源丰富,从基础算法的理解,到开发库的熟练运用,再到数据集的恰当选择,是逐步深入该领域的三个关键环节。建议初学者遵循“算法原理 -> 工具实践 -> 数据训练”的路径进行系统学习。

0%