在开源项目中,Issues是一个强大的功能,用于跟踪bug、功能请求和任务。然而,随着项目的发展,Issues可能会变得难以管理,特别是当你需要离线访问或进行深入分析时。
当然GitHub Issues除了上述功能以外,做在线笔记也非常的方便,它支持Markdown语法,还能打标签、分层等等,还天然的支持评论功能。由于我本人就非常热衷于使用Issue做笔记,但是问题就是在离线环境下无法使用,那么能不能把Issue作为离线Markdown文件下载到本地呢?

答案显然是可以的,也是我本次项目的主要功能之一。
issue2file
是一个用Go语言编写的命令行工具,它可以将GitHub仓库中的Issues导出为本地Markdown文件,并提供多种强大的功能:
1)完整内容保留:保留Issue的标题、内容、标签、状态、创建时间等信息
2)评论支持:可选择性地下载Issue的所有评论
除此之外,我想了想能不能通过AI扩展一下?答案显然也是可以的,所以还补充了如下功能:
3)AI分析:集成AI功能,可以对Issues进行智能分析和总结
4)数据可视化:自动生成多种图表,包括Issue状态分布、标签分布和时间趋势等
项目介绍
还是先说一下使用方式吧,当然如果想要支持私有仓库和AI功能的话,需要拿到自己的Github Token和DeepSeek的API Token。
1)仓库地址
shell
https://github.com/ibarryyan/issue2file
2)工具构建
shell
go mod tidy
go build
3)使用方式
最简单的使用方式就是直接运行可执行文件,加上仓库链接作为参数,比如
shell
./issue2file ibarryyan/golang-tips-100
如果想要拉取自己的私有仓库就要先生成一个自己的Github Token,然后使用命令行参数或者配置文件进行启动,详细说明可以参考:
shell
https://github.com/ibarryyan/issue2file/blob/master/README.md
当然了,还有额外的参数能支持生成图表分析仓库的所有Issue,主要有Issue创建时间趋势、状态分析和标签分布等几个维度:

