使用Go语言生成Excel任务表依赖图的代码实现

 更新时间:2025年09月09日 10:51:26   作者:witton  
在游戏中,任务是非常常见的玩法,可能会有主线任务,支线任务以及其它一些类型的任务,当任务比较多的时候,它们的依赖关系将变得不直观,很容易出错,所以本文介绍了使用Go语言生成Excel任务表依赖图的代码实现,需要的朋友可以参考下

一、前言

在游戏中,任务是非常常见的玩法,可能会有主线任务,支线任务以及其它一些类型的任务,各任务可能还会有前置任务,即需要完成某个任务之后,才能做当前任务。在游戏开发中,配置表可以使用Excel来编辑,如果是任务表,可能会是如下配置方式:

TaskIDTaskTitlePreTask
10任务100
20任务200
11任务1110
21任务2120

当任务比较多的时候,它们的依赖关系将变得不直观,很容易出错,出错也不容易发现。

有没比较直观的方式进行查看,排错呢?笔者想到了目前非常流程的Markdown文件,它可以简单地通过文本的方式输入然后输出强大的各种图表。这里就可以使用mermaid图来直观展现。

关于mermaid图可以去官网查看用例。

注意:mermaid图在渲染时,如果不设置subgraph则可能会出现乱序问题,即不是按代码中出现的顺序渲染。

二、实现

为了方便Go读取Excel,需要使用相关的Excel库,笔者使用excelize库。

根据前面的效果图,可以知道,这其实就是一个深度优先的树,实现方式有两种,一种是使用递归的方式来实现,这种方式实现起来简单,但是如果层次很深,那可能会出现栈溢出;另一种方式就是使用栈的方式来实现,将每一层节点先压栈,然后从栈顶取出一个节点然后再将其所有子节点入栈,再从栈顶取出一个节点处理,依此类推,直到栈中所有节点处理完毕。

下面列出使用递归方式实现的版本:

