1 Star 3 Fork 0

简单的机械键盘 / cdpc

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

cdpc:坚强的进程管理模块

Node.js环境的进程管理模块。用于针对不同进程的管理工作。可管理任何需要托管的程序。需要注意的是,此扩展目前不提供cluster功能,它利用child_process模块的spawn接口完成子进程的创建工作。

在Web服务中,需要用到cluster模块,有以下解决方案:

  • 自行实现,最简单的示例,使用cluster也就几行代码。

  • 使用titbit框架,此框架内置cluster支持,支持自动负载和监控。

  • 使用其他框架再配合相关扩展。

需要明确的是:

  • 此扩展是为了开发工作而设计,不是提供一个命令去管理程序。

  • 基于此扩展设计进程管理的命令也很容易,并且有一个基于此实现的cdpc命令和服务,具体参考cdpcmd。

  • 同一个命令,同样的参数不能重复。

  • 切忌不要把多个服务监听同一个端口。

示例配置中涉及到的Web服务文件,需要自行编写测试程序,使用任何你熟悉的方式都可以。

开发此扩展的原因

其主要原因是因为我在titbit中已经实现了cluster模式自动管理子进程。而基于worker_threads也可以实现多线程管理的模型。但是还需要一个既能和它们配合使用还可以单独使用的进程管理模块,可以整合多个Web应用,还可以管理脚本、编译的二进制程序等。

另一个原因是,cluster模式由于其内部实现机制比较复杂,考虑到不同用户权限导致的问题,在Linux/Unix上,子进程和父进程必须是相同的uid和gid,如果master进程是root身份,则worker进程也是root身份,所以对worker更改uid和gid会失败。

若要综合实现各种需求,那么这个扩展和web框架再结合cluster是一个利器。

特点

它很简单并强大,而且还很稳定,提供了简单的接口控制子进程的终止和启动,可以在运行时删除和添加子进程服务。

你可以进行嵌套式管理:调用此模块去管理另一个文件,另一个文件中还使用了此模块。

如此反复,可以实现任意复杂的多进程多线程模型。当然我不建议你做的太复杂,尽可能扁平化最好管理。

示例

通过调用runChilds,传递一个配置数组即可,每个元素就是一个要启动的子进程配置说明。


'use strict'

const cdpc = require('cdpc')

//开启strong模式,监听'uncaughtException' 和 'unhandledRejection'事件不退出。
//还可以传递参数设置自定义监听函数,两个监听函数对应的事件顺序就是:
//    'uncaughtException' 'unhandledRejection'

let cm = new cdpc({
  debug: true,
  //收到SIGTERM、SIGABRT、SIGINT信号不退出。
  notExit: true
})

cm.strong()

cm.runChilds([

    {
        name : 'api',
        file : 'app.js',
        args : ['--port', 2021],
        options : {
            stdio: ['ignore', 1, 2]
        }
    },

    {
        name : 'test',
        command : 'date',
        restart : 'count',
        restartLimit: 10,
        restartDelay: 1000,
        options : {
            stdio: ['ignore', 1, 2]
        }
    }
])

子进程配置选项详细说明

