分类 分享 下的文章

今天帮忙部署一下实验室接的一个项目的后端
出了几个比较大的问题,好在最后还是成功的完成了工作

第一个就是公司的服务器不能连外部的局域网,所以这就导致了不能使用git从仓库来拉取项目,只能是手动的上传。
他们那边的运维工具是堡垒机,说实话我还是第一次见,觉得很新奇。
和那种直接将服务器ip和账号密码给你的模式不同,他们是有一个统一的终端来保存这些服务器的信息,当选择特定的要运维的服务器的时候可以选择本地或者跳板机的ssh应用连接到服务器。
一开始因为本地的xshell没有配xfrp,然后不能上传文件,在这里卡了很久。
事实上一开始我都不知道服务器不能连外网。
因为不能连外网,所以不能用git来拉取,还有就是不能使用go mod tidy。
说实话这两步恶心了我很久。
对于前一个其实一开始就想过todesk把文件传过去,然后用xfrp传过去。
但是那边用windows应用连接不能访问本地的文件,后面想这个windows应用连接应该就是用堡垒机,或者说跳板机连接。
之后换本地连接,但是本地的xshell没有配xfrp,一开始想的是用rz -E,但是没有安装lrzsz
而且因为是内网所以安装不了。
后来就是我自己尝试安装xfrp,但是发现要使用的话安装7和5都让用(注xshell版本是5),一直提示要更新,更新到8之后确实可以用,但是在xshell界面跳转到xfrp界面时会发现报错,并且连接就掉了(指xfrp)xshell还健在。
其实在安装xfrp的过程中还把机器差点搞崩溃了
幸亏后面冒死重启没事
最后无奈还是求助了那边的运维,后面下午的时候他们装了一个版本为4的xshell和xfrp终于可以用了

下午的时候将go的项目文件上传
上传之后发现了一个大问题就是因为得装依赖,用go mod tidy
这个过程需要联网,所以行不通
后面想到要不就在本地编译一下再上传
但是后来发现由于之前项目负责人留下的技术债,导致在编译的时候有一个文件路径的变量是使用的当前编译环境的路径并且写死。
后面发现修改起来很困难,一度想放弃。
之后没办法在5点多的时候和那边的运维反映,然后他们说要连外网得申请,
找谁申请?谁负责找谁。 ……
后来去找了另外一个公司那边的人,这次干脆说服务器不允许连外网,让我自己想办法解决。

后面想过直接将本地gopath下面的依赖打包,然后传到服务器的gopath,但是还是会强制的使用go download
之后了解了一下知道了有 go mod vendor这一条指令,可以将项目用到的依赖打包
之后只要将vendor文件夹放到服务器项目的根目录下就可以了
不过后面还是出问题了
因为go mod和go sum和我开发的环境对不上,所以少了一些对应版本的依赖
后来发现是因为一开始我为了将项目拖进去,使用的是gitee上下载的zip包,不是本地在开发的
所以可能会有些出路。
后来我想过将go.mod和go.sum直接替换,但是失败了,也尝试过将部分依赖的版本号,手动修改到和开发环境一致,但是也失败了。

最后没办法就在下的master的zip包里运行了一次go mod vendor,然后将这里面的vendor包传到服务器。
最后是成功运行了,但是有报错,后面问了一下学长,主要是sonic包导入有问题。
这个sonic包是字节跳动的一个包,说是有魔法,正常用download下,下不干净,必须要用go get。
其实到这里的时候我的耐心快要没了。
不过最后突然灵光一现,将本地开发环境和vendor文件夹一块打包成zip
传到服务器,然后解压
最后成了。
我觉得成的原因主要就是这最后一次是整体传上去而不是分开的。

后面的任务就简单了,调试一下,配一下日志。我的工作结束了。
之后前端unity的部署,还有数据库方面的检查就是另外两位学长的工作了。
一下子就轻松了。

这次的部署任务还是很开心的,总体来说。因为对于我来说,这也是第一次给企业部署项目,多少也有点激动。也学到了挺多经验的。而且我已经很久没有做过运维性质的活了,真的感觉挺不错。当然这个不错是建立在我成功,并且不用返工的前提下,hhh。

Git 会同时考虑项目根目录和各级子目录中的 .gitignore 文件,它们会共同作用影响 Git 的忽略行为。

详细解释多级 .gitignore 文件的工作机制:

