分类 编程语言 下的文章

一、问题

在vercel部署nextjs项目时,报错A commit author is required,如图所示:

github的部署也报错:No GitHub account was found matching the commit author email address

二、原因

提交commit时候的邮箱未绑定到github中去。

三、解决方案

解决方案有两种:

  1. 修改commit的邮箱为已经绑定到github的邮箱
  2. 在github上绑定commit的邮箱

受限通过git log命令查看当前commit的邮箱:

Author列会展示当前提交的用户及邮箱,将邮箱登记到githb中去(位于Settings - Emails - Add email address):

或者通过命令修改git的用户和邮箱,邮箱要和github中绑定的一致:

git config --global user.name "username"
git config --global user.email "username@email.com"

然后就可以正常部署了:

四、参考

Adding an email address to your GitHub account

一、防抖技术

防抖是一种编程技术,用于限制某个函数在短时间内被频繁调用的次数。具体来说,当一个函数被触发时,防抖会确保该函数在指定的延迟时间内只执行一次。如果在延迟时间内再次触发该函数,计时器会被重置,重新开始计算延迟时间。

这在处理高频率事件(如窗口调整大小、输入框输入、拖动操作等)时非常有用,可以避免因频繁触发事件而导致的性能问题。

Lodash

Lodash 是一个流行的 JavaScript 工具库,提供了许多实用的函数,包括 debounce。以下是如何使用 Lodash 的 debounce 来优化你的前端代码。

二、使用Lodash

2.1 安装 Lodash

如果你的项目中还没有安装 Lodash,可以使用 npm 或 yarn 进行安装:

npm install lodash
pnpm install lodash

或者

yarn add lodash

2.2 引入debounce

在需要使用防抖功能的组件中引入 debounce

import { debounce } from 'lodash';

2.3 创建防抖函数

使用 debounce 包装你需要限制执行频率的函数。例如,在你的 CanvasComponent 中:

const saveGraphicsData = debounce((data) => {
  // 这里执行保存图形数据的逻辑
  console.log('Saving graphics data:', data);
  // 例如:调用API保存数据
}, 200); // 200毫秒的延迟

解释

  • saveGraphicsData 是经过防抖处理的函数。
  • 当 saveGraphicsData 被调用时,它不会立即执行,而是等待 200 毫秒。
  • 如果在这 200 毫秒内再次调用 saveGraphicsData,计时器会被重置,重新开始等待 200 毫秒。
  • 只有当 200 毫秒内没有新的调用时,saveGraphicsData 才会真正执行。

2.4 绑定事件处理

在你的事件处理函数中调用防抖函数:

const handleChange = (event) => {
  const data = event.target.value; // 根据实际情况获取数据
  saveGraphicsData(data);
};

解释

  • 每当 onChange 事件被触发时,handleChange 函数会被调用。
  • handleChange 调用 saveGraphicsData,但由于 saveGraphicsData 是防抖函数,实际的保存操作会被延迟执行。

2.5 清理防抖函数

为了避免内存泄漏,需要在组件卸载时取消防抖函数的计时器:

useEffect(() => {
  return () => {
    saveGraphicsData.cancel();
  };
}, []);

解释

  • useEffect 的返回函数会在组件卸载时执行。
  • saveGraphicsData.cancel() 会取消任何未执行的防抖计时器,确保不会有残留的回调函数在组件卸载后执行。

2.6 完整示例

import { useEffect, useRef } from 'react';
import { debounce } from 'lodash';

const CanvasComponent = () => {
  const canvasRef = useRef(null);
  const saveGraphicsData = useRef(null);

  // 创建防抖函数
  saveGraphicsData.current = debounce((data) => {
    console.log('Saving graphics data:', data);
    // 实际保存数据的逻辑
  }, 200);

  const handleChange = (event) => {
    const data = event.target.value; // 根据实际情况获取数据
    saveGraphicsData.current(data);
  };

  useEffect(() => {
    return () => {
      // 清理防抖函数
      saveGraphicsData.current.cancel();
    };
  }, []);

  return (
    <canvas ref={canvasRef} onChange={handleChange}>
      {/* Canvas 内容 */}
    </canvas>
  );
};

export default CanvasComponent;

三、ES语法问题

Lodash是一个比较古早的开源库,当时的代码规范和当前已经有所不同,因此在使用了es的项目中引入lodash会报错。

解决方案:使用lodash-es

pnpm install lodash-es @types/lodash-es

安装

脚本一键安装

境外的服务器可以使用脚本一键安装:

curl -L https://coder.com/install.sh | sh

手动安装

官方的安装脚本下载太慢了,直接手动下载rpm安装:

wget https://file.outman.icu/software/code-server-4.22.1-amd64.rpm  --no-check-certificate
sudo rpm -ivh code-server-4.22.1-amd64.rpm

启动服务

