当前位置: 首页 > news >正文

GitHub自动化利器:Probot框架实战指南

引言

在当今快节奏的软件开发世界中,自动化已成为提高生产力和保证代码质量的关键要素。GitHub作为全球最大的代码托管平台,其丰富的API生态系统为自动化提供了无限可能。Probot作为一个基于Node.js的开源框架,专门用于构建GitHub应用程序,正在改变开发者与GitHub交互的方式。

本文将深入探讨Probot框架的核心概念、工作原理、实战应用以及最佳实践。通过阅读本文,您将学习到:

  • Probot框架的核心架构和工作原理
  • 如何搭建和配置Probot开发环境
  • Webhook机制与GitHub事件的深度解析
  • 构建各种自动化工作流的实战技巧
  • 高级特性与性能优化策略
  • 生产环境部署与监控方案
  • Probot生态系统的扩展与社区资源

无论您是想要简化团队工作流程的Tech Lead,还是希望为开源项目添加自动化功能的维护者,本文都将为您提供全面的指导和技术细节。

大纲

  1. ​Probot框架概述​
    • 1.1 什么是Probot
    • 1.2 Probot的核心特性
    • 1.3 Probot与其它GitHub自动化工具的对比
  2. ​环境搭建与项目初始化​
    • 2.1 环境要求与前置条件
    • 2.2 使用create-probot-app创建项目
    • 2.3 项目结构解析
  3. ​核心概念深度解析​
    • 3.1 GitHub Webhook机制
    • 3.2 Node.js与Express在Probot中的角色
    • 3.3 GitHub API认证与权限管理
  4. ​实战:构建你的第一个Probot应用​
    • 4.1 基础事件处理
    • 4.2 自动化代码审查实现
    • 4.3 Issue自动管理机器人
  5. ​高级特性与最佳实践​
    • 5.1 状态管理与持久化
    • 5.2 错误处理与日志记录
    • 5.3 测试策略:单元测试与集成测试
  6. ​部署与运维​
    • 6.1 本地开发与调试技巧
    • 6.2 无服务器部署(AWS Lambda)
    • 6.3 监控与性能优化
  7. ​Probot生态系统与社区​
    • 7.1 官方与社区插件
    • 7.2 优秀案例研究
    • 7.3 贡献与参与社区
  8. ​未来展望与总结​
    • 8.1 Probot的发展方向
    • 8.2 自动化工作流的未来趋势
    • 8.3 总结与资源推荐

1. Probot框架概述

1.1 什么是Probot

Probot是一个基于Node.js的开源框架,专门用于构建GitHub应用程序(GitHub Apps)。它旨在简化接收和处理GitHub webhook事件的流程,让开发者能够专注于业务逻辑而非基础设施代码。Probot提供了一套强大的工具和抽象,使得创建响应GitHub事件的自动化机器人变得异常简单。

与传统的OAuth应用相比,Probot应用具有更高的安全性和更细粒度的权限控制。每个Probot应用都作为一个独立的GitHub App存在,可以安装在一个或多个仓库中,并只请求它实际需要的权限。

