在禅道项目管理软件v9.8.3的一个API调用bug的排错中使用到的一些工具和分析方法

禅道是一款国产的不错的项目管理软件,有开源和商业版本,软件做得挺不错的,如果大家所在的公司不差这些小钱,建议使用禅道商业版,也算是对优秀软件的一点支持吧。

1、为什么会需要调用禅道的API接口?

主要目的是为了解决一些运维工作自动化方面的问题。我希望把包括zabbix监控系统在内的各种监控报警事件,做成自动根据事件信息生成禅道上特定项目下的任务工单,同时指派给指定的值班人员。在过往的工作中,经常发生监控报警事件得不到处理,或处理后未更新事件状态的事情,有时也存在角色、职责分工不明,一个报警事件同时发给了十个人,却没有一个主要负责人,反而产生的事件响应处理上的延误。所以准备做一个自动将报警事件转换为待处理的工单的功能,需要使用到禅道API接口。

2、禅道创建新任务的API接口的说明

针对禅道的API使用方面的知识,会另外写一篇文章。这里只谈与bug有关的部分。

这个接口bug是存在于创建新任务的api中,产生的原因是禅道的版本迭代、功能演变,改变了一个表单参数的数据类型,而在源码的处理逻辑中忽视了这一点所带来的影响。

创建一个任务的API接口使用信息如下表所示:

GET/POST  /zentao/task-create-[projectID]-[storyID]-[moduleID]-[taskID]-[todoID].json

Create a task.

参数列表

类型

描述

projectID

int

 

storyID

int

 

moduleID

int

 

taskID

int

 

todoID

int

 

注:实际上参数projectID是个必选参数,其它参数可选。

下面是我的测试环境中的接口地址,向projectID=1, storyID=1, modeuleID=3的项目模块中创建一个task:

http://192.168.81.7/zentao/task-create-1-1-3.json?zentaosid=mfn6e7p8ptan851aum5l7fcao0&t=json

一点说明:

  • zentaosid,是事先获取到的session id,保存在本地cookie中,每次调用接口时需要带上
  • t=json,使用json格式进行数据交互
  • 使用POST方式提交
  • 使用form-data格式同步提交表单数据,从创建新任务的web表单页面上可以看到只有两个属性是必须的,即type, name,分别定义了任务类型和任务名称。

3、使用Postman调试api接口

我们需要使用Postman来模拟POST方式提交数据。从以下地址下载和安装postman工具。

https://www.getpostman.com/

安装好以后,按下图所示方法对指定的api接口地址进行测试。需要填写URL地址,选择提交方式,选择提交数据的编码格式,定义好必要的表单参数和数据。

点击“Send”按钮后,得到返回结果,"Body"数据显示结果是success的,"locate"则是一个页面重定向信息。

我们所要讨论的API接口bug,也就是在这里。因为上面操作是成功的,实际上禅道中指定项目-模块下并没有创建出我们的任务来。

如果不介意的话,上面Postman中的"Send"按钮,多点几次也无妨,因为每次都会返回一个"success"的结果回来。所有的提交尝试都是无效的。

与此同时,直接在禅道web页面上通过页面表单,仅填写type, name字段,是可以成功提交和创建出任务来的。

下面就依据分析和定准错误的过程做一下说明。

4、观察和分析创建禅道任务的表单参数

如上图所示,直接把创建任务的URL地址从html改为json后缀,就可以得到json格式的数据了。

主要包括了project,story,task,users等几部分数据。我们主要关注task这一段,如下所示:

"task":{"module":3,"assignedTo":"","name":"","story":1,"type":"","pri":"","estimate":"","desc":"","estStarted":"","deadline":"","mailto":"","color":""}

猜测,可能是需要填写和提交的表单参数不全?

验证办法:逐个把上面task涉及的所有参数,添加到Postman的form-data中去,并测试提交。

初步结论:显然是提交再多的表单参数也不管用

实际原因:从上面json数据得到的表单字段参数assignedTo名称有误,这个后面会逐步谈到。

5、使用wireshark抓包分析数据包内容

1)登录到运行这套禅道软件的测试机上,执行抓包命令:

# tcpdump -i eth0 -w /tmp/20180724001.pcap