技术实现
issue2file
采用模块化设计,主要包含以下几个核心组件:
1)配置管理:使用Viper库处理配置文件
2)GitHub API交互:使用go-github库获取Issues数据
3)Markdown生成:将Issue数据转换为Markdown格式
4)AI分析:集成AI能力对Issues进行分析
5)图表生成:使用go-echarts库生成数据可视化图表
关键技术点
1)配置管理
项目使用Viper库来处理配置,支持从配置文件中读取各种参数:
go
func InitConfig() {
viper.SetConfigName("config")
viper.SetConfigType("conf")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file: %s", err)
}
// 读取配置项
Config.GitHubToken = viper.GetString("gitHubToken")
Config.AIToken = viper.GetString("aiToken")
Config.CommentEnable = viper.GetBool("commentEnable")
Config.AIEnable = viper.GetBool("aiEnable")
Config.ChartEnable = viper.GetBool("chartEnable")
Config.OutputDir = viper.GetString("outputDir")
Config.SummaryFile = viper.GetString("summaryFile")
}
2)GitHub API交互
使用go-github库与GitHub API进行交互,获取Issues数据:
go
func FetchIssues(owner, repo string) ([]*github.Issue, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: Config.GitHubToken},
)
tc := oauth2.NewClient(ctx, ts)
client := github.NewClient(tc)
opt := &github.IssueListByRepoOptions{
State: "all",
Sort: "created",
Direction: "desc",
ListOptions: github.ListOptions{
PerPage: 100,
},
}
var allIssues []*github.Issue
for {
issues, resp, err := client.Issues.ListByRepo(ctx, owner, repo, opt)
if err != nil {
return nil, err
}
allIssues = append(allIssues, issues...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return allIssues, nil
}
3)Markdown生成
将Issue数据转换为结构化的Markdown文件:
go
func GenerateMarkdown(issue *github.Issue, comments []*github.IssueComment) string {
var md strings.Builder
// 添加标题
md.WriteString(fmt.Sprintf("# %s\n\n", *issue.Title))
// 添加元数据
md.WriteString(fmt.Sprintf("- **Issue编号**: #%d\n", *issue.Number))
md.WriteString(fmt.Sprintf("- **创建者**: %s\n", *issue.User.Login))
md.WriteString(fmt.Sprintf("- **创建时间**: %s\n", issue.CreatedAt.Format("2006-01-02 15:04:05")))
md.WriteString(fmt.Sprintf("- **状态**: %s\n", *issue.State))
// 添加标签
if len(issue.Labels) > 0 {
md.WriteString("- **标签**: ")
for i, label := range issue.Labels {
if i > 0 {
md.WriteString(", ")
}
md.WriteString(*label.Name)
}
md.WriteString("\n")
}
// 添加正文
md.WriteString("\n## 内容\n\n")
md.WriteString(*issue.Body)
// 添加评论
if len(comments) > 0 {
md.WriteString("\n\n## 评论\n\n")
for _, comment := range comments {
md.WriteString(fmt.Sprintf("### %s 评论于 %s\n\n", *comment.User.Login, comment.CreatedAt.Format("2006-01-02 15:04:05")))
md.WriteString(*comment.Body)
md.WriteString("\n\n---\n\n")
}
}
return md.String()
}
4)AI分析
集成AI能力,对Issues进行智能分析和总结:
go
func AnalyzeIssues(issues []*github.Issue) (string, error) {
if !Config.AIEnable || Config.AIToken == "" {
return "", errors.New("AI analysis is disabled or token is not provided")
}
// 准备AI分析的输入数据
var input strings.Builder
input.WriteString("请分析以下GitHub Issues,并提供总结报告:\n\n")
for _, issue := range issues {
input.WriteString(fmt.Sprintf("Issue #%d: %s\n", *issue.Number, *issue.Title))
input.WriteString(fmt.Sprintf("状态: %s\n", *issue.State))
input.WriteString(fmt.Sprintf("创建时间: %s\n", issue.CreatedAt.Format("2006-01-02")))
input.WriteString("标签: ")
for i, label := range issue.Labels {
if i > 0 {
input.WriteString(", ")
}
input.WriteString(*label.Name)
}
input.WriteString("\n\n")
// 限制内容长度,避免超出AI API的限制
body := *issue.Body
if len(body) > 500 {
body = body[:500] + "..."
}
input.WriteString(body)
input.WriteString("\n\n---\n\n")
}
// 调用AI API进行分析
analysis, err := callAIAPI(input.String())
if err != nil {
return "", err
}
return analysis, nil
}
5)图表生成
使用go-echarts库生成数据可视化图表:
go
func GenerateCharts(issues []*github.Issue, outputDir string) error {
if !Config.ChartEnable {
return nil
}
// 生成状态分布图
if err := generateStatusChart(issues, outputDir); err != nil {
return err
}
// 生成标签分布图
if err := generateTagsChart(issues, outputDir); err != nil {
return err
}
// 生成时间趋势图
if err := generateTimeChart(issues, outputDir); err != nil {
return err
}
return nil
}
全部代码目前已经开源,大家可以去Github上拉取~~
开源成果
issue2file
项目在开源后的一周内就获得了29个star,这种积极的社区反馈不仅验证了项目的价值,也为未来的发展提供了动力。

在这里主要感谢@ruanyf老师在《科技爱好者周刊》中的推荐!
总结与规划
issue2file
成功地实现了将GitHub Issues转换为本地Markdown文件的核心功能,并通过AI分析和数据可视化等特性提供了额外的价值。
基于社区反馈和项目愿景,未来的发展计划包括:
功能增强:
- 添加增量更新功能,只下载新的或更新的Issues
- 增强AI分析能力,提供更深入的洞察
用户体验改进:
- 提供Web界面,使非技术用户也能轻松使用
- 添加进度显示和更详细的日志
- 完善文档和示例
如果你对这个项目感兴趣,欢迎访问GitHub仓库(https://github.com/ibarryyan/issue2file
),给项目点个star,或者贡献代码!