配置项 说明 必须 可选值
name 子进程应用的名称 自定义,建议名称必写,方便管理。
command 要运行的命令 若是运行js文件,默认会使用当前node版本。
file 要运行的js文件路径 快捷选项,最终会把此选项指定的文件放在args中作为参数。
args 运行命令要传递的参数 默认为空,具体传递参数自行定义。
options spawn接口的options选项 参考child_process.spawn文档。
callback 创建子进程后的回调函数 回调函数传递的第一个参数是spawn的返回值,就是ChildProcess实例,第二个参数是cdpc实例。
onError error事件的回调函数 方便错误处理提供的选项,所有事件回调都可以在callback中自行定义。
restart 重启模式 默认为always,可选值:always,count,none,fail,fail-count。
user 指定以某个用户身份运行 只针对Linux、类Unix有效,指定的用户必须在/etc/passwd中有记录。
group 指定以某个用户组身份运行 只针对Linux、类Unix有效,指定的用户组必须在/etc/group中有记录。
cgroup 指定Linux cgroups控制组,默认为空表示不做资源控制。
monitor 是否开启监控,开启后会监控此进程的CPU、内存 true或false。
stopTimeout stop应用之后的定时器毫秒数值 取值范围:5 ~ 600000。包括边界值。
restartDelay 重启延迟 毫秒数,默认为延迟1000毫秒重启。
restartLimit 重启上限 当restart模式为count,则会通过计数和此值比较。
autoRemove 自动移除 只有在restart为count、fail、fail-count时有效,表示当应用运行完成后,自动移除。
lockReload 设置为true,重加载配置不会停止此应用 有些应用是直接通过runChilds运行的,并无配置文件,若触发reLoadConfig操作则导致这些应用不会再次加载,此时lockReload可以避免它们被停止。
after 声明关系依赖 用于声明此应用要在哪些服务运行之后再启动。可以传递一个字符串,或字符串数组,参数是所依赖服务的名字。
monitorNetData 是否监控网络数据 默认是false,当开启后,会在loadinfo.net上看到网络收发数据以及一段时间内的速率。
env 环境变量,object类型 此配置表示扩展添加的环境,如果是options.env配置是直接覆盖。

如果配置项monitor设置为true,需要调用monitorStart开启监控。

file和所在路径

当你指定file的时候,会自动在创建子进程的时候让子进程的工作目录在file文件所在目录。

restart模式

  • always 表示总是重启。
  • count 是表示重启次数有上限。
  • none 表示不重启。
  • fail 表示失败后重启,通过检测退出状态码(code)是不是为0。
  • fail-count 表示失败重启计数,只有在检测退出状态码不是0,并且计数不超过限制的情况下才会重启。

关于user和group

如果直接设定options的uid和gid会比较麻烦,需要去查看文件,而不同发行版或者同一发行版的不同版本其用户的uid和gid也可能不同。

Linux上默认就有很多为服务提供的系统用户,比如在各个不同发行版中基本都会有nobody、www、www-data等经常用于Web服务的系统用户。所以通过名称来指定用户和用户组是更好的选择。

当指定了user和group,则会自动去对应的配置文件解析查找出对应的uid和gid,如果不指定group,则使用user默认的uid和gid。

对用户和组进行缓存

指定了用户和组,会读取文件进行解析,但是这是一个同步处理的过程。所以提供了一个缓存机制。

首次解析用户会把解析后的结果加入到this.linuxUsers和this.linuxGroups进行缓存,格式如下:


//this.linuxUsers
{
  www: {uid: 123, gid: 126}
}

//this.linuxGroups
{
  www: {gid: 126}
}

如果想避免首次执行进程在指定用户和组的情况下去读取文件进行解析,可以自定义处理函数,预先解析好数据,并按照对应的格式,设置两个缓存变量的值。

Linux发行版默认的user和group文件路径:

  • /etc/passwd

  • /etc/group

如果你使用的Linux发行版对目录结构做了调整,可以通过配置来指定:


//假设你使用的系统把默认的配置文件放在了/usr/etc。

let cm = new cdpc({
  userFile: '/usr/etc/passwd',
  groupFile: '/usr/etc/group'
})

自定义事件

若要针对创建的子进程做事件处理,则可以在callback中完成,示例:

'use strict'

const cdpc = require('cdpc')

let cm = cdpc({
  debug: true
})

cm.runChilds([

    {
        name : 'testapp',
        command : 'date',
        restart: 'count',
        restartLimit: 10,
        restartDelay: 1000,
        callback: (ch) => {
            ch.stdout.on('data', data => {
                console.log(data.toString())
            })
        }
    }

])

指定用户和用户组

'use strict'

const cdpc = require('cdpc')

let cm = cdpc({
  debug: true
})

cm.runChilds([

    {
        name : 'web-service',
        file : 'app.js'
        user : 'www-data',
        group: 'www-data',
        options : {
            stdio : ['ignore', 1, 2]
        }
    }
])

其中的app.js是你使用web框架编写的服务程序。无论是直接通过options指定uid和gid还是使用user和group指定用户名,只有root用户有权限这样做,所以这个程序必须以root身份运行才可以成功,你需要用sudo。

假设以上代码的文件名是chld.js:

sudo node chld.js