2)在本地PC机上打开浏览器,登录到禅道网站并创建一个指定项目、模块下的禅道任务

3)在本地PC上分析抓包文件,关键信息如下图所示

  • 先找到POST操作的数据包
  • 再检查MIME数据内容,可以分辨出通过表单提交的各个属性字段和取值
  • 重点观察下上图中展示出的name="assignedTo[]"

因为wireshark中能看到的数据包内容不是很友好,所以虽然抓包时已经看到了assignedTo[]的信息,但被上一步骤中的json数据参数误导了,以为上图中的意思是assignedTo参数,取值为[],一个空数组。

实际情况是:表单参数名是assignedTo[],取值为空。这个就是最终水落石出时才发现的了。

6、阅读和分析禅道php源码

大部分WEB开发框架,不论是什么编程语言开发的,都会遵循MVC的一个设计逻辑。所以我们可以看到禅道项目源码目录如下图所示,主要实现逻辑就都在model.php中了,控制逻辑在control.php中。

我们只截取一点关键部分的代码做分析。

下面是control.php文件中的create方法的一部分:

    /**
     * Create a task.
     *
     * @param  int    $projectID
     * @param  int    $storyID
     * @param  int    $moduleID
     * @param  int    $taskID
     * @param  int    $todoID
     * @access public
     * @return void
     */
    public function create($projectID = 0, $storyID = 0, $moduleID = 0, $taskID = 0, $todoID = 0)
    {
        $task = new stdClass();
        $task->module     = $moduleID;
        $task->assignedTo = '';
        $task->name       = '';
        $task->story      = $storyID;
        $task->type       = '';
        $task->pri        = '';
        $task->estimate   = '';
        $task->desc       = '';
        $task->estStarted = '';
        $task->deadline   = '';
        $task->mailto     = '';
        $task->color      = '';
        if($taskID > 0)
        {
            $task      = $this->task->getByID($taskID);
            $projectID = $task->project;
        }

		
        if($todoID > 0)
        {
            $todo = $this->loadModel('todo')->getById($todoID);
            $task->name = $todo->name;
            $task->pri  = $todo->pri;
            $task->desc = $todo->desc;
        }

        $project   = $this->project->getById($projectID);
        $taskLink  = $this->createLink('project', 'browse', "projectID=$projectID&tab=task");
        $storyLink = $this->session->storyList ? $this->session->storyList : $this->createLink('project', 'story', "projectID=$projectID");

        /* Set menu. */
        $this->project->setMenu($this->project->getPairs(), $project->id);

        if(!empty($_POST))
        {
			
            $response['result']  = 'success';
            $response['message'] = '';

            $tasksID = $this->task->create($projectID);
			
			
            if(dao::isError())
            {
                $response['result']  = 'fail';
                $response['message'] = dao::getError();
                $this->send($response);
            }
  • 从上面代码可以看出我们在创建一个禅道任务时,是分成多个子步骤执行的,先创建了一个空的$task对象,然后逐步填充内容;
  • 根据$tasksID = $this->task->create($projectID);这行代码可以看到它是在调用model中的create($projectID)方法完成创建一个task的部分工作;

下面是model.php文件中create($projectID)方法的一部分:

class taskModel extends model
{
    /**
     * Create a task.
     *
     * @param  int    $projectID
     * @access public
     * @return void
     */
    public function create($projectID)
    {
        $taskIdList = array();
        $taskFiles  = array();
        $this->loadModel('file');
        $task = fixer::input('post')
            ->add('project', (int)$projectID)
            ->setDefault('estimate, left, story', 0)
            ->setDefault('status', 'wait')
            ->setIF($this->post->estimate != false, 'left', $this->post->estimate)
            ->setIF($this->post->story != false, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story))
            ->setDefault('estStarted', '0000-00-00')
            ->setDefault('deadline', '0000-00-00')
            ->setIF(strpos($this->config->task->create->requiredFields, 'estStarted') !== false, 'estStarted', $this->post->estStarted)
            ->setIF(strpos($this->config->task->create->requiredFields, 'deadline') !== false, 'deadline', $this->post->deadline)
            ->setDefault('openedBy',   $this->app->user->account)
            ->setDefault('openedDate', helper::now())
            ->stripTags($this->config->task->editor->create['id'], $this->config->allowedTags)
            ->join('mailto', ',')
            ->remove('after,files,labels,assignedTo,uid,storyEstimate,storyDesc,storyPri,team,teamEstimate,teamMember,multiple,teams')
            ->get();
		
        foreach($this->post->assignedTo as $assignedTo)
        {
			
            /* When type is affair and has assigned then ignore none. */
            if($task->type == 'affair' and count($this->post->assignedTo) > 1 and empty($assignedTo)) continue;

            $task->assignedTo = $assignedTo;
            if($assignedTo) $task->assignedDate = helper::now();
			
            /* Check duplicate task. */
            if($task->type != 'affair')
            {
                $result = $this->loadModel('common')->removeDuplicate('task', $task, "project=$projectID and story=$task->story");
                if($result['stop'])
                {
                    $taskIdList[$assignedTo] = array('status' => 'exists', 'id' => $result['duplicate']);
                    continue;
                }
            }
			
            $task = $this->file->processImgURL($task, $this->config->task->editor->create['id'], $this->post->uid);
            $this->dao->insert(TABLE_TASK)->data($task)
                ->autoCheck()
                ->batchCheck($this->config->task->create->requiredFields, 'notempty')
                ->checkIF($task->estimate != '', 'estimate', 'float')
                ->checkIF($task->deadline != '0000-00-00', 'deadline', 'ge', $task->estStarted)
                ->exec();
			
            if(dao::isError()) return false;

            $taskID = $this->dao->lastInsertID();
  • 该方法中先是对$task的一些字段进行了初始化,设置默认值等;
  • 然后没有经历任何判定条件就执行了foreach($this->post->assignedTo as $assignedTo)的命令,对一个指定的数组做循环处理;
  • 我们看一下这个数组是谁:$this->post->assignedTo
  • 要知道的是,创建task的写库表的操作都是在这个数组循环中完成的,如果进入不了这个循环,则是完全没有机会能创建出禅道的项目task的;
  • 到这里,问题基本上明确了,必须要在post中提交的表单参数,除了type, name外,还有一个assignedTo的参数,而它需要是一个数组变量!
  • 在Postman中提交一个数组变量时,需要将变更名称定义为"assignedTo[]"的形式,至于该变量的取值倒不重要,可以保持为空,也可以指定为一个或多个禅道用户名;

7、使用var_dump()方法调试php

在上面步骤中主要是讲的结论,实际上整个结论都是在使用var_dump()反复调试禅道API接口后才得到的。经历了几十次的对比正常的页面表单提交和异常的api数据提交过程,所打印的变更信息。

var_dump()方法可以直接将php变量在保持原数据结构的条件下,打印在console上。

只需要在control.php文件的下面位置打印一下$tasksID的变量信息即可。

  • $tasksID = $this->task->create($projectID);这一行是调用model中的方法创建出禅道task,返回值中除了task id的信息,还会包括了方法执行结果(created/existed)信息;
  • var_dump($tasksID); 我们把这个变量打印出来,分别观察下通过页面表单提交时这个$tasksID的值,通过api提交数据时的$tasksID的值。

1)通过web页面表单创建task

  • 在表单中我们只填写了type和name两个字段;
  • 提交后,成功创建出了一个task任务,页面弹出的窗口中展示出了$tasksID变量信息和返回的响应结果。
  • 从打印出来的信息可以看到,创建task的方法执行结果为"status"="created",task id为44. 

2)通过禅道api创建一个task

 

  • 上图中的array(0) { }就是我们所得到的$tasksID变量的取值,虽然返回的响应结果说result=success,但显然在调用model中的create方法时,没创建和返回有效的对象。
  • 而上面创建task失败的原因就在于,model中create方法的foreach($this->post->assignedTo as $assignedTo)的处理逻辑上存在设计漏洞。必须跟表单同步提交的参数只有type, name,但在该方法中又在没做任何初始赋值处理或判定的条件下,直接假定$this->post->assignedTo是一个有效的数组变量来使用。这就直接导致我们在使用api提交数据时,如果没有提供assignedTo[]参数,或者是误把该参数理解为了是一个string类型的参数变量,则都会得到上图所示的显示已经成功提交,但却是无效的。

3)补充上正确的assignedTo[]变量后的正确提交姿势

也可以按需设置好把该任务指派给哪个用户:

 

 

禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整地覆盖了项目管理的核心流程。注重实效的管理思想,合理的软件架构,简洁高效的操作,优雅的代码实现,灵活的扩展机制,强大而易用的api调用机制,多语言支持,多风格支持,搜索功能,统计功能——这一切,您通过禅道,都可以拥有!禅道在手,项目无忧! 禅道的功能列表: 1、产品管理:包括产品、需求、计划、发布、路线图等功能。 2、项目管理:包括项目、任务、团队、build、燃尽图等功能。 3、质量管理:包括bug、测试用例、测试任务、测试结果等功能。 4、文档管理:包括产品文档库、项目文档库、自定义文档库等功能。 5、事务管理:包括todo管理,我的任务、我的Bug、我的需求、我的项目等个人事务管理功能。 6、组织管理:包括部门、用户、分组、权限等功能。 7、统计功能:丰富的统计表。 8、搜索功能:强大的搜索,帮助您找到相应的数据。 9、灵活的扩展机制,几乎可以对禅道的任何地方进行扩展。 10、强大的api机制,方便与其他系统集成。 更新日志: 禅道15.0stable版本 修复的Bug 产品视图项目列表页面列宽和排版 在产品下批量创建需求时打开了项目的页面,然后进入产品需求页面保存后二级导航错误 审计页面,处理一下换行和字段显示不完整问题 地盘日志查看所有日志时,二级导航无选态 地盘日志查看所有日志时,日期控件icon不支持点击 内禅地盘项目列表,区块点击刷新后列表字段无法再进行排序 执行下打开需求详情,点击基本信息的所属模块后二级导航左侧应用图标位置偏移 进入创建文档库动态后二级导航高亮不对 查看已删除的任务时没有标记已删除 Bug批量指派给用户显示不完整 全局搜索结果页面上移 用户需求关闭后没有置灰细分按钮 页面刷新后会显示为“Bad Request”es 点击项目库跳转到了创建项目页 Bug详情页新建Bug保存后返回页面二级导航菜单未高亮 用例详情页显示所属s 点击项目文档库提示没有创建项目权限 用例列表页去掉底部滚动条 二级导航缺失菜单分隔线 左侧导航多显示了一条分隔线 项目和执行测试三级导航切换产品分支有问题 文档from参数全部删掉,检查逻辑 二级导航第一个菜单去掉权限后分割线还有 用例详情切换产品链接不对
PICC编译器9.8.3是一款由美国微芯片技术公司推出的集成电路编译器。该编译器可用于编译PIC(Peripherally Integrated Circuit)系列微控制器的汇编语言源代码,将其转化为可执行的机器码。PICC编译器是一种高级语言编译器,可以将使用C语言等高级语言编写的代码转换为机器指令,以实现对PIC芯片的程序控制。 PICC编译器9.8.3具有以下特点和功能: 1. 高效性能:该编译器经过优化和改进,具有快速、高效的编译和优化能力,能够生成高效的机器码,使程序在PIC芯片上运行更加稳定和高效。 2. 高兼容性:该编译器对多种PIC系列芯片提供支持,包括PIC10/12/16/18/24/30/33以及dsPIC和PIC32系列等,使开发者可以根据具体需求选择适合的芯片进行开发。 3. 强大的调试功能:PICC编译器9.8.3提供了丰富的调试功能,如单步执行、断点调试等,方便开发者进行代码的调试和错误排查,提高开发效率。 4.友好的开发环境:该编译器配套的开发环境友好易用,提供了直观的用户界面和丰富的开发工具,如语法高亮显示、自动补全、代码片段等,方便开发者进行代码编写和管理。 PICC编译器9.8.3是一款成熟和稳定的编译器工具,被广泛应用于电子产品开发、嵌入式系统等领域。它将高级语言与PIC芯片结合,为开发者提供了便捷、高效的开发方式,帮助开发者更好地实现对PIC芯片的控制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值