/*
MIT License

# Copyright (c) 2023 WittonBell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/xuri/excelize/v2"
)

var taskIDField string
var taskTitleField string
var preTaskField string
var noCaseSensitive bool // 是否不区分大小写
var fieldNameRow uint    // 字段名所在行号
var dataStartRow uint    // 数据开始行号

type node struct {
	taskID    string
	taskTitle string
}

type multiMap map[string][]*node

func (slf multiMap) Add(key string, nd *node) {
	if len(slf) == 0 {
		slf[key] = []*node{nd}
	} else {
		slf[key] = append(slf[key], nd)
	}
}

func (slf multiMap) Get(key string) []*node {
	if slf == nil {
		return nil
	}
	return slf[key]
}

func (slf multiMap) Del(key string) {
	delete(slf, key)
}

func searchKeyCol(rows *excelize.Rows) (TaskIDCol, PreTaskIDCol, TitleCol int) {
	row, err := rows.Columns()
	if err != nil {
		fmt.Println(err.Error())
	}

	for i, col := range row {
		name := col
		if noCaseSensitive {
			name = strings.ToLower(col)
		}
		if name == preTaskField {
			PreTaskIDCol = i + 1
		} else if name == taskIDField {
			TaskIDCol = i + 1
		} else if name == taskTitleField {
			TitleCol = i + 1
		}
	}
	return
}

func readExcel(filePath string) multiMap {
	fd, err := excelize.OpenFile(filePath)
	if err != nil {
		fmt.Printf("读取文件`%s`失败", filePath)
		return nil
	}
	defer func() {
		fd.Close()
	}()
	TaskIDCol, PreTaskIDCol, TitleCol := -1, -1, -1
	sheetName := fd.GetSheetName(0)
	rows, err := fd.Rows(sheetName)
	if err != nil {
		return nil
	}
	defer func() {
		rows.Close()
	}()

	m := multiMap{}
	for i := 1; rows.Next(); i++ {
		if i == int(fieldNameRow) {
			TaskIDCol, PreTaskIDCol, TitleCol = searchKeyCol(rows)
			isOk := true
			if TaskIDCol < 0 {
				isOk = false
				fmt.Printf("要求字段名:%s\n", taskIDField)
			}
			if PreTaskIDCol < 0 {
				isOk = false
				fmt.Printf("要求字段名:%s\n", preTaskField)
			}
			if TitleCol < 0 {
				isOk = false
				fmt.Printf("要求字段名:%s\n", taskTitleField)
			}
			if !isOk {
				return nil
			}
		}
		if i < int(dataStartRow) {
			continue
		}
		TaskIDCell, err := excelize.CoordinatesToCellName(TaskIDCol, i)
		if err != nil {
			continue
		}
		PreTaskIDCell, err := excelize.CoordinatesToCellName(PreTaskIDCol, i)
		if err != nil {
			continue
		}

		TitleColCell, err := excelize.CoordinatesToCellName(TitleCol, i)
		if err != nil {
			continue
		}

		TaskID, err := fd.GetCellValue(sheetName, TaskIDCell)
		if err != nil || TaskID == "" {
			continue
		}

		Title, err := fd.GetCellValue(sheetName, TitleColCell)
		if err != nil || Title == "" {
			continue
		}

		PreTaskID, err := fd.GetCellValue(sheetName, PreTaskIDCell)
		if err != nil {
			continue
		}

		if PreTaskID == "" {
			PreTaskID = "0"
		}

		m.Add(PreTaskID, &node{taskID: TaskID, taskTitle: Title})
	}

	return m
}

func usage() {
	w := flag.CommandLine.Output()
	fmt.Fprintf(w, "%s 应用程序是将Excel任务表中的关系转换成Markdown的mermaid图,方便使用Markdown工具直观地查看任务依赖。", filepath.Base(os.Args[0]))
	fmt.Fprintln(w)
	fmt.Fprintf(w, "命令格式:%s -hr [字段所在行号] -dr [数据起始行号] [-nc] -id [任务ID字段名] -t [任务标题字段名] -pid [前置任务ID字段名] -o <输出文件> <Excel文件路径>", filepath.Base(os.Args[0]))
	fmt.Fprintln(w)
	flag.CommandLine.PrintDefaults()
	fmt.Fprintln(w, "  -h")
	fmt.Fprintln(w, "    \t显示此帮助")
}

func main() {
	var outputFile string

	flag.CommandLine.Usage = usage
	flag.BoolVar(&noCaseSensitive, "nc", false, "字段名不区分大小写")
	flag.UintVar(&fieldNameRow, "hr", 1, "字段所在行号")
	flag.UintVar(&dataStartRow, "dr", 2, "数据起始行号")
	flag.StringVar(&taskIDField, "id", "ID", "-id [任务ID字段名]")
	flag.StringVar(&taskTitleField, "t", "Title", "-t [任务标题字段名]")
	flag.StringVar(&preTaskField, "pid", "PreTask", "-pid [前置任务ID字段名]")
	flag.StringVar(&outputFile, "o", "任务图.md", "-o <输出文件>")

	flag.Parse()
	if flag.NArg() < 1 {
		usage()
		return
	}
	if noCaseSensitive {
		taskIDField = strings.ToLower(taskIDField)
		taskTitleField = strings.ToLower(taskTitleField)
		preTaskField = strings.ToLower(preTaskField)
	}
	mapTask := readExcel(flag.Arg(0))
	buildGraph(mapTask, outputFile)
}

func buildGraph(mapTask multiMap, outputFile string) {
	graph := "```mermaid\ngraph TB\n"
	graph += "subgraph  \n"
	root := mapTask.Get("0")
	for _, v := range root {
		graph += visit(rootNodeName, v, mapTask)
	}
	graph += "end\n"
	graph += "```"

	os.WriteFile(outputFile, []byte(graph), os.ModePerm)

	fmt.Println("完成")
}

func visit(parent string, nd *node, mapTask multiMap) string {
	slice := mapTask.Get(nd.taskID)
	graph := fmt.Sprintf("%s --> %s:%s\n", parent, nd.taskID, nd.taskTitle)
	if parent == rootNodeName {
		graph += "subgraph  \n"
	}
	for _, x := range slice {
		graph += visit(fmt.Sprintf("%s:%s", nd.taskID, nd.taskTitle), x, mapTask)
	}
	mapTask.Del(nd.taskID)
	if parent == rootNodeName {
		graph += "end\n"
	}
	return graph
}

到此这篇关于使用Go语言生成Excel任务表依赖图的代码实现的文章就介绍到这了,更多相关Go Excel任务表依赖图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang接口IP限流,IP黑名单,IP白名单的实例

    golang接口IP限流,IP黑名单,IP白名单的实例

    这篇文章主要介绍了golang接口IP限流,IP黑名单,IP白名单的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang中错误处理机制详解

    Golang中错误处理机制详解

    平时在项目开发过程中少不了对错误的处理,一个好用的系统首先要确保其健壮性,不能经常发生错误就卡死之类的情况,为了让我们的程序更加健壮,我们就需要知道golang里的错误处理机制是怎么样的,这篇文章带大家一起学习,需要的朋友跟着小编一起来看看吧
    2024-05-05
  • Go语言进行多时区时间转换的示例代码

    Go语言进行多时区时间转换的示例代码

    本文介绍了使用Go语言进行多时区时间转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • Go语言协程处理数据有哪些问题

    Go语言协程处理数据有哪些问题

    协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。本文为大家详细介绍了Go中的协程,协程不需要抢占式调度,可以有效提高线程的任务并发性,而避免多线程的缺点
    2023-02-02
  • Go语言封装一个Cron定时任务管理器

    Go语言封装一个Cron定时任务管理器

    在现代应用中,定时任务是非常常见的需求,无论是用于定时清理数据,还是定时执行系统维护任务,下面我们就来使用Go语言封装一个Cron定时任务管理器吧
    2024-12-12
  • 浅谈golang package中init方法的多处定义及运行顺序问题

    浅谈golang package中init方法的多处定义及运行顺序问题

    这篇文章主要介绍了浅谈golang package中init方法的多处定义及运行顺序问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • GoLand安装与环境配置的完整步骤

    GoLand安装与环境配置的完整步骤

    作为一个go语言程序员,觉得自己有义务为go新手开一条更简单便捷的上手之路,下面这篇文章主要给大家介绍了关于GoLand安装与环境配置的完整步骤,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • golang中的jwt使用教程流程分析

    golang中的jwt使用教程流程分析

    这篇文章主要介绍了golang中的jwt使用教程,接下来我们需要讲解一下Claims该结构体存储了token字符串的超时时间等信息以及在解析时的Token校验工作,需要的朋友可以参考下
    2023-05-05
  • Go 连接 MySQL之 MySQL 预处理详解

    Go 连接 MySQL之 MySQL 预处理详解

    Go语言提供了丰富的库和工具,可以方便地连接MySQL数据库。MySQL预处理是一种提高数据库操作效率和安全性的技术。Go语言中的第三方库提供了MySQL预处理的支持,通过使用预处理语句,可以避免SQL注入攻击,并且可以提高数据库操作的效率。
    2023-06-06
  • golang-gorm自动建表问题

    golang-gorm自动建表问题

    这篇文章主要介绍了golang-gorm自动建表问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02

最新评论