name选项和应用管理

以下演示的pause、resume、stop、start、remove、restart都是基于name的,也就是说你要给应用命名。

暂停和恢复、停止和启动

'use strict'

const cdpc = require('cdpc')


let cm = cdpc({
  debug: true
})

cm.runChilds([

  {
      name : 'tofile',
      file : 'tofile.js',
      user : 'www',
      options : {
        stdio: ['ignore', 1, 2]
      }
  },

])

//5秒之后暂停tofile应用,此时应用程序不销毁,还在内存里。
setTimeout(() => {
  cm.pause('tofile')
}, 5000)

//15秒后恢复tofile应用,resume用于恢复pause暂停的应用。
setTimeout(() => {
  cm.resume('tofile')
}, 15000)

//25秒后停止tofile应用,此时应用销毁,子进程停止。
setTimeout(() => {
  cm.stop('tofile')
}, 25000)

//35秒后启动tofile应用,start用于启动stop停止的应用。
setTimeout(() => {
  cm.start('tofile')
}, 35000)

//45秒后重启tofile应用。
setTimeout(() => {
  cm.restart('tofile')
}, 45000)

stop和清理工作

stop接口会向指定的应用发送SIGTERM信号。5秒后检测是否还在运行,仍然运行则发送SIGKILL信号。

一个细节问题是,如果我使用了stop停止服务,但是如果服务子进程有一些资源需要清理,或者还需要向它自己的子进程发送通知,该如何处理?

子进程监听SIGTERM信号,当收到此信号,表示要退出,可用于后续任务安排后再选择退出。

stop支持第二个参数用于指定多少毫秒后检测是否运行并发送SIGKILL信号,默认为5000毫秒。若需要灵活的配置,可以在子进程配置项中通过stopTimeout指定。

移除和添加应用

remove通过name指定的名字来移除应用,移除应用是一个暴力操作,如果要安全移除,可以使用safeRemove,safeRemove会先进行stop,然后默认在5秒后进行remove操作。

safeRemove仍然支持第二个参数作为定时器超时检测的毫秒数值,safeRemove内部调用了stop,子进程配置项的stopTimout仍然会对此起作用。

add用于在运行时动态添加应用。

示例:

'use strict'

const cdpc = require('cdpc')


let cm = cdpc({
  debug: true
})

cm.runChilds([

  {
      name : 'tofile',
      file : 'tofile.js',
      user : 'wy',
      options : {
        stdio: ['ignore', 1, 2]
      }
  },

  {
      name : 'tofile2',
      file : 'tofile.js',
      user : 'wy',
      args : ['--port', 1235, '--https', '--session'],
      options : {
        stdio: ['ignore', 1, 2]
      }
  },

])

//25秒后安全移除tofile2应用,并添加subchld应用。
//subchld应用同样是一个使用cdpc模块管理子进程的应用。
//其内部应用也是几个Web服务程序。
setTimeout(() => {
  cm.safeRemove('tofile2')

  cm.add({
      name : 'subchld',
      file : 'mchld.js',
      user: 'www-data',
      group: 'www-data',
      options : {
        stdio: ['ignore', 1, 2]
      }
  })
}, 25000)

使用cgroup进行资源控制

在Linux上可以使用cgroups进行资源控制,cdpc在检测到是Linux平台会自动初始化cgroup功能模块,使用cgroup属性即可访问。


const cdpc = require('cdpc')

let cm = new cdpc()

//创建一个名为cf-test的控制组,设定模式为domain,这是默认值,type可以不传。
cm.cgroup.create('cf-test', {
  type: 'domain',
  //在10000时间片上,占有5000,就是50%占有率
  cpu: [5000, 10000],
  //内存占用~50M
  memory: 50000000
})

cm.runChilds([
  {
    name: 'xxx',
    file: './a.js',
    cgroup: 'cf-test'
  }
])

cdpc初始化选项