多级 .gitignore 文件的工作规则

  1. 级联生效原则

    • Git 会从文件所在目录开始向上查找所有 .gitignore 文件,直到仓库根目录
    • 每个 .gitignore 文件中的规则只作用于该文件所在目录及其子目录
  2. 规则叠加方式

    • 离文件最近的 .gitignore 文件的规则优先级最高
    • 如果不同层级的规则冲突,内层(更靠近文件的)规则会覆盖外层规则
  3. 具体案例

    /project-root/
    ├── .gitignore          # 根目录规则
    ├── /backend/
    │   ├── .gitignore      # 子目录规则
    │   └── tmp/            # 受两个.gitignore影响

实际影响示例

  1. 如果 /backend/.gitignore 包含

    # 会覆盖根目录的忽略规则
    !tmp/important.log

    即使根目录 .gitignore/backend/tmp/,这个文件仍会被跟踪

  2. 如果 /backend/.gitignore 包含

    tmp/

    会强化忽略效果,即使根目录没有忽略这个目录

最佳实践建议

  1. 清晰的规则分层

    • 在根目录放全局忽略规则(如 node_modules/, .DS_Store
    • 在子目录放特定于该目录的忽略规则
  2. 调试方法

    # 查看最终生效的忽略规则
    git check-ignore -v path/to/file
    
    # 查看所有参与忽略的.gitignore文件
    git ls-files --others --ignored --exclude-standard
  3. 常见问题处理

    • 如果子目录 .gitignore 导致意外行为,可以:

      1. 使用 ! 来取消特定忽略
      2. 合并规则到更高级别的 .gitignore
      3. git add -f 强制添加被忽略的文件
  4. 特别注意

    • 已经纳入版本控制的文件不会被 .gitignore 忽略
    • 要停止跟踪这类文件需要先执行 git rm --cached

什么是异步操作?

异步操作(Asynchronous Operation)是指程序在执行某个操作时,不需要等待该操作完成就能继续执行后续代码的编程模式。与同步操作(必须等待操作完成才能继续)不同,异步操作允许程序在等待某些耗时操作(如I/O、网络请求等)完成的同时继续执行其他任务。

异步操作的核心特点

  1. 非阻塞:主线程不会被阻塞,可以继续执行其他任务
  2. 延迟处理:操作结果将在未来某个时间点可用
  3. 回调机制:通常通过回调函数、Promise或async/await来处理结果
  4. 提高效率:特别适合I/O密集型操作,能更好地利用系统资源

同步 vs 异步

同步操作示例(阻塞式)

// 同步读取文件(Node.js)
const fs = require('fs');
const data = fs.readFileSync('file.txt'); // 程序会停在这里直到文件读取完成
console.log(data);
console.log('这行代码会在文件读取完成后执行');

异步操作示例(非阻塞式)

// 异步读取文件(Node.js)
const fs = require('fs');
fs.readFile('file.txt', (err, data) => {
    // 这个回调函数会在文件读取完成后执行
    if (err) throw err;
    console.log(data);
});
console.log('这行代码会立即执行,不必等待文件读取完成');

异步操作的常见场景

  1. 网络请求

    fetch('<https://api.example.com/data>')
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(error => console.error('Error:', error));
    
    console.log('这个日志会立即显示,不必等待请求完成');
  2. 定时操作

    setTimeout(() => {
      console.log('这条消息将在2秒后显示');
    }, 2000);
    console.log('这条消息会立即显示');
  3. 数据库操作

    // 伪代码示例
    database.query('SELECT * FROM users', (err, results) => {
      if (err) throw err;
      console.log(results);
    });
    console.log('查询已发起,继续执行其他代码');
  4. 用户交互

    document.getElementById('button').addEventListener('click', () => {
      console.log('按钮被点击了');
    });
    console.log('已设置点击监听器,继续执行其他代码');

异步编程的实现方式

  1. 回调函数(最基础的方式):

    function asyncOperation(callback) {
      setTimeout(() => {
        callback('操作完成');
      }, 1000);
    }
    
    asyncOperation((message) => {
      console.log(message); // 1秒后输出"操作完成"
    });
  2. Promise(ES6引入):

    function asyncOperation() {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve('操作完成');
        }, 1000);
      });
    }
    
    asyncOperation()
      .then(message => console.log(message));
  3. async/await(ES2017引入,语法糖):

    async function doWork() {
      const message = await asyncOperation();
      console.log(message);
    }
    
    doWork();

为什么需要异步操作?

  1. 性能优化:避免阻塞主线程,特别是在单线程环境(如浏览器中的JavaScript)
  2. 用户体验:保持UI响应流畅,避免"卡死"现象
  3. 资源利用:在等待I/O操作(如磁盘读写、网络请求)时,CPU可以处理其他任务
  4. 可扩展性:适合处理高并发场景(如服务器同时处理多个请求)