启动服务:

sudo systemctl enable --now code-server@$USER

启动后默认监听localhost:8080,初始密码以及配置默认放在cat ~/.config/code-server/config.yaml

bind-addr: 127.0.0.1:8080
auth: password
password: *********************
cert: false

配置

nginx反向代理

反响代理的配置需要注意添加web socket转发以及host头部的转发:

proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

登录页隐藏配置文件路径

在默认的登录页,会把密码所在的配置文件打印出来:

存在安全风险,需要去掉。修改/usr/lib/code-server/src/browser/pages/login.html文件,找到:

<div class="sub">{{I18N_LOGIN_BELOW}} {{PASSWORD_MSG}}</div>

删除其中的{{PASSWORD_MSG}}

GVM(Go Version Manager)是一款用于管理和切换不同Go语言版本的工具。它允许用户在同一台计算机上轻松安装、使用和管理多个Go版本,同时还能确保项目之间的依赖关系井然有序。GVM的主要功能包括:

  1. 安装和卸载Go版本:GVM允许用户快速安装和卸载Go语言的不同版本,以便在不同项目中使用。
  2. 切换Go版本:GVM可以轻松切换当前正在使用的Go版本,这对于在不同项目中使用不同Go版本的开发者来说非常有用。
  3. 设置默认Go版本:GVM允许用户设置一个默认的Go版本,以便在新的终端会话中自动使用。
  4. 管理Go的环境变量:GVM可以自动管理Go的环境变量,如GOROOTGOPATH,以确保每个Go版本的正确配置。
  5. 支持离线安装:GVM支持通过本地二进制包进行Go语言的安装,这对于无法访问Go官方网站的用户来说非常有帮助。

通过GVM,开发者可以更方便地在不同版本的Go语言之间进行切换,从而提高开发效率和降低潜在的兼容性问题。

如何安装gvm

首先将gvm安装脚本内容拷贝到本地:

wget https://file.maqian.xin/scripts/gvm-installer.sh

然后执行以下命令安装:

yum install bison
# 设置安装的代码源仓库,默认是github,在国内大概率拉不下来,因此需要拉到国内
export SRC_REPO=git@git.code.tencent.com:3part/gvm.git
# 安装
sh gvm_installer.sh

用法:

# 查看所有版本
gvm list
# 使用指定版本
gvm use go1.18
# 设置默认的版本
gvm use go1.18 --default

如何基于本地包安装go

gvm默认使用golang官网来下载二进制,但是国内无法访问golang官网,所以是无法安装成功。gvm提供了通过离线二进制包安装的能力,可以在国内golang网站下载好二进制包,放到~/.gvm/archive/目录下,然后执行命令安装:

gvm install go1.21.5 --binary

这样就解决了网络不通的问题。

注意:版本号必须要匹配

安装

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh

sh Miniconda3-latest-Linux-x86_64.sh

一路yes即可安装完成,完成后source ~/.zshrc或者source ~/.bashrc生效。

# shell自动加载
conda config --set auto_activate_base false
# 回滚
conda init --reverse $SHELL

初始化环境

创建环境

conda create --name myenv python=3.11

创建后不是默认生效的,需要执行conda activate myenv生效,如果需要默认生效则需要加入到~/.zshrc中:

echo "conda activate myenv" >>~/.zshrc

删除环境

conda remove --name myenv --all

安装包

conda install package_name

更新包

conda update package_name

查看已安装的package

conda list

更新conda

conda update conda

GVM(Go Version Manager)是一款用于管理和切换不同Go语言版本的工具。它允许用户在同一台计算机上轻松安装、使用和管理多个Go版本,同时还能确保项目之间的依赖关系井然有序。GVM的主要功能包括:

  1. 安装和卸载Go版本:GVM允许用户快速安装和卸载Go语言的不同版本,以便在不同项目中使用。
  2. 切换Go版本:GVM可以轻松切换当前正在使用的Go版本,这对于在不同项目中使用不同Go版本的开发者来说非常有用。
  3. 设置默认Go版本:GVM允许用户设置一个默认的Go版本,以便在新的终端会话中自动使用。
  4. 管理Go的环境变量:GVM可以自动管理Go的环境变量,如GOROOTGOPATH,以确保每个Go版本的正确配置。
  5. 支持离线安装:GVM支持通过本地二进制包进行Go语言的安装,这对于无法访问Go官方网站的用户来说非常有帮助。

通过GVM,开发者可以更方便地在不同版本的Go语言之间进行切换,从而提高开发效率和降低潜在的兼容性问题。

问题:GVM工具默认从官网下载安装包安装,因为众所周知的原因,国内网络无法访问到golang官网,因此下载安装包会失败。包括安装gvm工具本身也是一样。

如何安装gvm

首先将gvm安装脚本内容拷贝到本地,使用可以访问外网的浏览器打开:

