轻狂侠客
开发大学

2023OSPP大作戦、完結です!

by Marlene, 2023-09-17


项目信息

项目名称:AliceBot 插件商店实现

项目产出要求:编写一个插件商店用于用户分享自己编写插件和适配器使用 GitHub Workflow 和 GitHub App 自动获取用户提交的信息并更新商店页面界面美观,易于使用。

时间规划:2023.7-2023.9

项目进度

根据项目计划书内容和项目导师要求,本项目已全部完成,功能内容测试完成,已做相关优化。

插件商店页面部分

要求描述

AliceBot使用了非常灵活且易于使用的插件编写方式,用户只需要编写两个方法即可实现一个功能强大的插件。随着AliceBot用户量的提高,许多用户都编写了自己的插件和适配器,为了方便用户交流,避免“重复造轮子”,急需开发一个商店页面用于用户分享自己编写插件和适配器。

项目思路

插件商店的需求是聚合相关插件、适配器、样例的资源站,提供插件等内容的浏览与链接跳转。

资源信息主要包含名称、类型(插件/适配器/样例)、模组名、描述、作者、仓库链接、标签、是否为官方出品等元信息。这些内容可通过pr合并至文档仓库的plugin.json、bot.json等文件内,用于存储相关信息。

界面可以采用vue3.3+vitepress开发,无需引入额外的库。

由于vitepress并无直接的内容聚合型展示功能,需要进行额外开发。主要思路是在vitepress上开发一个插件商店页面,包含多个相关组件。通过vitepress的markdown内置vue功能开发相关组件及页面功能和样式。通过vitepress提供的数据加载器功能实现插件商店数据的实时获取。

完成情况

商店主要用于展示官方/第三方经审核的插件、适配器、机器人