配置项 说明 可选值
debug 是否启用调试模式。 true或false
signalHandle 信号处理函数,不设置采用默认处理,参考process.on的信号事件。 函数,接收参数signal
onExit process的exit事件回调函数,不设置则采用默认处理。 函数
signals 要处理的信号有哪些,默认为SIGINT、SIGABRT、SIGTERM 数组,示例:['SIGABRT', 'SIGTERM']
errorHandle 统一的错误处理函数,接收参数第一个是error,第二个是错误描述的辅助标记名称。 函数,示例:(err, errname) => {}
eventDir fs.watch事件目录,默认/tmp/cdpc_watch 对重新加载配置、重启、暂停、恢复应用等操作的文件事件目录。
notExit 不退出应用,默认为false,设置为true则会监听信号不退出。 若自定义signalHandle,则需要自行处理。
config 配置文件路径,配置格式和runChilds接收参数一致。 json或js类型,若是js则必须用module.exports导出模块。
loadInfoType 负载信息的格式,json格式主要用于程序解析。 text或json。
loadInfoFile 负载信息的写入文件路径。 若是不设置则输出到终端。
showColor 在终端输出是否显示颜色。 true或false。
userFile Linux用户信息文件路径,默认为/etc/passwd。 若非特殊发行版或更改了配置路径不要修改此值。
groupFile Linux用户组信息文件路径,默认为/etc/group。 若非特殊发行版或更改了配置路径不要修改此值。
notWatch 不监听文件事件。 true或false。若为true则eventDir设置的事件目录不再起作用。

以配置文件的方式加载。


const cdpc = require('cdpc')

const cm = new cdpc({
  debug: true,
  config: './config.js'
})

cm.loadConfig()

开启IPC

若要使用IPC通信,需要使用options.stdio选项:


const cdpc = require('cdpc')

const cm = new cdpc({
  debug: true,
})

cm.runChilds([
  {
    name: 'app',
    file: 'query-load.js',
    options: {
      //如果不需要输出信息,也可以是 ['ignore', 'ignore', 'ignore', 'ipc']
      stdio: ['ignore', 1, 2, 'ipc']
    },
    //cdp是就是cdpc实例,若是在配置文件中,独立出去的模块,这个参数可以让你操作cdpc实例上的接口。
    callback: (child, cdp) => {
      child.on('message', msg => {
        if (msg.type === 'query-load') {
          //把json格式的负载监控信息发送给子进程。
          child.send(cdp.fmtLoadInfo('json'))
        }
      })
    }
  }
])

对应的query-load.js文件的代码是:


'use strict'

const fs = require('fs')

process.on('message', msg => {
  fs.writeFile('/tmp/query-load.json', JSON.stringify(msg), err => {
    err && console.error(err)
  })
})

setInterval(() => {
  process.send && process.send({
    type: 'query-load'
  })
}, 1000)

启用监控

负载监控的定时器采用了时间片和步进式的策略,并且支持波动策略。


let cm = new cdpc()

//...
/*
  定时器每10毫秒执行一次,每执行一次,步进计数器加1。
  步进从100到105开始波动,采取的方案是:计数到100开始读取监控信息,然后是计数到101开始读取监控信息,
  直到步进计数达到105,然后开始计数到104进行负载信息的获取,直到步进达到100的状态。
  如此反复。
  dynamicStep = 5 设定动态步进为5,默认是1。这表示每次增长是5,就是50、55、60...
*/
cm.dynamicStep = 5
cm.setStepSlice(10)
cm.setMaxStep(50, 105)

cm.monitorStart()

cdpc.prototype.setStepSlice

设定定时器时间片

cdpc.prototype.setMaxStep

设定步进最大计数

步进数和定时器时间片相乘就是获取负载信息的时间间隔。

Net部分

若要获取进程的网络数据统计,请传递选项:monitorNetData,设置为true。

重置网络数据统计

cdpc.prototype.resetNetData(name)

注意: 使用resetNetData重置网络统计数据,会把统计项的几个属性设置为0,但是loadinfo.net.devData直接一个新的空对象替换。

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

cdpc是Node.js的子进程管理模块,可管理任何编译的二进制程序、脚本。方便直接利用nodejs运行服务。可整合cluster和worker_threads模块做任意的多进程多线程通信模型。 展开 收起
JavaScript
MulanPSL-2.0
取消

发行版 (30)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/daoio/cdpc.git
git@gitee.com:daoio/cdpc.git
daoio
cdpc
cdpc
master

搜索帮助