https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer

然后执行以下命令安装:

yum install bison
# 设置安装的代码源仓库,默认是github,在国内大概率拉不下来,因此需要拉到国内
export SRC_REPO=https://gitee.com/voidint/gvm.git
# 安装
./gvm_install.sh

用法:

# 查看所有版本
gvm list
# 使用指定版本
gvm use go1.18
# 设置默认的版本
gvm use go1.18 --default

如何基于本地包安装go

gvm默认使用golang官网来下载二进制,但是国内无法访问golang官网,所以是无法安装成功。gvm提供了通过离线二进制包安装的能力,可以在国内golang网站下载好二进制包,放到~/.gvm/archive/目录下,然后执行命令安装:

gvm install go1.21.5 --binary

这样就解决了网络不通的问题。

注意:版本号必须要匹配

NVM(Node Version Manager)是一个用于管理和切换不同版本Node.js的命令行工具。它允许用户在同一台计算机上轻松安装、使用和管理多个Node.js版本,从而确保开发者可以在不同项目中使用不同版本的Node.js,避免版本冲突带来的问题。

NVM的主要功能和特点包括:

  1. 安装和卸载Node.js版本:NVM允许用户快速安装和卸载Node.js的不同版本,以便在不同项目中使用。
  2. 切换Node.js版本:NVM可以轻松切换当前正在使用的Node.js版本,这对于在不同项目中使用不同Node.js版本的开发者来说非常有用。
  3. 设置默认Node.js版本:NVM允许用户设置一个默认的Node.js版本,以便在新的终端会话中自动使用。
  4. 查看已安装的Node.js版本:NVM可以列出已安装的所有Node.js版本,方便用户查看和管理。
  5. 支持自定义安装路径:NVM支持自定义Node.js的安装路径,可以根据用户需求进行配置。
  6. 无需管理员权限:NVM不需要管理员权限,用户可以在自己的用户空间内安装和管理Node.js版本。

通过NVM,开发者可以更方便地在不同版本的Node.js之间进行切换,从而提高开发效率和降低潜在的兼容性问题。以下是NVM的一些常用命令:

安装NVM

注意:需要外网环境,可以先到github.com上把Install.sh脚本内容拷贝到本地文件中

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

或者:

curl -o- https://file.maqian.xin/scripts/nvm_install.sh | bash

安装版本

nvm install 14.17.0

切换版本

nvm use 14.17.0

设置默认版本

nvm alias default 14.17.0

查看已安装的所有版本

nvm ls

查看可用的Node.js版本

nvm ls-remote

卸载

nvm uninstall 14.17.0

import time


# 进度条打印函数
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='=', print_end="\r"):
    """
    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : positive number of decimals in percent complete (Int)
        length      - Optional  : character length of bar (Int)
        fill        - Optional  : bar fill character (Str)
        print_end    - Optional  : end character (e.g. "\r", "\r\n") (Str)
    """
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filled_length = int(length * iteration // total)
    bar = fill * filled_length + ">" + '-' * (length - filled_length)
    print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)
    # Print New Line on Complete
    if iteration == total:
        print()


if __name__ == '__main__':
    for i in range(1, 11):
        print_progress_bar(i, 10, "Progress: ", )
        time.sleep(0.2)

一、文件流

C++的IO类中定义了三个文件读写流fstream、ifstream以及ofstream,它们都继承于相同的父类istream,通过不同的实现以实现不同的文件流操作。

三者的区别为:

  • ifstream:从文件读取数据
  • ofstream:从文件写入数据
  • fstream:既可以读数据、又可以写数据

1.1 IO接口和读写模式

三个文件流实现了以下几个函数接口:

函数名用途
open(s, mode)以mode模式打开文件s
close()关闭文件流
is_open()返回文件是否已经打开
read(buff, size)读入最多size字节数据到buff中
write(buff, size)写入size字节数据到文件中

在使用open的时候,可以只传入文件s,不指定打开模式。如果不指定模式,系统会自动根据文件类型选择默认的打开模式。同时,除了open()的方式打开文件以外,还可以在对象构造的时候打开文件:

ofstream output("/tmp/test.txt", ios::out);

ios::out表示已只读方式打开文件,对应unix c中的O_WRONLY模式。在C++中,有以下读写模式可以选择:

模式说明
ios::in以读方式打开
ios::out以写方式打开
ios::app以追加写方式打开
ios::trunc以截断方式打开文件
ios:binary以二进制方式打开文件
ios::ate打开文件后指针定位到文件尾

这些模式可以单独使用,也可以组合使用,如果需要组合使用,使用逻辑操作符|或起来即可。这里要特别注意的是ios::out模式默认会截断文件,也就是说,ios::outios::out | ios::trunc效果是一样的,都会将文件截断。如果不希望以截断方式打开文件时,则需要设置读写模式为ios::out | ios::app,以这种模式打开文件后,数据会以追加的方式写入到文件。