商店具有以下功能:

  • 展示插件、适配器、机器人信息
  • 寻找相应插件、适配器、机器人信息
  • 复制模块安装命令 ( 默认pip

自适应

完美适配移动端、 PC端等各种尺寸设备

result-1
result-2

统一风格 & 暗色模式

由于AliceBot文档采用Vitepress,因此商店页面整体 UI风格色调与Vitepress风格色调一致,包括暗色模式、动画样式、交互体验等

result-3

精确实时搜索

在搜索栏键入关键词即可搜索

result-4

分页

顶部、底部均有分页组件,分页组件具有自适应宽度,具体显示和省略效果均会随长度和页面宽度做相应适配调整

result-5

信息展示 & 类型选择

可根据插件、适配器、机器人按钮选择对应信息查看。每个模块显示前均经过自动化验证 + 人工验证,均公开分享,代码符合规范。模块除当前页面外,均可在Pypi查询到相应信息

模块具体信息包含以下内容 ( 缺失信息不做显示 ) :

  1. 名称
  2. 描述
  3. 标签
  4. Github地址(可选
  5. 是否为官方模块
  6. 作者
  7. Pypi名称
  8. 模块名称
  9. 实时版本
  10. 开源协议

信息实时更新

为能够信息实时更新,采用非静态页面设计,通过API获取模块信息

API信息来源于另一个仓库,后续或可用于模块提交:https://github.com/MarleneJiang/issue-ops

PR详细链接

https://github.com/AliceBotProject/alicebot/pull/87

Workflow部分

要求描述

用户想要分享自己的插件或者适配器,可以通过github仓库进行开源分享。为了更方便插件审核以及插件商店的插件提交。急需一个基于GitHub Action开发的workflow,用于插件的提交和审核。

完成情况

第一版参考相关workflow进行修改,实现以下功能:

  1. 用户提交issue、issue被评论/validate即可触发流程
  2. 完善的issue标签管理和部分的显式错误提示
  3. 根据是否勾选验证条款进行流程分支
  4. 支持获取插件信息,并添加信息到PR

后与导师进行讨论交流,进一步完善项目需求,并重写第二版。

整体框架和第一版类似。在代码方面进行更细一步的简化和错误拦截提示,在插件验证方面进一步优化。支持bot、plugin、adapter多种插件类型提交,验证流程也有所区别。

具体流程如下:

  1. 用户新建(或者重启、编辑)issue(按照issue template 填写)或者评论/validate触发流程
  2. 输入信息解析及初级验证
  3. python脚本进行更进一步验证
  4. 添加插件信息至pr
  5. 人工审核pr,合并自动关闭issue
  6. 出现错误结束流程,并添加issue标签和评论相关错误原因。

具体的一些验证如下:

  1. 信息解析:

    1. 标题的type解析,如果不符合就报错
    2. 提取name、module_name、pypi_name,如果不符合就报错
    3. pypi_name在pip网站中检查,不存在则报错
  2. 脚本验证:

    1. 安装python环境,安装所有依赖及模块
    2. 测试模块是否能被正常import
    3. 使用subprocess测试模块能否被AliceBot加载,错误则报错
    4. 获取其他元信息,获取不成功则默认替代
  3. 添加信息:

    1. 获取对应json文件并解析
    2. 查询是否有同名模块,若存在则覆盖,并更新时间
    3. 否则添加至最后一行,并附上更新时间
    4. 保存至文件

https://user-images.githubusercontent.com/49270362/262572834-21610bcd-7322-43a1-ae09-2dda81d4f417.png

正常流程

result-6

错误流程

result-7

PR链接

https://github.com/AliceBotProject/alicebot-store/pull/1

遇到的问题及解决方案

如何让用户输入规定的信息

为了让issue能够提供必要的信息,并且workflow能提取issue的信息,必须要对issue做一些规范,例如比如提供一些数据,按某种方式提供。

对此,github提供了ISSUE_TEMPLATE功能。例如编写一个yaml即可支持固定的issue输入。


name: Submit to marketplace
description: Submit your plugin to be automatically validated and published on the marketplace.
title: '[plugin/adapter/bot]: xxx'
body:
  - type: markdown
    attributes:
      value: |
        请在标题中括号内填入类型,例如:[plugin],冒号后附带插件名称,例如:[plugin]: xxx
  - type: input
    attributes:
      label: module-name
      description: 导入时的模块名称
  - type: input
    attributes:
      label: pypi-name
      description: 导入时的模块名称
  - type: markdown
    attributes:
      value: |
        感谢你的贡献,提交issue后将会进行自动审核。

result-8

如何通过issue完成整个流程

首先,需要明确,我们到底需要什么功能:用户通过issue提交信息,然后我们对信息进行初步审核,然后将信息提交pr。
所以,我们的整体思路可以是,用户提交issue,就能触发某个workflow进行执行,然后获取用户输入的信息,对其审查,然后提交pr。

因此,最重要的第一步,如何触发?

workflow提供了很多的触发条件,我们根据项目要求,确定了如下方式:

on:
  issues:
    types:
      - opened
      - reopened
      - edited
  issue_comment:
    types:
      - created
      - edited

如何使整个流程更加交互友好

由于完成整个时间需要花费一些时间,我们需要有明确的消息提示给到开发者用户。例如刚开始处理,可以进行消息提示。

  react-to-new-issue:
    name: New Issue
    if: github.event_name == 'issues' && github.event.action == 'opened'
    runs-on: ubuntu-latest
    outputs:
      comment-id: ${{ steps.comment.outputs.comment-id }}
    steps:
      - name: Add comment
        id: comment
        uses: peter-evans/create-or-update-comment@v2
        with:
          issue-number: ${{ github.event.issue.number }}
          body: |
            感谢你的提交。

            自动验证将在几分钟内开始。

结束时也可进行消息提示,错误也可以显示错误因素。对于消息中大部分内容可以分离到某个文件中,然后通过插槽渲染的方式进行提示。

//validation-failed.md

# 自动检查失败

:x: 验证失败,以下是错误原因

> [!IMPORTANT]
>
> {{ .validation_output }}

请修复问题并在本地确认一切正常后,再次触发验证,方法是在评论中输入 `/validate`。
  validation-failed:
    name: Validation Failed
    runs-on: ubuntu-latest
    needs: validate
    if: always() && needs.validate.outputs.result == 'error'
    steps:
      - uses: actions/checkout@v3.3.0

      - name: Render template
        id: render
        uses: chuhlomin/render-template@v1.6
        with:
          template: .github/workflows/templates/validation-failed.md
          vars: |
            validation_output: ${{ needs.validate.outputs.output }}

      - name: Add comment
        uses: peter-evans/create-or-update-comment@v3.0.2
        with:
          issue-number: ${{ github.event.issue.number }}
          body: ${{ steps.render.outputs.result }}

如何处理复杂的条件控制

整个workflow流程很多,也有很多分支结构,那么如何编写yaml,实现复杂的条件控制功能呢?

github对此提供needs和if。我们可以通过if确定是否执行某一步。通过needs明确需要先通过某个步骤然后才能执行当前步骤。有些情况下,某些步骤可能会跳过,可以通过if: always() &&...来强制执行。

根据以上,我们可以设计出一个具有复杂分支的流程,同时做一个比较好的错误处理流程。

  validation-failed:
    name: Validation Failed
    runs-on: ubuntu-latest
    needs: validate
    if: always() && needs.validate.outputs.result == 'error'
    
   validation-succeeded:
    name: Validation Succeeded
    runs-on: ubuntu-latest
    needs: [validate, create-pr]
    if: always() && needs.validate.outputs.result == 'success' && needs.create-pr.outputs.result == 'success'

如何在workflow中执行py脚本

首先,可以通过run: pdm run .github/actions_scripts/test.py执行对应的脚本。

那么如何在脚本中获取环境变量,如何在yaml中传递环境变量,如何在脚本中输出内容呢?

首先,先通过env传递环境变量。

      - name: Run Python script
        id: run
        env:
          TITLE: ${{ env.ISSUE_TITLE }}
          PYPI_NAME: ${{ steps.set-output.outputs.pypi_name }}
        run: |
          pdm run .github/actions_scripts/parse.py

然后在脚本中,通过os获取环境变量。

import os
title = os.environ["TITLE"]
pypi_name = os.environ["PYPI_NAME"]

我们通过GITHUB_OUTPUT输出内容。

def set_action_outputs(output_pairs: dict[str, str]) -> None:
    """设置 GitHub Action outputs。"""
    if "GITHUB_OUTPUT" in os.environ:
        with Path(os.environ["GITHUB_OUTPUT"]).open(mode="a") as f:
            for key, value in output_pairs.items():
                f.write(f"{key}={value}\n")
    else:
        for key, value in output_pairs.items():
            print(f"::set-output name={key}::{value}")
            
 #...
set_action_outputs(
    {
        "result": "success",
        "output": "",
        "type": parsed.get("type", ""),
        "name": parsed.get("name", ""),
    }
)

最后通过steps.xxx.outputs.xxx获取输出内容。

    outputs:
      module_name: ${{ steps.set-output.outputs.module_name }}
      pypi_name: ${{ steps.set-output.outputs.pypi_name }}
      result: ${{ steps.run.outputs.result }}
      output: ${{ steps.run.outputs.output }}
      type: ${{ steps.run.outputs.type }}
      name: ${{ steps.run.outputs.name }}

如何进行错误处理

我们知道workflow如果在执行之中,如果发生什么问题,必须可以显示出来,什么原因,而不是在workflow直接exit。那么可以使用大量的try和catch去做错误的拦截,在最外面一层进行错误的输出,通过消息进行提示。

if __name__ == "__main__":
    if TYPE != "bot" and (check_module(MODULE_NAME) is False):
        set_action_outputs({"result": "error", "output": "输入的 module_name 存在问题"})
    else:
        try:
            if TYPE != "bot":
                alicebot_test()
        except ValueError as e:
            set_action_outputs({"result": "error", "output": str(e)})
        else:
            get_meta_info()

如何处理插件依赖冲突

在开发调试的过程中,使用pdm进行虚拟python环境管理和包管理方便线上环境与线下环境像统一,避免因环境不同而导致的意外问题。

但是即便如此,也会存在一系列插件所需库的版本和已有库版本之类的存在冲突,那么遇到这种因为执行命令而引起的冲突,如何进行拦截,进行消息提示呢?

首先,错误是发生在workflow的某一个步骤的命令执行阶段。因此,我们可以通过workflow强大的条件判断,进行相应的处理。例如当某个步骤执行结果存在问题则执行这一步,否则执行另一步等等。由于发生错误,会造成所有流程终止,那么没法做相应的错误处理,我们可以通过添加continue-on-error: true来继续处理任务。

      - uses: actions/checkout@v3
      - uses: pdm-project/setup-pdm@v3
        name: Setup PDM
        with:
          python-version: 3.9
          cache: true
      - name: Install dependencies
        run: pdm install --prod
      # 若 type 不为 bot,则安装 pypi_name
      - name: Install pypi_name
        id: install-pypi
        if: needs.parse-issue.outputs.type != 'bot'
        continue-on-error: true
        run: pdm add ${{ needs.parse-issue.outputs.pypi_name }}
      - name: Install Error
        id: install-pypi-error
        if: steps.install-pypi.outcome != 'success' || steps.install-pypi.conclusion != 'success'
        run: | # 输出错误信息
          echo "result=error" >> $GITHUB_OUTPUT
          echo "output=安装pypi包失败,请检查是否存在依赖冲突或者其他情况" >> $GITHUB_OUTPUT
      - name: alicebot test
        id: test
        if: steps.install-pypi.outcome == 'success' && steps.install-pypi.conclusion == 'success'
        env:
          PYPI_NAME: ${{ needs.parse-issue.outputs.pypi_name }}
          MODULE_NAME: ${{ needs.parse-issue.outputs.module_name }}
          TYPE: ${{ needs.parse-issue.outputs.type }}
        run: |
          pdm run .github/actions_scripts/test.py

如何获取插件的信息

通过module_name获取插件的信息是必不可少的关键一步。导师建议使用importlib获取相关的信息 。但通过实际测试发现有一些脚本未install的情况下,完全没办法进行获取信息。这将导致一些初步验证等问题,例如需要预先验证module_name再安装。很显然,这存在悖论。因此,这里采用了一种更为简单的方法——借助https://pypi.org/pypi获取插件的信息

最终,封装出了一个pypi对象。

class PyPi:
    """PyPi 工具类。"""

    PYPI_BASE_URL = "https://pypi.org/pypi"

    def __init__(self, name: str) -> None:
        """初始化。"""
        self.name: str = name

        target_url = f"{self.PYPI_BASE_URL}/{self.name}/json"
        response = requests.get(target_url, timeout=5)
        if response.status_code != requests.codes.ok:
            msg = "pypi_name 检查出错"
            raise ValueError(msg)
        res = response.json()
        if not isinstance(res, dict) or "info" not in res or not res["info"]:
            msg = "请求插件 PyPi 信息失败"
            raise ValueError(msg)
        self.data: dict[str, Any] = response.json()["info"]

    def check_pypi(self) -> None:
        """检查 pypi_name。"""
        msg = "输入的 pypi_name 存在问题"
        if self.name == "null":
            raise ValueError(msg)
        package_url = self.data.get("package_url")
        if package_url is None:
            raise ValueError(msg)
        module_name = package_url.split("/")[-2]
        if self.name.lower() != module_name.lower():
            raise ValueError(msg)

    def get_info(self) -> dict[str, str]:
        """获取 PyPi 包信息"""
        name = self.data.get("name")
        if (name is None) or (name == ""):
            msg = "模块名称获取失败"
            raise ValueError(msg)
        description = self.data.get("summary")
        if (description is None) or (description == ""):
            msg = "模块描述获取失败"
            raise ValueError(msg)
        author = self.data.get("author")
        if (author is None) or (author == ""):
            email = self.data.get("author_email")
            if email is not None and "<" in email:  # PDM发包问题
                author = email.split("<")[0].strip()
            else:
                msg = "作者信息获取失败"
                raise ValueError(msg)
        license_info = self.data.get("license")
        if license_info is None:
            license_info = ""
        homepage = self.data.get("home_page")
        if homepage is None:
            homepage = ""
        tags = self.data.get("keywords")
        if (tags is None) or (tags == ""):
            msg = "标签信息获取失败"
            raise ValueError(msg)
        return {
            "name": name,
            "description": description,
            "author": author,
            "license": license_info,
            "homepage": homepage,
            "tags": tags,
        }

如何动态执行脚本并处理错误

由于项目的需求,需要对插件进行运行验证。但是python需要通过import才能调用相关插件功能。因此,这里必须采用动态执行python脚本。如果采用生成python文件,然后执行,或许过于麻烦,我们可以直接使用传递执行参数的方式,告诉脚本需要执行的插件。动态执行也可以采用subprocess执行。

def alicebot_test() -> None:
    """验证插件是否能在 AliceBot 中正常运行。"""
    try:
        # 要执行的 Python 脚本路径
        python_script_path = ".github/actions_scripts/plugin_test.py"
        result = subprocess.run(
            f"python {python_script_path} {MODULE_NAME} {TYPE}",
            timeout=10,
            check=True,
            shell=True,  # noqa: S602
            capture_output=True,
        )
        if result.returncode != 0:
            msg = f"脚本执行失败: {result.stdout}"
            raise ValueError(msg)
    except subprocess.TimeoutExpired as e:
        msg = f"脚本执行超时: {e.stdout}"
        raise ValueError(msg) from e
    except subprocess.CalledProcessError as e:
        msg = f"脚本执行错误: {e.stdout}"
        raise ValueError(msg) from e

然后可以通过sys的argv获取到执行参数。

import sys
MODULE_NAME = sys.argv[1]
TYPE = sys.argv[2]

在上文代码可以看到如何通过catch和e.stdout接住动态执行python脚本的错误信息或者成果信息,那么如何在动态脚本中输出这些信息呢?

if MODULE_NAME == "null":
    sys.stdout.write("Invalid MODULE_NAME value. Must be a valid Python module name.")
    sys.exit(1)

if TYPE not in {"plugin", "adapter", "bot"}:
    sys.stdout.write(
        "Invalid TYPE value. Must be one of 'plugin', 'adapter', or 'bot'."
    )
    sys.exit(1)

如何进行alicebot的插件验证

根据项目需求和导师建议,在bot运行的一个钩子生命周期(加载完插件,但还没启动bot)中直接设置程序结束。

@bot.bot_run_hook
async def bot_run_hook(bot: Bot) -> None:
    """在 Bot 启动后直接退出。"""
    if TYPE == "plugin":
        bot.should_exit.set()
    sys.exit(0)

那么如果出现意外错误,如何进行错误处理,或者拦截错误呢?根据项目需求和导师建议,这里采用代码插桩的方式,替换bot原有的错误处理函数,将错误通过write暴露给外面一层的脚本,再蹭蹭外传,最终通过issue评论显示错误和错误原因。

bot = Bot(config_file=None)

old_error_or_exception = Bot.error_or_exception


def error_or_exception(self: Bot, message: str, exception: Exception) -> None:
    """出现错误直接退出."""
    old_error_or_exception(self, message, exception)
    sys.stdout.write(message)
    sys.exit(1)


Bot.error_or_exception = error_or_exception

如何进行PR的自动提交

首先,如果要进行pr,需要先检查pr是否已存在,否则不要重新生成pr,防止冲突。

      - name: Get open linked PR
        id: get_open_linked_pr
        run: |
          open_linked_pr_length=$(\
            gh pr list \
              --repo $REPO \
              --state open \
              --search "close #$ISSUE_NUM in:body" \
              --json number | jq '. | length'\
          )
          echo "::set-output name=open_linked_pr_length::$open_linked_pr_length"

      - name: Check open linked pr length
        if: steps.get_open_linked_pr.outputs.open_linked_pr_length != 0
        run: |
          echo "Unclosed pull request is existing."
          exit 1

然后,如果要生成pr,很重要的就是,将文件写进需要修改的文件里。

首先需要根据信息,判断需要写入的文件是哪个。然后需要查看原来的文件是否存在相同的插件,有则进行更新。没有相同,则直接进行添加。同时,附赠一个最新的时间戳,方便前端页面可以按照时间戳进行排序。在时间的获取时,需要注意使用东八区时间,github运行的默认环境是在美国,所以时区非常需要注意。

pr的一个前提是它在不同的分支里才能pr到主分支,因此,需要先创建一个随机命名的新分支。这里按照时间命名新分支,防止重复。

      - name: Define new branch name
        id: define_new_branch_name
        run: |
          new_branch_name=$(echo "${ISSUE_NUM}-$(TZ=UTC-9 date '+%Y%m%d-%H%M%S')")
          echo "::set-output name=new_branch_name::$new_branch_name"

      - name: Create branch
        uses: EndBug/add-and-commit@v9
        with:
          new_branch: ${{ steps.define_new_branch_name.outputs.new_branch_name }}

接着就是创建一个pr,然后吧之前的一些内容全部推上去,等待合并。在pr的信息里写close issue即可在合并时自动关闭issue。

      - name: Create PR
        run: |
          gh pr create \
            --head $NEW_BRANCH_NAME \
            --base $BASE_BRANCH_NAME \
            --title "$ISSUE_TITLE" \
            --body "close #${ISSUE_NUM}"
        env:
          NEW_BRANCH_NAME: ${{ steps.define_new_branch_name.outputs.new_branch_name }}
          BASE_BRANCH_NAME: ${{ github.event.repository.default_branch }}

      - name: Copy Commands
        id: copy-commands
        run: |
          echo "git fetch origin ${NEW_BRANCH}"
          echo "git checkout ${NEW_BRANCH}"
          echo "code --reuse-window ${TYPE}.json"
          result="success"
          echo "result=$result" >> $GITHUB_OUTPUT
        env:
          NEW_BRANCH: ${{ steps.define_new_branch_name.outputs.new_branch_name }}

如何设计一份翻页组件

由于项目状况,仓库不引入新的ui组件库,但是翻页组件因为实际需求,所以需要自行设计和编写。该组件能够在不同屏幕给出不同的长度和内容。在不同的页码不同的显示效果,做到一个自适应。

显示逻辑

如果总页数小于数组大小+2:所有页码都会显示,没有省略符号。

  • 例如,如果总页数是 5,数组大小是 6,那么显示为:[1, 2, 3, 4, 5]

如果当前页码离第一页很近:显示前几页,然后是一个省略符号,最后是最后一页。

  • 例如,如果当前页是 2,总页数是 20,数组大小是 10,那么显示为:[1, 2, 3, 4, 5, 6, 7, 8, ..., 20]

如果当前页码离最后一页很近:显示第一页,然后是一个省略符号,最后是最后几页。

  • 例如,如果当前页是 19,总页数是 20,数组大小是 10,那么显示为:[1, ..., 13, 14, 15, 16, 17, 18, 19, 20]

如果当前页码在中间:显示第一页,然后是一个省略符号,中间是一些页码,再然后是一个省略符号,最后是最后一页。

  • 例如,如果当前页是 10,总页数是 20,数组大小是 10,那么显示为:[1, ..., 8, 9, 10, 11, 12, ..., 20]
const genPageArray = (current: number, total: number, size: number) => {
  let arr: Array<string | number> = [];
  if (total < size + 2) {
    arr = Array.from({ length: total }, (v, k) => k + 1);
  } else if (current < size - 2) {
    arr = Array.from(
      (function* gen(i, l) {
        while (i < l) yield i++;
      })(1, size - 2 + 1)
    );
    arr.push("...");
    arr.push(total);
  } else if (total - current < size - 2) {
    arr.push(1);
    arr.push("...");
    arr = arr.concat(
      Array.from(
        (function* gen(i, l) {
          while (i < l) yield i++;
        })(total - size + 2, total + 1)
      )
    );
  } else {
    arr.push(1);
    arr.push("...");
    arr = arr.concat(
      Array.from(
        (function* gen(i, l) {
          while (i < l) yield i++;
        })(
          current - Math.floor((size - 4) / 2),
          current - Math.floor((size - 4) / 2) + size - 4 + 1
        )
      )
    );
    arr.push("...");
    arr.push(total);
  }
  return arr;
};

自适应尺寸

这些逻辑会根据三种不同的尺寸(pageArrayLg, pageArrayMd,pageArraySm)进行调整,以适应不同屏幕尺寸。

  • 大屏幕(pageArrayLg): 17个元素(数字或省略符号)。
  • 中等屏幕(pageArrayMd): 10个元素。
  • 小屏幕(pageArraySm): 6个元素。

因此,当你缩放浏览器或在不同设备上查看时,页码数组的大小和内容会相应地改变。

总结与心得

前端的界面由于自身的技术背景,开发很顺利。就连翻页的组件,设计一下逻辑也不过家常便饭。但是issue-ops的构建,就没有想象中的那么顺利了。

waketime

根据waketime的统计,整体issue-ops开发调试时长达到了119h。经常一个小问题调试到凌晨4-5点。当然,其中很大一部分原因是整体workflow还是不够熟练,对于bug无法发觉和修复。对于调试,很难做到本地调试,大部分时间浪费在线上调试。项目开发也在不断改版,最后为了能够适应各种情况,做出各种错误信息显示,进行了大量的边缘测试,耗费许多时间。

在本次项目开源开发过程中,作为一名初学者获得了一个较为完整的issue-ops开发经验,给之后个人的其他项目,例如pr-ops,llm-ops带来了很多便利和帮助,对于个人的成长来说很有价值和意义。作为一个开源项目,其本身的完成就是得益于很多网络代码的开源,使得能够站在他人的肩膀上继续开发。很感谢ospp官方和alicebot导师给予这次机会,从这次经历中可以获得很多的技术经验和能力提升。

后续工作安排

  1. 提供基于前端的插件信息提交
  2. 将workflow打包成action app
  3. ...
Marlene

作者: Marlene

2024 © MarleneJ & 少轻狂