异步操作的挑战

  1. 代码复杂性:回调嵌套可能导致"回调地狱"
  2. 错误处理:异步错误不像同步错误那样可以通过try-catch直接捕获
  3. 调试困难:执行流程不如同步代码直观
  4. 状态管理:多个异步操作之间的协调可能变得复杂

实际应用示例

多个异步操作并行执行

// 使用Promise.all
const fetchUser = fetch('/user');
const fetchPosts = fetch('/posts');

Promise.all([fetchUser, fetchPosts])
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(([user, posts]) => {
    console.log('用户数据:', user);
    console.log('帖子数据:', posts);
  });

异步操作串行执行

// 使用async/await
async function getData() {
  const user = await fetch('/user').then(r => r.json());
  const posts = await fetch('/posts').then(r => r.json());

  console.log('用户数据:', user);
  console.log('帖子数据:', posts);
}

异步操作是现代编程中不可或缺的概念,特别是在Web开发、Node.js、移动应用开发等领域。理解异步编程是成为高效开发者的关键一步。

直接调用 db.Delete(&article) 仅会删除 articles 表中的记录,而不会自动清除多对多关联关系(即 article_tags 连接表中的记录)。

在 GORM 中,对于多对多关联,如果需要同时删除关联关系,需要使用 Association Mode 来额外处理,例如:

// 删除文章所有 Tag 关联(删除连接表中的记录)
db.Model(&article).Association("Tags").Clear()

或者,可以配置数据库中的外键约束(例如添加 ON DELETE CASCADE),不过这需要在数据库层面配置,是另一种实现方式。

什么是回调函数?

回调函数是一种编程模式,指的是将一个函数作为参数传递给另一个函数,并在特定条件或事件发生时由后者调用前者。回调函数允许异步操作和事件驱动的编程。

简单来说:回调函数是你定义好但由别人(通常是系统或其他函数)在适当时候调用的函数。

回调函数的主要特点

  1. 函数作为参数:回调函数通常作为参数传递给另一个函数
  2. 延迟执行:回调函数不会立即执行,而是在特定条件满足时执行
  3. 异步处理:常用于处理异步操作的结果
  4. 解耦:将调用者与被调用者分离,提高代码灵活性

回调函数的例子

1. JavaScript中的简单回调

// 定义一个回调函数
function greeting(name) {
    console.log('Hello ' + name);
}

// 定义一个接收回调的函数
function processUserInput(callback) {
    const name = prompt('Please enter your name.');
    callback(name); // 在适当时候调用回调
}

// 使用回调
processUserInput(greeting);

2. Node.js中的文件读取(异步I/O)

const fs = require('fs');

// 回调函数
function fileReadCallback(err, data) {
    if (err) {
        console.error('Error reading file:', err);
        return;
    }
    console.log('File content:', data);
}

// 异步读取文件,完成后调用回调
fs.readFile('example.txt', 'utf8', fileReadCallback);

3. 事件监听(浏览器DOM事件)

// 回调函数
function handleClick(event) {
    console.log('Button clicked!', event);
}

// 注册回调
document.getElementById('myButton').addEventListener('click', handleClick);

4. 排序中的比较回调

const numbers = [3, 1, 4, 1, 5, 9, 2, 6];

// 使用回调函数定义排序规则
numbers.sort(function(a, b) {
    return a - b; // 升序排序
});

console.log(numbers); // [1, 1, 2, 3, 4, 5, 6, 9]

回调函数的优缺点

优点

  • 实现异步编程
  • 提高代码复用性
  • 解耦调用者和被调用者
  • 适用于事件驱动编程

缺点

  • 回调地狱(Callback Hell):多层嵌套回调使代码难以阅读和维护
  • 错误处理复杂:需要在每个回调中单独处理错误
  • 控制流不直观

回调地狱示例

// 多层嵌套的回调,难以阅读和维护
doSomething(function(result) {
    doSomethingElse(result, function(newResult) {
        doThirdThing(newResult, function(finalResult) {
            console.log('Got the final result:', finalResult);
        }, failureCallback);
    }, failureCallback);
}, failureCallback);

现代JavaScript通常使用Promise或async/await来解决回调地狱问题。

回调函数是编程中非常重要的概念,特别是在JavaScript和Node.js中广泛使用。理解回调函数是掌握异步编程的基础。