// 一个最简单的Probot应用示例
module.exports = (app) => {app.on('issues.opened', async (context) => {// 当有新issue创建时自动添加评论const issueComment = context.issue({body: '感谢您提交issue!我们会尽快查看。'});await context.github.issues.createComment(issueComment);});
};

1.2 Probot的核心特性

Probot框架具有几个关键特性,使其成为GitHub自动化的理想选择:

  • ​简化的事件处理​​:Probot提供了直观的API来处理GitHub webhook事件,开发者只需关注特定事件的处理逻辑。
  • ​内置认证​​:框架自动处理GitHub API认证,无需手动管理token或实现OAuth流程。
  • ​TypeScript支持​​:Probot完全支持TypeScript,提供了完整的类型定义,增强了开发体验和代码可靠性。
  • ​测试工具​​:提供了丰富的测试工具,使得编写单元测试和集成测试变得简单。
  • ​扩展生态系统​​:拥有丰富的插件生态系统,可以轻松扩展功能。

1.3 Probot与其它GitHub自动化工具的对比

虽然GitHub提供了多种自动化方式(如GitHub Actions、Webhook直接集成等),但Probot在某些场景下具有独特优势。

与GitHub Actions相比,Probot提供了更细粒度的事件控制和更复杂的状态管理能力。而与直接使用Webhook相比,Probot大大降低了开发复杂度,提供了开箱即用的认证、日志和错误处理机制。

2. 环境搭建与项目初始化

2.1 环境要求与前置条件

在开始Probot开发之前,需要确保系统满足以下要求:

  • Node.js 18.0.0或更高版本
  • npm(通常随Node.js一起安装)或yarn
  • Git
  • GitHub账户

可以通过以下命令检查Node.js版本:

node -v

如果未安装Node.js,需要从Node.js官网下载并安装最新版本。

2.2 使用create-probot-app创建项目

Probot提供了便捷的命令行工具create-probot-app来快速初始化项目:

# 使用npx直接创建Probot应用
npx create-probot-app my-first-app# 按照提示输入应用信息
# 应用名称: my-first-app
# 描述: My first Probot app
# 作者: Your Name
# 模板: basic-js (或basic-ts用于TypeScript)

创建完成后,进入项目目录并查看结构:

cd my-first-app
ls -la

2.3 项目结构解析

一个典型的Probot项目包含以下文件和目录:

my-first-app/
├── src/
│   └── index.js          # 主应用文件
├── test/
│   └── index.test.js     # 测试文件
├── .env                  # 环境变量
├── app.yml               # GitHub App配置
├── package.json          # 项目依赖和脚本
└── README.md             # 项目说明文档

​package.json​​是项目的核心配置文件,包含了所有依赖和脚本:

{"name": "my-first-app","version": "1.0.0","description": "My first Probot app","author": "Your Name","dependencies": {"probot": "^12.0.0"},"scripts": {"start": "probot run ./src/index.js","test": "jest"},"devDependencies": {"jest": "^27.0.0","smee-client": "^1.0.0"}
}

3. 核心概念深度解析

3.1 GitHub Webhook机制

Webhook是Probot与GitHub交互的核心机制。当GitHub上发生特定事件(如创建issue、提交PR等)时,GitHub会向配置的Webhook URL发送HTTP POST请求。

Probot框架内置了Webhook处理功能,简化了事件处理流程:

GitHubProbot ServerApp LogicPOST Webhook EventVerify Webhook SignatureParse Event PayloadRoute to Event HandlerProcess EventOptional API ResponseGitHubProbot ServerApp Logic

Webhook事件处理代码示例:

module.exports = (app) => {// 处理issue相关事件app.on(['issues.opened', 'issues.edited'], async (context) => {// context.payload包含完整的事件数据const { issue, repository } = context.payload;// 使用context.github进行API调用await context.github.issues.addLabels({owner: repository.owner.login,repo: repository.name,issue_number: issue.number,labels: ['triage']});});// 处理PR相关事件app.on('pull_request.opened', async (context) => {// 自动请求代码审查const { pull_request, repository } = context.payload;await context.github.pulls.requestReviewers({owner: repository.owner.login,repo: repository.name,pull_number: pull_request.number,reviewers: ['team-lead', 'senior-dev']});});
};

3.2 Node.js与Express在Probot中的角色

Probot基于Node.js和Express构建,利用了Node.js的非阻塞I/O模型和Express的Web框架能力。这种组合使得Probot能够高效处理大量并发Webhook请求。

Express中间件在Probot中的应用:

// 自定义中间件示例
const addRequestId = (req, res, next) => {req.id = Date.now() + Math.random().toString(36).substr(2, 5);next();
};module.exports = (app) => {// 注册自定义中间件app.use(addRequestId);// 内置中间件的使用app.on('push', async (context) => {// 这个处理函数本质上是一个特殊的Express路由app.log.debug(`Processing push event with ID: ${context.req.id}`);// 业务逻辑...});
};

3.3 GitHub API认证与权限管理

Probot自动处理GitHub API认证,支持两种主要认证方式:

  1. ​应用认证​​:用于获取应用级别信息
  2. ​安装认证​​:用于在特定仓库执行操作

权限通过在app.yml中配置来管理:

# app.yml示例
name: my-probot-app
description: My awesome Probot app
# 请求的API权限
permissions:issues: writepull_requests: writecontents: read
# 订阅的事件
events:- issues- pull_request- push

API调用示例:

module.exports = (app) => {app.on('issue_comment.created', async (context) => {// 使用认证后的GitHub API客户端const { github, payload } = context;// 创建issue评论await github.issues.createComment({owner: payload.repository.owner.login,repo: payload.repository.name,issue_number: payload.issue.number,body: '感谢您的评论!'});// 读取文件内容const fileContent = await github.repos.getContent({owner: payload.repository.owner.login,repo: payload.repository.name,path: 'README.md'});});
};

4. 实战:构建你的第一个Probot应用

4.1 基础事件处理

让我们构建一个简单的Probot应用,当用户创建新issue时自动欢迎他们:

module.exports = (app) => {app.on('issues.opened', async (context) => {const { issue, repository, sender } = context.payload;// 构建欢迎消息const welcomeMessage = `
嗨 @${sender.login}!感谢您为${repository.name}提交issue。我们的团队会尽快查看您的问题。与此同时,请确保您已经:1. 查看了我们的文档
2. 搜索了已有的issue,避免重复祝您有美好的一天!✨`;// 创建评论return context.github.issues.createComment({owner: repository.owner.login,repo: repository.name,issue_number: issue.number,body: welcomeMessage});});
};

4.2 自动化代码审查实现

实现一个基本的自动化代码审查功能,当PR创建时自动运行ESLint检查:

const { ESLint } = require('eslint');module.exports = (app) => {app.on('pull_request.opened', async (context) => {const { pull_request, repository } = context.payload;const { github } = context;// 获取PR中的文件变更const { data: files } = await github.pulls.listFiles({owner: repository.owner.login,repo: repository.name,pull_number: pull_request.number});// 过滤出JavaScript文件const jsFiles = files.filter(file => file.filename.endsWith('.js') || file.filename.endsWith('.jsx'));if (jsFiles.length === 0) {return; // 没有JS文件,退出}// 初始化ESLintconst eslint = new ESLint();let lintResults = [];// 检查每个JS文件for (const file of jsFiles) {// 这里简化了代码获取逻辑,实际中需要获取文件内容const results = await eslint.lintText('模拟的JS代码');lintResults = lintResults.concat(results);}// 生成审查报告const errors = lintResults.reduce((sum, result) => sum + result.errorCount, 0);const warnings = lintResults.reduce((sum, result) => sum + result.warningCount, 0);// 添加审查评论await github.issues.createComment({owner: repository.owner.login,repo: repository.name,issue_number: pull_request.number,body: `## ESLint检查结果发现${errors}个错误和${warnings}个警告。<details>
<summary>查看详细报告</summary>\`\`\`
${JSON.stringify(lintResults, null, 2)}
\`\`\`</details>`});});
};

4.3 Issue自动管理机器人

创建一个智能的issue管理机器人,自动分类和标记issue:

module.exports = (app) => {// 关键词到标签的映射const keywordToLabel = {'bug': 'bug','error': 'bug','fix': 'bug','feature': 'enhancement','improvement': 'enhancement','docs': 'documentation','documentation': 'documentation'};app.on(['issues.opened', 'issues.edited'], async (context) => {const { issue, repository } = context.payload;const { title, body } = issue;const content = (title + ' ' + body).toLowerCase();// 识别关键词并确定标签const labelsToAdd = new Set();for (const [keyword, label] of Object.entries(keywordToLabel)) {if (content.includes(keyword)) {labelsToAdd.add(label);}}// 如果没有识别到标签,添加默认标签if (labelsToAdd.size === 0) {labelsToAdd.add('needs-triage');}// 添加标签await context.github.issues.addLabels({owner: repository.owner.login,repo: repository.name,issue_number: issue.number,labels: Array.from(labelsToAdd)});// 如果是bug,自动分配给核心团队if (labelsToAdd.has('bug')) {await context.github.issues.addAssignees({owner: repository.owner.login,repo: repository.name,issue_number: issue.number,assignees: ['core-team']});}});
};

5. 高级特性与最佳实践

5.1 状态管理与持久化

对于复杂的Probot应用,通常需要持久化状态数据。虽然Probot本身不提供内置的持久化解决方案,但可以轻松集成各种数据库:

const { Sequelize, DataTypes } = require('sequelize');// 初始化数据库连接
const sequelize = new Sequelize('database', 'username', 'password', {host: 'localhost',dialect: 'sqlite',storage: './database.sqlite'
});// 定义数据模型
const Issue = sequelize.define('Issue', {id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true},githubId: {type: DataTypes.INTEGER,unique: true},title: DataTypes.STRING,status: DataTypes.STRING,triagedAt: DataTypes.DATE
});module.exports = (app) => {// 在应用启动时初始化数据库app.on('app.start', async () => {await sequelize.sync();app.log.info('Database synchronized');});app.on('issues.opened', async (context) => {const { issue } = context.payload;// 保存issue到数据库await Issue.create({githubId: issue.id,title: issue.title,status: 'new',triagedAt: new Date()});app.log.debug(`Issue ${issue.id} saved to database`);});
};

5.2 错误处理与日志记录

健壮的错误处理和日志记录对生产环境应用至关重要:

module.exports = (app) => {// 全局错误处理中间件app.on('error', (error) => {app.log.error('Unhandled error occurred:', error);});app.on('issues.opened', async (context) => {try {const { issue, repository } = context.payload;app.log.debug(`Processing new issue #${issue.number} in ${repository.name}`);// 模拟可能失败的操作if (issue.title.includes('fail')) {throw new Error('Simulated failure');}// 业务逻辑...await context.github.issues.createComment({owner: repository.owner.login,repo: repository.name,issue_number: issue.number,body: '感谢您的提交!'});app.log.info(`Successfully processed issue #${issue.number}`);} catch (error) {// 记录错误并尝试优雅恢复app.log.error({err: error,payload: context.payload}, `Failed to process issue event`);// 可以在这里添加错误通知逻辑(如发送到Slack)}});
};

5.3 测试策略:单元测试与集成测试

Probot提供了优秀的测试支持,使得编写测试变得简单:

// test/my-app.test.js
const { Probot } = require('probot');
const app = require('../src/index');describe('My Probot App', () => {let probot;beforeEach(() => {probot = new Probot();// 加载应用probot.load(app);});test('adds labels to new issues', async () => {// 模拟GitHub webhook事件const mock = nock('https://api.github.com')// 预期会调用添加标签API.post('/repos/test/repo/issues/1/labels', (body) => {return body.labels.includes('triage');}).reply(200);// 发送模拟事件await probot.receive({name: 'issues.opened',payload: {issue: { number: 1 },repository: { owner: { login: 'test' }, name: 'repo' }}});// 验证API被调用expect(mock.pendingMocks()).toEqual([]);});
});

测试配置文件示例(jest.config.js):

module.exports = {testEnvironment: 'node',setupFilesAfterEnv: ['./test/setup.js'],coverageDirectory: 'coverage',collectCoverageFrom: ['src/**/*.js','!src/index.js']
};

6. 部署与运维

6.1 本地开发与调试技巧

本地开发Probot应用时,可以使用Smee.io转发Webhook事件:

# 启动Smee客户端
npx smee -u https://smee.io/your-unique-url -t http://localhost:3000# 在另一个终端启动Probot应用
npm start

调试配置(.vscode/launch.json):

{"version": "0.2.0","configurations": [{"type": "node","request": "launch","name": "Debug Probot","skipFiles": ["<node_internals>/**"],"program": "${workspaceFolder}/node_modules/.bin/probot","args": ["run", "${workspaceFolder}/src/index.js"],"env": {"WEBHOOK_PROXY_URL": "https://smee.io/your-unique-url"}}]
}

6.2 无服务器部署(AWS Lambda)

Probot应用可以轻松部署到无服务器平台如AWS Lambda:

// lambda.js
const { createLambdaFunction } = require('@probot/serverless-lambda');
const app = require('./src/index');module.exports = createLambdaFunction(app, {probot: {// 从环境变量读取配置appId: process.env.APP_ID,privateKey: process.env.PRIVATE_KEY,secret: process.env.WEBHOOK_SECRET}
});

Serverless框架配置(serverless.yml):

service: my-probot-appprovider:name: awsruntime: nodejs18.xenvironment:APP_ID: ${env:APP_ID}PRIVATE_KEY: ${env:PRIVATE_KEY}WEBHOOK_SECRET: ${env:WEBHOOK_SECRET}functions:webhook:handler: lambda.handlerevents:- http:path: /method: postplugins:- serverless-dotenv-plugin

部署脚本:

# 设置环境变量
export APP_ID=12345
export PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
export WEBHOOK_SECRET=your-secret# 部署到AWS Lambda
npx serverless deploy

6.3 监控与性能优化

生产环境中的Probot应用需要适当的监控和性能优化:

// 添加性能监控
const { monitor } = require('@probot/monitor');module.exports = (app) => {// 启用监控monitor(app, {enabled: process.env.NODE_ENV === 'production',metrics: true,healthCheck: true});// 添加自定义指标app.on('issues.opened', async (context) => {const startTime = Date.now();// 业务逻辑...// 记录处理时间const duration = Date.now() - startTime;app.metrics.histogram('issue_processing_time').observe(duration);});
};

性能优化策略:

  1. ​批量处理API调用​​:减少对GitHub API的请求次数
  2. ​使用缓存​​:缓存不经常变动的数据
  3. ​异步处理​​:对于耗时操作,使用队列异步处理
  4. ​限制并发​​:控制同时处理的事件数量

7. Probot生态系统与社区

7.1 官方与社区插件

Probot拥有丰富的插件生态系统,以下是一些常用插件:

  • ​probot-scheduler​​:用于定期触发事件,例如定期清理旧的GitHub问题。
  • ​probot-metadata​​:用于在GitHub问题中存储和检索元数据。
  • ​probot-commands​​:为应用添加斜杠命令支持
  • ​probot-config​​:基于仓库的配置文件管理

使用插件示例:

const scheduler = require('probot-scheduler');
const metadata = require('probot-metadata');module.exports = (app) => {// 启用调度器scheduler(app, {delay: !process.env.DISABLE_DELAY,interval: 24 * 60 * 60 * 1000 // 每天运行一次});// 定期清理任务app.on('schedule.repository', async (context) => {const { owner, name } = context.repo();// 获取30天前的issuesconst cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);const { data: issues } = await context.github.issues.listForRepo({owner, repo: name,state: 'open',since: cutoffDate.toISOString()});// 关闭过期issuesfor (const issue of issues) {await context.github.issues.update({owner, repo: name,issue_number: issue.number,state: 'closed'});}});// 使用元数据存储app.on('issues.opened', async (context) => {// 存储元数据await metadata(context).set('firstSeen', new Date().toISOString());// 读取元数据const firstSeen = await metadata(context).get('firstSeen');app.log.debug(`Issue first seen at: ${firstSeen}`);});
};

7.2 优秀案例研究

许多知名组织和项目使用Probot自动化他们的工作流程:

  1. ​Welcome Bot​​:当新用户创建新问题、新的提取请求或首次合并时,该机器人会欢迎新用户。
  2. ​Stale Bot​​:关闭了过时的问题和提取请求,帮助维护大型仓库。
  3. ​WIP Bot​​:通过在标题中添加WIP来防止合并请求请求。
  4. ​All Contributors​​:自动生成贡献者列表,展示项目中的所有贡献者。

7.3 贡献与参与社区

Probot是开源项目,欢迎社区贡献:

  • ​GitHub仓库​​:probot/probot
  • ​问题报告​​:使用GitHub Issues报告bug或提出功能请求
  • ​文档改进​​:帮助改进文档和教程
  • ​插件开发​​:创建和分享自己的Probot插件

社区资源:

  • ​官方文档​​:probot.github.io
  • ​Slack频道​​:加入Probot的Slack社区进行实时讨论
  • ​Stack Overflow​​:使用probot标签提问

8. 未来展望与总结

8.1 Probot的发展方向

随着GitHub平台的不断演进,Probot框架也在持续发展。未来的方向可能包括:

  1. ​更好的TypeScript支持​​:提供更完整的类型定义和开发体验
  2. ​增强的测试工具​​:更强大的模拟和测试实用程序
  3. ​扩展的无服务器支持​​:更多部署平台的官方支持
  4. ​性能优化​​:改进事件处理性能和资源利用率

8.2 自动化工作流的未来趋势

GitHub自动化领域正在快速发展,几个趋势值得关注:

  • ​AI驱动的自动化​​:使用机器学习智能分类issue和PR
  • ​跨平台集成​​:与更多开发工具和服务集成
  • ​可视化工作流构建器​​:低代码方式创建自动化工作流
  • ​增强的安全特性​​:更细粒度的权限控制和审计功能

8.3 总结与资源推荐

Probot框架为GitHub自动化提供了强大而灵活的基础。通过本文,您应该对Probot的核心概念、实战应用和高级特性有了全面了解。

​推荐学习资源​​:

  1. Probot官方文档 - 官方指南和API参考
  2. GitHub Apps文档 - 了解GitHub Apps底层机制
  3. GitHub API文档 - 完整的API参考

​下一步行动建议​​:

  1. 从简单应用开始,逐步增加复杂度
  2. 参与社区,学习其他开发者的实践经验
  3. 关注Probot和GitHub平台的更新
  4. 考虑将自动化应用到自己的项目中

自动化是现代软件开发的核心竞争力之一,掌握Probot等工具将极大提升您和团队的生产力。开始构建您的第一个Probot应用,探索GitHub自动化的无限可能吧!


文章转载自:

http://XqhxXXpS.bccLs.cn
http://wWRyP4FO.bccLs.cn
http://rXmocBZv.bccLs.cn
http://noDYFEFd.bccLs.cn
http://Pu7MRDFu.bccLs.cn
http://IYM5mCNn.bccLs.cn
http://DQ7uiV6j.bccLs.cn
http://jMhYmw8N.bccLs.cn
http://gUax49ex.bccLs.cn
http://uUjjMkMR.bccLs.cn
http://UOCkLlyj.bccLs.cn
http://lNXpWYA6.bccLs.cn
http://Ds9XlIgI.bccLs.cn
http://HVmOa4Q5.bccLs.cn
http://YS6rpPQc.bccLs.cn
http://S1DaoHNI.bccLs.cn
http://MYDVQI82.bccLs.cn
http://xhsPkXxQ.bccLs.cn
http://NSKJf7oK.bccLs.cn
http://pgsIvwRy.bccLs.cn
http://WU6csOI0.bccLs.cn
http://Yp6jCobb.bccLs.cn
http://YCIQG60b.bccLs.cn
http://HOENZNQs.bccLs.cn
http://Sz98KZ5b.bccLs.cn
http://iQkcFCHL.bccLs.cn
http://rg4k3zHs.bccLs.cn
http://eVzZzrnP.bccLs.cn
http://tNd2UaNs.bccLs.cn
http://GsHJoWI9.bccLs.cn
http://www.dtcms.com/a/372149.html

相关文章:

  • 一款没有任何限制的免费远程手机控制手机的软件简介
  • 企云网多应用授权系统源码 正版查询系统源码
  • Windows netstat 命令使用说明
  • 软件工程:DO-178中的适航要求核心要素
  • Caffeine Count-Min Sketch TinyLFU实现:FrequencySketch
  • 【系统分析师】第7章-基础知识:软件工程(核心总结)
  • 【拍摄学习记录】00-总结记录
  • 探索 CSS 过渡:打造流畅网页交互体验
  • 大语言模型(LLM)的基本概念
  • unsloth FastLanguageModel类主要函数详解,具体作用和参数
  • HTTPS协议——对于HTTP的协议的加密
  • Qwen2.5-VL翻译
  • 碳纤维和短切碳纤维(中)
  • unsloth 笔记: training的时候进行evaluation
  • 【linux kernel 常用数据结构和设计模式】【数据结构 1】【如何表达数据之间的一对一、一对多、多对多关系】
  • 【软件架构设计(19)】软件架构评估二:软件架构分析方法分类、质量属性场景、软件评估方法发展历程
  • 在OpenHarmony上适配图形显示【1】——确认drm是否正常
  • 四大金刚之计算机组成原理
  • 第 15 篇:PCA与降维——如何在信息爆炸的时代,抓住“主要矛盾”?
  • 《沈南鹏传 - 做最擅长的事》(中篇)读书笔记
  • 还在重启应用改 Topic?Spring Boot 动态 Kafka 消费的“终极形态”
  • 纸飞机飞行漂流瓶小游戏抖音快手微信小程序看广告流量主开源
  • 《沈南鹏传 - 做最擅长的事》(下篇)读书笔记
  • 网易UU远程,免费电脑远程控制软件
  • Prometheus 存储学习
  • 八.迪杰斯特拉(Dijkstra)算法
  • 大模型术语
  • Python入门教程之关系运算符
  • 9. Mono项目与Unity的关系
  • 【C#】 资源共享和实例管理:静态类,Lazy<T>单例模式,IOC容器Singleton我们该如何选