1.2 读写文件示例

写文件

// 写文件
void write_file() {
  ofstream output;
  // 待写入数据
  string output_data = "HelloWorld\nWelcome To Tencent\n";

  // 打开文件
  output.open("test.txt", ios::out);

  // 写入数据
  output.write(output_data.c_str(), output_data.size());

  // 关闭文件
  output.close();
}

读文件

void read_file() {
  ifstream input;
  char input_data[1024];

  // 打开文件
  input.open("test.txt", ios::in);

  // 读取数据
  input.read(input_data, 1024);

  // 打印读取到的数据
  cout << input_data;

  // 关闭文件
  input.close();
}

打印结果:

1.3 以IO操作符读写数据

三个IO操作类都继承于istream,可以直接使用IO操作符(<<>>)来进行文件读写。

ofstream output("test.txt");
ifstream input("test.txt");
std::string input_data;

output << "HelloWorld\n";
output.close();

input >> input_data;
cout << input_data;
input.close();

return 0;

注意:在使用IO操作符从文件读取数据的时候,数据输入的对象可以是字符串,也可以是对应的数据类型(如int)。如果输入到字符串,默认是读到空白字符的时候就停止了,需要通过循环控制读取后面的数据。

二、流状态

流在执行IO操作的时候,会根据不同的情况产生不同的状态码,如:

状态位说明
strm::badbit流已崩溃
strm::failgitIO已崩溃
strm::eofbit已经读到文件尾
strm::goodbit一切正常,没有异常

我们可以使用已经封装好的函数bad()/fail()/eof()/good()来判断当前IO是否已经达到某种状态,如判断文件是否已经读到结束。同时,我们还可以使用clear()函数来清除当前IO对象的状态位,当流已崩溃(如将流中的字符串对象读到一个int对象上)时,可以手动来清除状态位继续往下读取。

示例,使用eof标志位判断文件是否读完成

ofstream output("test.txt");
ifstream input("test.txt");
std::string input_data;

output << "Hello World\n";
output.close();

while (!input.eof()) {  // 没有读到文件尾就一直读取
  input_data.clear();

  input >> input_data;  // 读到空白符就停止
  // 过滤掉空行
  if (input_data == "") continue;

  cout << input_data << endl;
}
input.close();

因为使用IO操作符从流中读取数据默认遇到空行就停止,所以Hello World\n字符串需要读三次才能读完,第一次是Hello,第二次是World,第三次是换行后的空行,所以,最后的输出结果是:

三、getline一次读一行

流对象中内置了getline()成员方法可以一次读一行数据到字符串数组中,需要传入一个字符数组和最大长度size:

  ifstream input("test.txt", ios::in);
  char buff[1024];
  while (!input.eof()) {
    input.getline(buff, 1024);
    cout << buff << endl;
  }

这个成员方法可以实现一次读一行的操作,但是只支持C风格的字符串传入,无法支持C++中的string类型。这里可以使用系统库的getline()函数来实现,系统库中有一个getline()函数可以直接从IO流中读取一行数据到string中:

ifstream input("test.txt", ios::in);
std::string input_data;
while (!input.eof()) {
  getline(input, input_data);
  cout << input_data << endl;
}

上面两个实现都可以正常按行输出文件内容:

四、fstream和文件指针

当使用fstream流时,对象既可以写入文件,也可以读取文件。此时读写文件指针是公共的,写入文件会导致指针后移,再读就会从当前位置重新读取(读到垃圾数据)。为了避免这个问题,可以使用seekp和seekg来移动文件指针:

fstream file("test.txt", ios::in | ios::out);
string input_data;

file << "HelloWorld\n" << unitbuf;

// 移动读指针到文件开始处
file.seekg(0, ios::beg);
file >> input_data;
cout << input_data

五、错误处理

5.1 读取文件失败了,应该如何判断

执行完open操作后,直接判断对象是否为真即可确定打开文件是否失败。出现错误后,可以使用errno输出错误原因。

例如,打开一个不存在的文件:

ifstream input("aabbcc.txt", ios::in);
if (!input) {
  cout << "open fail error: " << strerror(errno) << endl;
}

输出结果会打印出文件不存在的错误:

使用形式:

  • using指令(using directive)的使用形式为using namespace std
  • using声明(using declaration)的使用形式为using std::cout

作用差别:

  • using指令的作用是让std内的所有声明在当前文件作用域内都可用,我们可以使用域作用符::直接访问std命名空间内的所有定义。
  • 而using声明只是在当前作用域内声明std命名空间内的一个函数,只有被声明的函数才能在当前作用域内使用。

使用建议:using声明的作用域更小,可以更有效地缩小使用作用域,避免函数冲突问题,更推荐使用。