zoukankan      html  css  js  c++  java
  • 终端多窗口管理旗舰screen

    ######################################################
    终端多窗口管理

    ------tmux byobu screen terminator谁与争锋之screen
    #########################################################

    ###################
    GNUscreen命令详解
    ###################
                                                                                                                        


    #######
    引   言:
    #######

        "你是不是经常需要 SSH 或者 telent 远程登录到 Linux 服务器?你是不是经常为一些长时间运行的任务而头疼,比如系统备份、ftp 传输等等。通常情况下我们都是为每一个这样的任务开一个远程终端窗口,因为他们执行的时间太长了。必须等待它执行完毕,在此期间可不能关掉窗口或者断开连 接,否则这个任务就会被杀掉,一切半途而废了。
    元凶SIGHUP 信号
    让我们来看看为什么关掉窗口/断开连接会使得正在运行的程序死掉。
    在Linux/Unix中,有这样几个概念:
    * 进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组ID,即进程组长进程的ID。
    * 会话期(session):一个或多个进程组的集合,有唯一一个会话期首进程(session leader)。会话期ID为首进程的ID。
    * 会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其余进程组称为后台进程组。
    根据POSIX.1定义:
    * 挂断信号(SIGHUP)默认的动作是终止程序。
    * 当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)。
    * 如果会话期首进程终止,则该信号发送到该会话期前台进程组。
    * 一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中所有进程。
        因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出。
        如果我们可以忽略SIGHUP信号,关掉窗口应该就不会影响程序的运行了。nohup命令可以达到这个目的,如果程序的标准输出/标准错误是终 端,nohup默认将其重定向到nohup.out文件。值得注意的是nohup命令只是使得程序忽略SIGHUP信号,还需要使用标记&把它放 在后台运行。"

    此段引自前辈:田 强 (tianq@cn.ibm.com), 软件工程师, IBM中国软件开发中心 2007 年 7 月 31 日

        的确,相信很多朋友都有过这样的经历。在使用telnet或SSH等远程登录linux时,在服务器上跑一些程序,有时候得跑很长时间(超过12小时)这 时如果程序没结束就退出远程管理终端,远程跑的程序很有可能就此挂点;此外,可能也经常需要在多个程序之间来回切换,怎么办?ctrl-z和fg、bg nohup?这些太麻烦,且时常捉襟见肘。更让人无法忍受地,如果连接非正常中断,重新连接时,系统将开启一个新的session,无法恢复原来的 session。
        以前常用的解决方式是用命令nohup但nohup有很多解决不了的问题。值得庆幸的是GNU screen命令可以很好地解决这个问题。
        linux的screen命令,对于远程登录来说,不仅提供了类似于nohup的功能,而且提供“多个桌面”的功能。screen工具是一个终端多路转接器,在本质上,这意味着你能够使用一个单一的终端窗口运行多终端的应用。
       简单讲,screen是一个可以在多个进程之间多路复用一个物理终端的窗口管理器。Screen中有会话(session)的概念,用户可以在一个 screen会话中创建多个screen窗口(window),在每一个screen窗口中和实际操作一个真实的telnet/SSH连接窗口没什么区 别。


    #######
    安   装:
    #######


       流行的Linux发行版(例如Red Hat,CentOS,fedora,ubuntu,openSUSE等等)通常会自带GNU screen实用程序。如果没有的话,可以从GNU screen的官方网站下载。

       通常对于rpm系列的linux,安装screen,可以通过yum在线安装或本地ISO yum源安装,这里就不多赘述
    sudo yum install screen     #yum在线安装
    sudo yum --disablerepo=\* --enablerepo=c6-media install screen
    #本地ISO yum源安装


       对于deb系列,如ubuntu,也可以通过官方源在线安装和本地ISO源安装
    sudo apt-get install screen




    #########
    语     法:
    #########


    screen [-AmRvx -ls -wipe][-d ][-h ][-r ][-s ][-S ]

    补充说明:
    screen为多重视窗管理程序。此处所谓的视窗,是指一个全屏幕的文字模式画面。

    参  数:

    -4        使用ipv4
    -6        使用ipv6
    -a        all,包括所有的功能
    -A        将所有的视窗都调整为目前终端机的大小。
    -c file     使用配置文件file,而不使用默认的~/.screenrc
    -d|-D [pid.tty.host] 将指定的screen会话暂时中断,可通过screen -r来恢复。
    -dmS name    启动一个初始状态断开的screen会话daemon
    -D        暂时中断会话并注销
    -e xy        改变命令行映射
    -h lines      指定历史回滚缓冲区大小为lines行
    -f        开启follow control
    -i        当follow control开启时,中断输出
    -l        开启登陆模式
    -list或-ls     显示目前所有的screen会话,格式为pid.tty.host
    -L        开启output日志功能
    -m        忽视$STY变量,强制新建screen会话。
    -O        指定输出来代替默认的VT100
    -p window            如果存在己经命名过的window则选定所指定的window
    -q        安静启动。如果不成功则返回非零值
    -r sessionowner/ [pid.tty.host]     重新连接一个断开的会话。多用户模式下连接到其他用户screen会话需要指定sessionowner,需要setuid-root权限
    -R        先试图恢复中断的会话。否则建立新的screen会话。
    -s shell    指定screen会话所要执行的shell。
    -S sessionname  创建screen会话时为会话指定一个名字
    -t title    设置window的标题
    -T term        使用指定的term来作为$TERM对对所有的window而言,而不是screen
    -U        告诉screen使用UTF-8编码
    -v         显示screen版本信息
    -wipe       清除已经无法使用的screen会话,格式为pid.tty.host
    -x             多重显示模式,用于多会话多用户同时联机
    -X             在指定的会话中自动运行一个screen命令


    ##############
    常用参数举例:
    ##############


    screen -S hello        -> 新建一个叫hello的session
    screen -ls                -> 列出当前所有的session
    screen -r hello        -> 恢复hello这个session
    screen -d hello       -> 远程detach某个session
    screen -d -r hello    -> 结束当前session并恢复hello这个session


    ##################
    screen键绑定(默认)
    ##################

    #screen的默认控制符,所有的快捷键都由^A开始
    Commandkey:     ^A   Literal  ^A: a

    #帮助及退出相关window
    C-a ?           (help)             显示键绑定列表帮助
    C-a C-\           (quit)             kill掉所有的window并退出screen
    C-a k|C-k       (kill)             kill掉当前window
    C-a X           (remove)         kill掉当前分割的window
    C-a Q           (only)             关闭所有分割的window,仅保留当前的window

    #命令行模式
    C-a :           (colon)             命令行模式
    如:
    C-a :screen      新建 screen 终端,并运行命令
    C-a :resize            改变当前窗口高度为
    C-a :quit                              退出 screen,将关闭所有 screen 终端

    #新建切换window
    C-a c|C-c       (screen)            新建window
    C-a h|p|C-p    (prev)               切换到上一个window
    C-a space|n|C-n    (next)       切换到下一个window
    C-a ’           (select)                在提示符下交互地输入要切换的window编号来切换window
    C-a "           (windowlist -b)   列出当前session下所有的window以供切换
    C-a [0-9]    (select [0-9])       切换到编号为[0-9]的window
    C-a -           (select -)            切换到一个空白的window
    C-a C-a           (other)           在两个最近使用的window间切换
    C-a A           (title)                 重命名当前所在的window

    #分割窗口及切换
    C-a S           (split)             横向分割当前window
    C-a tab           (focus)             在横向分割的窗口间进行切换
    C-a F           (fit)             将所有window的尺寸调整到同当前window一样

    #中断session
    C-a d|C-d       (detach)         暂时中断当前session,即将目前的session放到后台执行,并回到shell状态,此时在screen session里每个window内运行的process(无论前台/后台)都在继续执行,即使logout也不影响。
    C-a D            (pow_detach)         中断session并注销

    #显示相关信息
    C-a ,           (license)         显示screen的简介信息
    C-a v           (version)         显示版本信息和编译日期
    C-a i|C-i       (info)             显示当前窗口的一些信息
    C-a N           (number)         显示当前window编号及标题
    C-a m|C-m       (lastmsg)         显示上一次的提示信息
    C-a t|C-t    (time)             显示系统时间和平均负载
    C-a w|C-w    (windows)         显示所有己开启的window列表
    C-a *           (displays)         显示登录信息

    #关闭或开启标准输出
    C-a q|C-q       (xon)             向当前window发出一个control-q信号
    C-a s|C-s    (xoff)             向当前window发出一个control-s信号

    #功能设置相关
    C-a r|C-r    (wrap)             是否设置automatic margins
    C-a C-v           (digraph)         设置digraph
    C-a W           (width)             设置列宽80/132
    C-a f|C-f       (flow)             设置flow on, off or auto.
    C-a C-g           (vbell)             切换到虚拟响铃模式(vbell)

    #清屏,锁屏,挂起
    C-a x|C-x    (lockscreen)         锁屏,锁住当前window,需用户密码解锁
    C-a z|C-z    (suspend)         挂起,把当前session放到后台执行,用shell的fg命令恢复到前台
    C-a C           (clear)            清屏
    C-a l|C-l       (redisplay)         刷新当前window
    C-a Z           (reset)             重置环境变量到screen开启时的最初状态
    C-a {|}           (history)         复制并粘贴上一次命令行,类似bash命令行的C-p

    #复制粘贴模式相关
    C-a [|C-[|esc    (copy)             进入到copy/scrollback模式,可以复制,回滚和搜索,命令模式与vi相同
    C-a esc|]    (paste .)         粘贴buffer
    C-a =           (removebuf)         删除buffer
    C-a <           (readbuf)         从文件中读取buffer
    C-a >           (writebuf)         将buffer写入文件

    #monitor功能
    C-a _           (silence)         开启或关闭monitoring
    C-a M           (monitor)         是否将当前window设置为monitored

    #login相关
    C-a I        (login on)        开启login
    C-a O        (login off)        关闭login
    C-a L           (login)             切换当前window到login

    #写入文件相关
    C-a h           (hardcopy)         将当前window的快照写入到当前目录的"hardcopy.n".
    C-a H           (log)             将当前window的日志记录到当前目录的"screenlog.n".
    C-a .           (dumptermcap)         写入".termcap"
    ###################################################################

    #########
    常见用法:
    #########


    *******
    启动
    *******


       1.直接在命令行键入screen命令

        screen

        直接敲入命令screen会创建一个跑着shell的screen session。在这个session里,你所做的一切操作和实际shell里的操作完全一样,最多可能在键值上有些许的交叉,但几乎不会有什么影响。 键入exit可以退出该window,如果这是该screen session的唯一window,则该screen session退出,否则screen自动切换到前一个window。

       2.Screen命令后接要执行的程序。

       screen ranger     #screen创建一个执行ranger的单窗口会话,退出ranger也将退出该窗口/会话。
       screen mplayer -vo fbdev2 -zoom -x 1024 -y 768 foo.rmvb     #screen创建一个执行mplayer的单窗口,同样,退出ranger也将退出该窗口/会话。

       3.以上两种方式都创建新的screen会话。
       我们还可以在一个已有screen session中创建新的window。在当前screen窗口中键入C-a c,即Ctrl键+a键,之后再按下c键,screen 在该会话内生成一个新的window并切换到该window。同时,screen可以在不中断screen窗口中程序的运行而暂时断开 (detach)screen会话,并在随后时间重新连接(attach)该会话,重新控制各窗口中运行的程序。
       而给screen发送命令使用了特殊的键组合C-a。这是因为键盘上键入的信息是直接发送给shell,所以必须用其他方式向screen窗口管理器发 出命令,默认情况下,screen接收以C-a开始的命令。这种命令形式在screen中叫做键绑定(key binding),C-a叫做命令字符(command character)。
        通过C-a d暂时中断刚创建的session(回到进入screen前的shell环境),然后再敲入screen则可再创建一个新的screen session。这样,你可以建立多个session,再建立多个window来满足系统管理的多窗口需求,比起Ctrl+Alt+F1...F7这样在 多个控制台切换来得方便实用且高效,当然,比"nohup [argument…] &"(虽然nohup很容易使用,但还是比较“简陋”的,对于简单的命令能够应付过来,对于复杂的需要人机交互的任务就麻烦了) 也更实际得多。此外,即使从系统中注销原来在screen session中的程序或进程也仍在继续运行,真的是相当方便咧。
           在shell(没有进入screen之前)环境下,可以通过:

        screen -d              #detach某个session
        screen -ls             #查看所有的screen sessions
        screen -r     #进入sessionid所指定screen session
        exit             #关闭当前window,并且切换到下一个window(当退出最后一个window时,该终端自动终止,并且退回到原始shell状态)

    *************
    分割窗口
    *************

        GNU screen默认提供了一个水平(横向)分割窗口的功能,目前还不支持垂直(纵向)分割,这一点上,以BSD协议发布的tmux就更胜一筹,详情请参阅tmux篇。
        按下Ctrl-A再按一下S(大写的S)就会看到屏幕被水平等分成两部分,可以通过C-A Tab来再分割的窗口间跳转。
        调整窗口尺寸,可以在命令行模式进行调整,如:

        resize +N   #增加当前分割窗口N行
        resize -N   #减少当前分割窗口N行
        resize    #指定当前分割窗口为N行
        resize    #让所有的window一样高
        resize max  #让当前的window扩大到最高
        resize min  #让当前的window减少到最低

        可以将键绑定写入到~/.screenrc中,如:
            bind + resize +3
            bind - resize -3
            bind = resize =
            bind m resize max
        之后,通过source ~/.screenrc重新加载配置后便可以使用新的键绑定来调整窗口大小了!
    C-a +                     扩大当前窗口,增加3行
    C-a -                     缩小当前窗口,减小3行
    C-a F           (fit)             将所有window的尺寸调整到同当前window一样

        当然,这样设置与某些键绑定重复,但不影响使用,如果喜欢这个功能可以牺牲某些用之甚少的功能么!呵呵!
        设置后以~/.screenrc中的键绑定为主,有兴趣的朋友可以多试试!


    ****************
    转义控制符
    ****************

       screen默认把C-a看作是screen命令的开始,所以如果想要screen窗口接收到C-a字符(C-a在shell环境下默认跳到命令行的开头),就要输入C-a a。
       screen也允许使用-e选项自定义命令字符和转义字符,其格式为:

       -exy x为命令字符,y为转义命令字符的字符,如:
       screen -e^tt     #指定命令字符为C-t,转义C-t的字符为t

       当然,这只是临时性的修改了命令字,待系统重启后则又恢复到默认状态,那么有没有一种方法可以永久性地修改并保存命令字呢!答案是肯定的,可以编辑~/.screenrc或全局文件/etc/screenrc,建议修改用户各自的自定义文件~/.screenrc,增加以下选项即可。
       escape ^Tt         #定义screen的功能键为Ctrl-T

        思考:有的朋友肯定会问,为什么要设置为Ctrl-T呢?其它键绑定不行吗?
        问的好,确实,键值可以依个人喜好任意设置,但我们知道,unix-like系统的快捷键多如牛毛,很多键都是shell默认的绑定键,所以找个空缺的键还真的比较困难,但我们可以根据击键方便度和使用频率来加以取舍, 个人觉得,Ctrl-T是个不错的建议,因为,在shell(bash)下,Ctrl-T是交换相邻两个字符的位置,Ctrl-A是跳到命令行的开头,从 使用频率来看,Ctrl-A用的频繁得多,而当命令字设为Ctrl-T后,需要交换字符的位置时,按下Ctrl-T t也可以达到字符交换位置的效果。




    **************
    Copy模式
    **************


    C-a [         可以进入copy模式,此模式的快捷键与vi相同,如:
    C-b            Backward,PageUp         向上翻一页
    C-f             Forward,PageDown         向下翻一页
    C-d           Backward,Half PageUp     向上翻半页
    C-u           Forward,Half PageDown     向下翻半页

    H Head        屏暮的顶部
    M Middle     屏幕的中间
    L Last         屏幕的底部

    gg 整个缓冲区的第一行
    ng 整个缓冲区的第几行,n为行数,如:第3行则为,3g
     整个缓冲区的最后一行

     行首
     行末

     以词为单位向前移动到下一个词的开头
     以词为单位向前移动到下一个词的结尾
     以词为单位向后移动到上一个词的开头
     以空格为单位向前移动到下一个词的开头
     以空格为单位向前移动到下一个词的结尾
     以空格为单位向后移动到上一个词的开头

        Space第一次按为标记区域的起点,第二次按为终点(回车也可以),这样,起点和终点间的字符即为所复制的部分

           粘贴所复制的区域
        Esc      退出copy mode


    :copy模式的更多的快捷键请自行参考vi



    *********************
    多session实例:
    *********************

        通过ps -e|grep screen我们可以看到,
    7153 ?        00:00:02 screen
    7391 pts/8    00:00:00 screen
    7392 ?        00:00:00 screen
    7615 pts/1    00:00:00 screen
        目前有4个screen进程在运行

        现在我开启了3个session,其中有两个(liujun,ubuntu)是Detached状态,一个(test)是Attached状态。
    liujun@ubuntu:~$screen -ls
    There are screens on:
            7415.liujun     (2012年06月10日 19时52分00秒)   (Detached)
            7392.test       (2012年06月10日 19时51分30秒)   (Attached)
            7153.pts-1.ubuntu       (2012年06月10日 18时12分50秒)   (Detached)
    3 Sockets in /var/run/screen/S-liujun.

        当我们想恢复某个session且只开启了一个session时,只需要键入screen -r即可恢复。而当有多个session时,系统将提示选择一个
        如:对于"test"这个session是不能够通过screen -r 7392来恢复的,因为它本来就没有detach,所以根本无需recover,当我们要恢复ubuntu这个session时我们可以这样:
        screen -r 7153

        如果由于某种原因其中一个session死掉了(例如人为杀掉该会话),这时screen -li会显示该会话为dead状态。可以使用screen -wipe命令清除该session:

        screen -wipe

       "-d –m" 选项是一对很有意思的搭档。因为他们启动的是一个开始就处于断开模式的会话。你可以在随后需要的时候连接上该会话。有时候这是一个很有用的功能,比如我们可以使用它调试后台程序。
       该选项一个更常用的搭配是:-dmS sessionname

       screen -dmS ubuntu test xiao   #这样就启动了3个一开始就处于离线状态的session
       screen -r ubuntu           #连接(恢复)该会话



    ****************
    重命名窗口
    ****************


        通常我们在使用过程中都会开启好多个window,而当按下C-a w查询window列表时,给出来

    1$ bash  2-$ bash  3*$ bash

        少还好,一旦超过5个,哇,那真的是有些头晕,毕竟每个都叫bash切换时总会时不时的打搅,很影响心情的。所有我每次都会重命名几个window,这样既方便记忆又方便切换,真的是不错的功能噢!

    C-a A(大写A)可以轻松地(根据自己的喜好或window功能)进行重命名。如:我常用这几个,哈,什么意缊这里就不多作阐释了,自己心里有数就行!呵呵,

    1*$ ubuntu  2$ xiao  3-$ hello




    *********************
    指定session
    *********************


        缺省情况下screen使用PID.TTY.HOST的命名规则,太多的session,很可能太多的PID让你眼花缭乱,所以指定一个好记的名字,就显得重要起来
    screen -S ubuntu    #可开启一个名叫ubuntu的session
    screen -dmS ubuntu  #可开启一个名叫ubuntu的且初始状态是断开状态的session




    *******************
    管理远程会话
    *******************

        使用ssh等远程连接linux服务器工作的时候,比如需要编译某些程序而这时网络不稳定断线,工作就白干了,又的重新连接重复刚才的工作,这时候screen就可以派上用场了。
        只要别杀掉screen会话, 把多窗口的管理任务放心地交给screen吧,screen一定不会让你失望,screen能帮你“保存”你想继续运行的任务,并在你需要恢复的时候随时待命,怎么样,是不是相当方便呢!
        你需要做的只是打开一个terminal(或Putty)通过ssh(telnet,rsh等)登录主机,再键入screen创建需要的screen session,退出的时候C-a d“保存”你的工作,下次登录后直接screen -r 就可以了。
        当然,最好能给每个窗口起一个名字。使用C-a A给window起名,使用C-a w查看window列表。




    ****************************
    同步显示与操作功能
    ****************************

        1.当几个分割的窗口处于同一window时,则输出是完全一模一样,完全同步。比如:水平分割的两个窗口,上面一个是bash1,下面一个也是bash1的话,那么这两个分割的窗口将保持同步,有点像金山打字通的感觉,呵呵!

        2.screen 给我们提供了一个"-x"选项,这个选项可相当神奇噢!这也是所谓的“多重显示模式”,即在同一个session下能在不同的终端显示并共同控制窗口动作。
        简单讲,她不仅能同步显示你的屏幕给另一个会话(有点类似于文本界面下远程协助或桌面噢!),而且同时联机的两个终端都能对该window进行控制操作。 这在帮别人处理问题可是一个相当好用的功能咧,因为这样既可以让对方同步看到你的操作,也可以实地检验对方的操作,就像实地演示手把手交一样,省下很多后 续解释工作呢!

    方法:

      a.双方同时登陆linux主机    #同一个session多个用户同时attach

      b.一方(演示方)运行:
      screen -S test               #开启一个session,我这时命名为test
        mocp test.m3u            #用mocp播放mp3

      c.一方(观看方)运行:
      screen -x test            #通过multi display mode来attach同一个会话

        怎么样,是不是两边都能同时显示并同时操控呢!很好玩呢,赶快尝试一下吧!




    **********************
    强大的可定制性
    **********************


       GNU screen为我们提供了丰富强大的定制功能。
       你可以在screen的默认两级配置文件/etc/screenrc和$HOME/.screenrc中指定更多的个性化设置。包括设定screen选 项,定制绑定键,设定screen会话自启动窗口,启用多用户模式,定制用户访问权限控制等等。如果你愿意的话,也可以自己指定screen配置文件。
       例如:以多用户功能为例,screen默认是以单用户模式运行的,你需要在配置文件中指 定 multiuser on 来打开多用户模式,通过acl*(acladd,acldel,aclchg...)命令,你可以灵活配置其他用户访问你的screen会话。更多配置文 件内容请参考screen的man页。

    startup_message off       #启动时不显示欢迎屏幕
    escape ^Tt                      #定义screen的功能键为Ctrl-T
    defscrollback 1024          #屏幕缓冲区1024行
    vbell on|off                       #关闭错误提示
    vbell_msg "Wuff--Wuff!"  #错误提示消息文字
    bind w windowlist            #按C-a w或C-a C-w显示窗口列表
    defflow on                       # 强制screen去接收^S^Q信号
    hardstatus off                 # 关闭hardstatus
    hardstatus string "%h%? users: %u%?"       #设置hardstatus字符串
    hardstatus onhardstatus alwayslastlinehardstatus string "%{.bW}%-w%{.rY}%n %t%{-}%+w %=%{..G} %H(%l) %{..Y} %Y/%m/%d %c:%s "                                    #在最下一行显示窗口列表和时钟
    hardstatus lastline "%-Lw%{= BW}P>%n%f* %t%{-}%+Lw%<"                #在屏幕的底部列出所有window的名称并以蓝色高亮显示当前window
    termcapinfo xterm*|rxvt*|kterm*|Eterm* hs:ts=\E]0;:fs=\007:ds=\E]0;\007    #在gui终端界面中设置hardstatus的标题和图标





    *************
    参考资料
    *************

       * “Advanced Programming in the UNIX® Environment: Second Edition” W. Richard Stevens, Stephen A. Rago 提供了更多关于Linux/Unix进程关系、信号的知识。
       * GNU Screen的官方网站:http://www.gnu.org/software/screen/
       * Screen的man page提供了最详细的信息:http://www.slac.stanford.edu/com ... nsole/screen.1.html
       * http://hi.baidu.com/phps/blog/item/790973c6c29c8d159c163d0c.html
  • 相关阅读:
    Oracle SQL语句大全—查看表空间
    Class to disable copy and assign constructor
    在moss上自己总结了点小经验。。高手可以飘过 转贴
    在MOSS中直接嵌入ASP.NET Page zt
    Project Web Access 2007自定义FORM验证登录实现 zt
    SharePoint Portal Server 2003 中的单一登录 zt
    vs2008 开发 MOSS 顺序工作流
    VS2008开发MOSS工作流几个需要注意的地方
    向MOSS页面中添加服务器端代码的另外一种方式 zt
    状态机工作流的 SpecialPermissions
  • 原文地址:https://www.cnblogs.com/lixuebin/p/10814676.html
Copyright © 2011-2022 走看看