`
xiaozhonghua
  • 浏览: 251266 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

小demo带来大项目

阅读更多

今天心情不错,公司终于签下了一个综合业务监控系统的大单。到底有多大我也不知道,反正连软件带硬件不算小。按照销售的说法,拿下这个项目一个重要的因素就是要提供一个吸引眼球的demo,而我们做的不错!今天和大家分享一下喜悦和经验!

 

这个项目是一个省级电信运营商的综合业务监控系统。公司跟了好长时间了。由于是一个综合的业务监控系统,涉及的管理资源非常多,又要提供大屏幕显示,所以对系统的呈现效果要求非常高(“政绩工程”么)。前前后后提了很多“无礼”要求,陆续提过的有3D电子地图、3D机房监控、场景仿真、全动画、Google Earth、全Flash等等....弄的我们晕头转向、焦头烂额。其实很多时候,用户自己也不知道想要什么,反正对厂商的要求就是一个字:“炫”,两个字“好看”,三个字:“一定好好看!”(不对,好像是四个字哦)。

 

言归正传,项目跟踪过程中,商务始终告诉我们研发一定要做好一件事:如何做好呈现,尤其是综合的业务监控和呈现,这是获得项目的关键,一定要“出彩”。这个“综合呈现”说起来容易,做起来难。作为省级的电信运营商,内部的各种软硬件系统无数,要监控从上到下、从软到硬,真是烦不胜烦:

  • 基础设施层:主要是网络上的硬件设备,包括交换机、路由器、防火墙、各种主机设备服务器等等;
  • 软件层:这一层主要是主机上面运行的操作系统和各类业务软件系统,例如操作系统(Windows、AIX/HP-UX/LINUX/SOLARIS)、各种中间件(WebLogic、JBoss、IIS、WebSphere等)、数据库(Oracle、Sybase、MySQL)等;
  • 应用层:这一层是指运行在软件层内部的一些细粒度资源,包括一些关键的软件进程、数据库表空间、业务连接、消息队列等等。这一层数量繁杂、数量众多。不过这些资源的状态直接会影响其上层支撑的业务。
  • 业务层:业务层是最顶层,由以上底层资源所共同支撑起来的面向用户的各种业务。对业务最容易理解的描述就是电信提供给客户的具体“服务”。例如视频业务、短信业务、WAP业务、专网业务等等。这些业务才是用户最终关心的东西,因为这些业务才是客户的核心资产,是拿来卖钱的最终产品。一旦出问题,将直接影响money!

此外,还有一大堆机房环境监控的要求,什么配电柜供电、开关状态、UPS、蓄电池、空调、适度温度漏水消防通风门禁视频.........一大堆。所以,要对业务进行监控,就必须对业务所支撑的各个底层资源进行监控。另外,还要能够对这些资源的关系进行连接和定义,一旦发生故障和问题,能够从上到下迅速定位故障起源,在最短时间内发现问题、解决问题。如何呈现这些依赖关系,并对故障和告警进行纵向定位,是软件呈现的一个核心问题,也是用户最关心的一个问题。

 

用户要求我们先制作一个demo程序,看我们将如何呈现“综合监控”的效果。在充分了解了用户需求之后,经过讨论,我们想做一个完全图形化的分层、跨层的综合监控界面。界面要美观,有动画效果,能够清晰的显示资源依赖关系和告警传播定位。

需要监控和管理的资源

 


 接下来要写代码了。肯定先用我熟悉的TWaver试试。研究了一下,TWaver中有一个平行四边形的Group对象,适合做上图中的“层”的概念。先如下封装并设置属性:

package demo;

import java.awt.Color;
import twaver.Group;
import twaver.TWaverConst;

public class LayerGroup extends Group {

    public LayerGroup() {
        init();
    }

    public LayerGroup(Object id) {
        super(id);
        init();
    }

    private void init() {
        this.setGroupType(TWaverConst.GROUP_TYPE_PARALLELOGRAM);
        this.putGroupAngle(45);

        this.putGroup3D(true);
        this.putGroupDeep(10);
        this.putGroupOutline(false);
        this.putGroupFillColor(Color.green.darker());
        this.putGroupGradient(true);
        this.putGroupGradientFactory(TWaverConst.GRADIENT_LINE_E);
        this.putGroupHandlerVisible(false);
        this.putGroupDoubleClickEnabled(false);
        this.putBorderColor(Color.white);
        this.putBorderInsets(3);
        this.putBorderAntialias(true);
        this.putBorderStroke(TWaverConst.STROKE_SOLID_4);
        this.putBorderVisible(false);
        this.putLabelHighlightable(false);

        this.setEnableAlarmPropagationFromChildren(false);
    }
}

 通过这个简单的封装,再往Group里头放几个节点和连线,显示效果如下:

 

用Group制作的“层”效果

 

怎么样,有点意思吧?开头不错,继续改进!再依次排列4个Group,用不同颜色,试试效果:

createLayer(Color.orange, 50, 0, 10, "7.png", "<html><center>软件<br>业务层</center></html>");
createLayer(Color.green.darker(),180, 200, 15, "8.png", "<html><center>技术<br>应用层</center></html>");
createLayer(Color.magenta.darker(),280, 350, 5, "5.png", "<html><center>技术<br>软件层</center></html>");
createLayer(Color.cyan.darker(),400, 570, 7, "1.png", "<html><center>基础<br>设施层</center></html>");

以上代码封装了创建一个层的函数,给定颜色、坐标位置、内部节点数量、图标、文字等等。上面代码中的HTML风格字符串是为了在TWaver中(好像Swing中也是这样的)显示换行的标签。每一个层作为容器包含了很多不同类型的资源。显示效果如下图:

 

四层拓扑图显示效果

 

注意其中的连线有下垂的弯曲效果。这是我以前在做项目封装过的一个TWaver技巧:通过重写twaver的Link的UI类,重新指定path走向实现的。其实也很简单,首先获得link的from点和to点,取值中间点,再把y纵向增加20,把这个点作为quadTo的控制点画曲线即可。对TWaver熟悉的朋友可以看一下这段代码(其实这个效果也是从TWaver Java的demo源代码中学习到的):

package demo;

import java.awt.Point;
import java.awt.geom.GeneralPath;
import twaver.network.TNetwork;
import twaver.network.ui.LinkUI;

public class InnerLinkUI extends LinkUI {

    public InnerLinkUI(TNetwork network, InnerLink link) {
        super(network, link);
    }

    @Override
    public GeneralPath getPath() {
        GeneralPath customPath = new GeneralPath();
        Point p1 = this.getFromPoint();
        Point p2 = this.getToPoint();
        customPath.moveTo(p1.x, p1.y);
        int offset = 20;
        customPath.quadTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2 + offset, p2.x, p2.y);
        return customPath;
    }
}

 用这种link做出的拓扑图比较生动美观。多加几个节点连线就能看出来了:

 

四层复杂拓扑图显示效果

 

不过发现平行四边形Group一个问题:当两个Layer叠加后,下面的节点会被完全覆盖,看不见了。用户说:能不能也能看见?(晕,盖住了也要看见。谁让人家是甲方呢?)于是询问TWaver的人,一个哥们说Group有透明属性。于是试了一下,效果不还错:

this.putGroupOpaque(false);

 

层的透明与覆盖

 

下一步,关键了:要增加层与层之间资源的“依赖关系”。例如一个Oracle跑在一台主机上,而Oracle中的一个关键表空间需要重点监控,它决定了上层一个视频点播业务是否能够正常。为了体现这个依赖关系,在跨层的节点中间建立link。这个link和层内部link显示上应当有所区别:

package demo;

import java.awt.Color;
import twaver.Link;
import twaver.Node;
import twaver.TWaverConst;
import twaver.base.OrthogonalLinkDirectionType;

public class LayerLink extends Link {
    public LayerLink(Node from, Node to) {
        super(from, to);
        init();
    }

    public LayerLink(Object id, Node from, Node to) {
        super(id, from, to);
        init();
    }

    private void init() {
        this.putLink3D(true);
        this.putLinkWidth(4);
        this.putLinkOutlineWidth(0);
        this.putLinkColor(Color.lightGray);
        this.putLinkAntialias(false);
        this.setLinkType(TWaverConst.LINK_TYPE_ORTHOGONAL);
    }
}

 显示出来后,效果并不理想,有点乱。主要是没有“跨层”的立体感。

 

跨层连线效果

 

图中跨层的link没有呈现出“穿透层”的感觉,多了以后反而破坏了整个拓扑图的立体感和生动感,需要再改进。最好能够显示“穿层而过”的效果。需求变态么?不弄点猛药还想拿单子么,程序员就是要与各种“不可能”说“不”嘛!经过反复研究和实验,终于做出了一个更好的效果,如下图:

连线的跨层穿透效果

注意观察其中穿层效果,不知大家是否喜欢?

 

连线的透明穿透

 

怎么做到的呢?其实也简单,一点就破,我就不点破了吧,卖个关子先。大家可以先猜猜看,源代码里头也能看到答案。接下来,可以增加一些跨层连线了!看看下图效果:

 

跨层连线的综合效果图

效果还不错吧?销售看过后非常满意,连说有新意。不过还有最后一个很头大的问题:需要显示告警及其传播路线,也就是告警发生后,要从底层一直沿着依赖关系传播到上层。于是开始研究TWaver的AlarmPropagator告警传播器。通过研究发现,其实告警传播也不复杂,主要原理是当告警发生后,它会根据AlarmPropagator的“指示”和定义的规则,沿着一个特定的“路径”进行告警传播。被传播过的地方,会显示一个有告警颜色的外框,标志其告警状态。

 

但是问题是,TWaver的告警传播器是按照“父子关系”进行传播的。也就是默认情况下,告警总是从孩子传给父亲,一直到没有parent为止。按照这个规则,这个demo中一个节点发生告警后,会传播给平行四边形这个层对象,这显然是没有意义的,不符合我的要求。我们需要告警沿着层的“依赖关系”进行跨层传播。于是重写AlarmPropagator!也不难,调试了几个小时,用一个递归法总算搞定了。代码如下:

package demo;

import java.util.Collection;
import java.util.Iterator;
import twaver.AlarmSeverity;
import twaver.Element;
import twaver.Node;

public class DemoPropagator {

    public void propagate(Element element) {
        AlarmSeverity severity = element.getAlarmState().getHighestNativeAlarmSeverity();
        if (element instanceof Node) {
            Node node = (Node) element;

            Collection links = node.getAllLinks();
            if (links != null && !links.isEmpty()) {
                Iterator it = links.iterator();
                while (it.hasNext()) {
                    Object o = it.next();
                    if (o instanceof LayerLink) {
                        LayerLink link = (LayerLink) o;
                        if (link.getAlarmState().isEmpty()) {
                            link.getAlarmState().addAcknowledgedAlarm(severity);

                            Node anotherNode = link.getFrom();

                            if (anotherNode.getAlarmState().isEmpty()) {
                                anotherNode.getAlarmState().addAcknowledgedAlarm(severity);
                                if (anotherNode != node) {
                                    propagate(anotherNode);//这里递归!
                     }
                            }
                        }
                    }
                }
            }
        }
    }
}

 

这里代码的逻辑主要是判断是不是跨层link,如果是就沿着它进行传播。噢吼!上面代码好像泄露了上面“穿透Layer”的秘密了,呵呵。最后,再来一个“告警模拟器”来模拟随机、随时发生告警,也就是用一个单独的线程在里面sleep然后生成Alarm并发送到拓扑图的节点上。直接上代码:

package demo;

import java.util.Iterator;
import javax.swing.SwingUtilities;
import twaver.AlarmSeverity;
import twaver.Element;
import twaver.TDataBox;
import twaver.TWaverUtil;

public class AlarmMocker extends Thread {

    private TDataBox box = null;
    private DemoPropagator propagator = new DemoPropagator();

    public AlarmMocker(TDataBox box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }

            SwingUtilities.invokeLater(new Runnable() {

                public void run() {

                    if (TWaverUtil.getRandomInt(5) == 1) {
                        //clear all alarm and propagation.
                        Iterator it = box.iterator();
                        while (it.hasNext()) {
                            Element e = (Element) it.next();
                            e.getAlarmState().clear();
                        }
                    }

                    Element element = box.getElementByID("4." + TWaverUtil.getRandomInt(10));
                    if (element != null) {
                        element.getAlarmState().addNewAlarm(AlarmSeverity.getRandomSeverity());
                        propagator.propagate(element);
                    }
                }
            });
        }
    }
}

 

告警模拟器把最底层的里面的节点随机产生告警,再随机的清除,模拟现实网络的监控状态。然后运行demo,观察其告警传播的路线是否符合预期,也就是沿着层进行传播。

 

注意一个细节:由于上面告警模拟器在一个单独的Thread线程中运行,在产生告警并更改界面时候,需要用SwingUtilities.invokeLater进行代码封装调用,保证它在Swing线程中执行,避免屏幕和界面“花”或不可预知的显示结果。唉,谁让Swing是单线程而且是线程不安全呢?这是个古老话题,就不罗嗦了。

 

废话不多说,直接上最终效果图:

 

 

demo最终运行界面

 

双击层动画旋转并放大

 

看到告警跨层传播的效果了吗?最后,根据客户的要求,又增加了一些动画效果:双击平行四边形后,平行四边形会动画变成矩形、动画飞到屏幕中间,然后动画放大内部拓扑图,供用户查看细节;再次双击,平行四边形快速旋转缩小并回到原来位置。demo程序交付并演示后,获得客户高度评价。用我们商务人员的话来说就是:“我们的demo最出彩!”作为程序员,自己做的东西能为公司创造价值和利润就是最大的肯定和成就感!

 

由于demo掺杂了不少公司的代码,我花了一点时间整理一下,弄出了一个干净的demo代码,请见附件,请感兴趣的朋友请直接下载,欢迎留言讨论。 解压demo.zip后,中有两个jar包和run.bat,双击run.bat就可以运行demo。我是用JDK6编译的,请各位确保首先安装了JAVA 6环境。如有任何运行问题请留言。

 

demo所限内容仅供技术交流和参考,请慎作商业用途。

 

补充:

 

看到留言中很多朋友都说“第一次看到用程序做demo”,也把我弄“惊诧”了。因为自己从业这些年基本上都是用程序做demo,已经习以为常,甚至成为理所当然了。看到很多朋友说“用PPT和美工图片”做DEMO,确实感觉自己有点out了。在电信、电力这些行业里面,项目都比较大、复杂,没有一定的商务和技术实力以及经验是很难获得项目机会的。PPT作为产品介绍和各种交流肯定是没问题的;但如果说要做大项目的“系统演示”那可是够“空对空”的。可以想象一下:如果我们是局方、甲方或客户,要花几百万做一个项目,给各个厂商一个月的时间来准备一个技术交流和产品演示,我们是愿意把项目交给用精美PPT演示的厂商,还是愿意给效果同样漂亮但用实实在的程序或DEMO来演示的厂商呢?谁家更有实力?谁家更重视项目?谁家更有技术和人才?谁家更有想法?谁家更有经验?这个问题几乎不用回答。

 

一个有实力的软件公司,打大单不拿点真家伙,光靠美工弄几个花里胡哨的图片或者PPT就能拿下项目,那绝对是太不可思议了。我接触的这些公司哪个不是靠多年的行业积累和系统经验,哪次面对这样的大战役不是要抽出一两个技术好手来加班加点的做些真家伙(半真也行,老系统扣出一部分模块和代码,就算在此基础上修修改改去运行也可)去演示?甚至有的时候去演示的几乎就是可以上线的系统,或在另外一个老的类似项目紧急改进出来的半成品系统。如果没有这些项目和技术经验做沉淀和积累,一旦中标,很多时候系统上线只给3个月的时间,完全从头来个瀑布式的从需求分析、从头开发?做梦吧。现在行业竞争越来越惨烈,客户要求越来越高越复杂,技术变化快,你不能做到又快又好的提供系统和解决方案,只能靠边站了。

 

这是一个大的监控系统,涉及很多子系统和其他业务系统。这里仅仅是一个简洁的高层次全网监控界面,是很小一部分,是一个小UI而已。但是这个UI会被投射到一个2*4的大型液晶屏幕墙上,让全屋子的人抬头就能看到全网的链接情况、告警情况;让公司领导经常过来看到;让兄弟省市领导经常参观到。所以,它的作用不可小视(提高形象的作用不也是作用么)。这也是为什么客户这么重视的原因。另外麻雀虽小,五脏俱全;以管窥豹也可以时见一斑。我们搞技术的就是应当多思考多创新多学习多交流、踏踏实实在细节上下功夫,也许才能更好的体现我们的价值。

 

如果demo所体现出来的UI呈现思路和设计想法或者代码中的技巧能够给大家带来一点点启发,本人就感到非常非常满足了!

 

谢谢各位!

 


附:带窗口的Demo运行图


 

  • 大小: 56.1 KB
  • 大小: 11.3 KB
  • 大小: 24.5 KB
  • 大小: 84.5 KB
  • 大小: 44.9 KB
  • 大小: 98.9 KB
  • 大小: 20.6 KB
  • 大小: 18.6 KB
  • 大小: 88.9 KB
  • 大小: 93.3 KB
  • 大小: 96.3 KB
  • 大小: 138 KB
分享到:
评论
204 楼 moqinan 2011-03-08  
PPT做的真不错。。。想要。。。
203 楼 cdcxzw 2011-02-28  
嗯,挺不错!楼主,也给我发一份代码学习学习,感谢楼主的奉献精神.cdcxzw@qq.com
202 楼 shichuanliujie 2011-02-19  
楼主 做得 不错,我也在为多demo 烦恼啊,哎
201 楼 pubobo 2011-02-02  
楼主很赞~  人品和思想都令人深刻~~
200 楼 wn_1985 2011-01-20  
效果不评论,佩服lz涵养一个`
199 楼 zhaojun1717 2011-01-19  
<div class="quote_title">xiaozhonghua 写道</div>
<div class="quote_div">
<p>今天心情不错,公司终于签下了一个综合业务监控系统的大单。到底有多大我也不知道,反正连软件带硬件不算小。按照销售的说法,拿下这个项目一个重要的因素就是要提供一个吸引眼球的demo,而我们做的不错!今天和大家分享一下喜悦和经验!</p>
<p> </p>
<p>这个项目是一个省级电信运营商的综合业务监控系统。公司跟了好长时间了。由于是一个综合的业务监控系统,涉及的管理资源非常多,又要提供大屏幕显示,所以对系统的呈现效果要求非常高(“政绩工程”么)。前前后后提了很多“无礼”要求,陆续提过的有3D电子地图、3D机房监控、场景仿真、全动画、Google Earth、全Flash等等....弄的我们晕头转向、焦头烂额。其实很多时候,用户自己也不知道想要什么,反正对厂商的要求就是一个字:“炫”,两个字“好看”,三个字:“一定好好看!”(不对,好像是四个字哦)。</p>
<p> </p>
<p>言归正传,项目跟踪过程中,商务始终告诉我们研发一定要做好一件事:如何做好呈现,尤其是综合的业务监控和呈现,这是获得项目的关键,一定要“出彩”。这个“综合呈现”说起来容易,做起来难。作为省级的电信运营商,内部的各种软硬件系统无数,要监控从上到下、从软到硬,真是烦不胜烦:</p>
<ul>
<li>基础设施层:主要是网络上的硬件设备,包括交换机、路由器、防火墙、各种主机设备服务器等等;</li>
<li>软件层:这一层主要是主机上面运行的操作系统和各类业务软件系统,例如操作系统(Windows、AIX/HP-UX/LINUX/SOLARIS)、各种中间件(WebLogic、JBoss、IIS、WebSphere等)、数据库(Oracle、Sybase、MySQL)等;</li>
<li>应用层:这一层是指运行在软件层内部的一些细粒度资源,包括一些关键的软件进程、数据库表空间、业务连接、消息队列等等。这一层数量繁杂、数量众多。不过这些资源的状态直接会影响其上层支撑的业务。</li>
<li>业务层:业务层是最顶层,由以上底层资源所共同支撑起来的面向用户的各种业务。对业务最容易理解的描述就是电信提供给客户的具体“服务”。例如视频业务、短信业务、WAP业务、专网业务等等。这些业务才是用户最终关心的东西,因为这些业务才是客户的核心资产,是拿来卖钱的最终产品。一旦出问题,将直接影响money!</li>
</ul>
<p>此外,还有一大堆机房环境监控的要求,什么配电柜供电、开关状态、UPS、蓄电池、空调、适度温度漏水消防通风门禁视频.........一大堆。所以,要对业务进行监控,就必须对业务所支撑的各个底层资源进行监控。另外,还要能够对这些资源的关系进行连接和定义,一旦发生故障和问题,能够从上到下迅速定位故障起源,在最短时间内发现问题、解决问题。如何呈现这些依赖关系,并对故障和告警进行纵向定位,是软件呈现的一个核心问题,也是用户最关心的一个问题。</p>
<p> </p>
<p>用户要求我们先制作一个demo程序,看我们将如何呈现“综合监控”的效果。在充分了解了用户需求之后,经过讨论,我们想做一个完全图形化的分层、跨层的综合监控界面。界面要美观,有动画效果,能够清晰的显示资源依赖关系和告警传播定位。</p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220431/8c24aff1-c63c-3bd5-87bd-db952f9b561b.png" alt=""></p>
<p style="text-align: center;">需要监控和管理的资源</p>
<p style="text-align: center;"> </p>
<p><br> 接下来要写代码了。肯定先用我熟悉的TWaver试试。研究了一下,TWaver中有一个平行四边形的Group对象,适合做上图中的“层”的概念。先如下封装并设置属性:</p>
<pre name="code" class="java">package demo;

import java.awt.Color;
import twaver.Group;
import twaver.TWaverConst;

public class LayerGroup extends Group {

    public LayerGroup() {
        init();
    }

    public LayerGroup(Object id) {
        super(id);
        init();
    }

    private void init() {
        this.setGroupType(TWaverConst.GROUP_TYPE_PARALLELOGRAM);
        this.putGroupAngle(45);

        this.putGroup3D(true);
        this.putGroupDeep(10);
        this.putGroupOutline(false);
        this.putGroupFillColor(Color.green.darker());
        this.putGroupGradient(true);
        this.putGroupGradientFactory(TWaverConst.GRADIENT_LINE_E);
        this.putGroupHandlerVisible(false);
        this.putGroupDoubleClickEnabled(false);
        this.putBorderColor(Color.white);
        this.putBorderInsets(3);
        this.putBorderAntialias(true);
        this.putBorderStroke(TWaverConst.STROKE_SOLID_4);
        this.putBorderVisible(false);
        this.putLabelHighlightable(false);

        this.setEnableAlarmPropagationFromChildren(false);
    }
}
</pre>
<p> 通过这个简单的封装,再往Group里头放几个节点和连线,显示效果如下:</p>
<p> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220437/2db467ce-1b93-38cb-a371-1c0151f485a7.png" alt=""></p>
<p style="text-align: center;">用Group制作的“层”效果</p>
<p style="text-align: center;"> </p>
<p>怎么样,有点意思吧?开头不错,继续改进!再依次排列4个Group,用不同颜色,试试效果:</p>
<pre name="code" class="java">createLayer(Color.orange, 50, 0, 10, "7.png", "&lt;html&gt;&lt;center&gt;软件&lt;br&gt;业务层&lt;/center&gt;&lt;/html&gt;");
createLayer(Color.green.darker(),180, 200, 15, "8.png", "&lt;html&gt;&lt;center&gt;技术&lt;br&gt;应用层&lt;/center&gt;&lt;/html&gt;");
createLayer(Color.magenta.darker(),280, 350, 5, "5.png", "&lt;html&gt;&lt;center&gt;技术&lt;br&gt;软件层&lt;/center&gt;&lt;/html&gt;");
createLayer(Color.cyan.darker(),400, 570, 7, "1.png", "&lt;html&gt;&lt;center&gt;基础&lt;br&gt;设施层&lt;/center&gt;&lt;/html&gt;");</pre>
<p>以上代码封装了创建一个层的函数,给定颜色、坐标位置、内部节点数量、图标、文字等等。上面代码中的HTML风格字符串是为了在TWaver中(好像Swing中也是这样的)显示换行的标签。每一个层作为容器包含了很多不同类型的资源。显示效果如下图:</p>
<p> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220444/459bfe3d-9ac4-3fc9-ae9d-e12a2a9a5b56.png" alt=""></p>
<p style="text-align: center;">四层拓扑图显示效果</p>
<p style="text-align: center;"> </p>
<p style="text-align: left;">注意其中的连线有下垂的弯曲效果。这是我以前在做项目封装过的一个TWaver技巧:通过重写twaver的Link的UI类,重新指定path走向实现的。其实也很简单,首先获得link的from点和to点,取值中间点,再把y纵向增加20,把这个点作为quadTo的控制点画曲线即可。对TWaver熟悉的朋友可以看一下这段代码(其实这个效果也是从TWaver Java的demo源代码中学习到的):</p>
<pre name="code" class="java">package demo;

import java.awt.Point;
import java.awt.geom.GeneralPath;
import twaver.network.TNetwork;
import twaver.network.ui.LinkUI;

public class InnerLinkUI extends LinkUI {

    public InnerLinkUI(TNetwork network, InnerLink link) {
        super(network, link);
    }

    @Override
    public GeneralPath getPath() {
        GeneralPath customPath = new GeneralPath();
        Point p1 = this.getFromPoint();
        Point p2 = this.getToPoint();
        customPath.moveTo(p1.x, p1.y);
        int offset = 20;
        customPath.quadTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2 + offset, p2.x, p2.y);
        return customPath;
    }
}
</pre>
<p style="text-align: left;"> 用这种link做出的拓扑图比较生动美观。多加几个节点连线就能看出来了:</p>
<p style="text-align: left;"> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220450/0740a69f-96dc-3919-93ec-71bcfce6358b.png" alt=""></p>
<p style="text-align: center;">四层复杂拓扑图显示效果</p>
<p style="text-align: center;"> </p>
<p style="text-align: left;">不过发现平行四边形Group一个问题:当两个Layer叠加后,下面的节点会被完全覆盖,看不见了。用户说:能不能也能看见?(晕,盖住了也要看见。谁让人家是甲方呢?)于是询问TWaver的人,一个哥们说Group有透明属性。于是试了一下,效果不还错:</p>
<pre name="code" class="java">this.putGroupOpaque(false);</pre>
<p style="text-align: left;"> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220457/06708924-1e8b-3efb-8a70-c47872295d10.png" alt=""></p>
<p style="text-align: center;">层的透明与覆盖</p>
<p style="text-align: center;"> </p>
<p style="text-align: left;">下一步,关键了:要增加层与层之间资源的“依赖关系”。例如一个Oracle跑在一台主机上,而Oracle中的一个关键表空间需要重点监控,它决定了上层一个视频点播业务是否能够正常。为了体现这个依赖关系,在跨层的节点中间建立link。这个link和层内部link显示上应当有所区别:</p>
<pre name="code" class="java">package demo;

import java.awt.Color;
import twaver.Link;
import twaver.Node;
import twaver.TWaverConst;
import twaver.base.OrthogonalLinkDirectionType;

public class LayerLink extends Link {
    public LayerLink(Node from, Node to) {
        super(from, to);
        init();
    }

    public LayerLink(Object id, Node from, Node to) {
        super(id, from, to);
        init();
    }

    private void init() {
        this.putLink3D(true);
        this.putLinkWidth(4);
        this.putLinkOutlineWidth(0);
        this.putLinkColor(Color.lightGray);
        this.putLinkAntialias(false);
        this.setLinkType(TWaverConst.LINK_TYPE_ORTHOGONAL);
    }
}
</pre>
<p style="text-align: left;"> 显示出来后,效果并不理想,有点乱。主要是没有“跨层”的立体感。</p>
<p style="text-align: left;"> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220459/c6a16afc-2bff-3444-9462-54dfcbceb9f0.png" alt=""></p>
<p style="text-align: center;">跨层连线效果</p>
<p style="text-align: center;"> </p>
<p style="text-align: left;">图中跨层的link没有呈现出“穿透层”的感觉,多了以后反而破坏了整个拓扑图的立体感和生动感,需要再改进。最好能够显示“穿层而过”的效果。需求变态么?不弄点猛药还想拿单子么,程序员就是要与各种“不可能”说“不”嘛!经过反复研究和实验,终于做出了一个更好的效果,如下图:</p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220463/5a3cd6ee-006c-39e9-a5dd-3ea523e72610.png" alt=""></p>
<p style="text-align: center;">连线的跨层穿透效果</p>
<p style="text-align: left;">注意观察其中穿层效果,不知大家是否喜欢?</p>
<p style="text-align: left;"> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220465/b65d175b-7d55-3157-b72f-12bd6101c478.png" alt=""></p>
<p style="text-align: center;">连线的透明穿透</p>
<p style="text-align: center;"> </p>
<p style="text-align: left;">怎么做到的呢?其实也简单,一点就破,我就不点破了吧,卖个关子先。大家可以先猜猜看,源代码里头也能看到答案。接下来,可以增加一些跨层连线了!看看下图效果:</p>
<p style="text-align: left;"> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220467/98c462d3-88c0-3dc9-ac36-13cb65f55a17.png" alt=""></p>
<p style="text-align: center;">跨层连线的综合效果图</p>
<p style="text-align: left;">效果还不错吧?销售看过后非常满意,连说有新意。不过还有最后一个很头大的问题:需要显示告警及其传播路线,也就是告警发生后,要从底层一直沿着依赖关系传播到上层。于是开始研究TWaver的AlarmPropagator告警传播器。通过研究发现,其实告警传播也不复杂,主要原理是当告警发生后,它会根据AlarmPropagator的“指示”和定义的规则,沿着一个特定的“路径”进行告警传播。被传播过的地方,会显示一个有告警颜色的外框,标志其告警状态。</p>
<p style="text-align: left;"> </p>
<p style="text-align: left;">但是问题是,TWaver的告警传播器是按照“父子关系”进行传播的。也就是默认情况下,告警总是从孩子传给父亲,一直到没有parent为止。按照这个规则,这个demo中一个节点发生告警后,会传播给平行四边形这个层对象,这显然是没有意义的,不符合我的要求。我们需要告警沿着层的“依赖关系”进行跨层传播。于是重写AlarmPropagator!也不难,调试了几个小时,用一个递归法总算搞定了。代码如下:</p>
<pre name="code" class="java">package demo;

import java.util.Collection;
import java.util.Iterator;
import twaver.AlarmSeverity;
import twaver.Element;
import twaver.Node;

public class DemoPropagator {

    public void propagate(Element element) {
        AlarmSeverity severity = element.getAlarmState().getHighestNativeAlarmSeverity();
        if (element instanceof Node) {
            Node node = (Node) element;

            Collection links = node.getAllLinks();
            if (links != null &amp;&amp; !links.isEmpty()) {
                Iterator it = links.iterator();
                while (it.hasNext()) {
                    Object o = it.next();
                    if (o instanceof LayerLink) {
                        LayerLink link = (LayerLink) o;
                        if (link.getAlarmState().isEmpty()) {
                            link.getAlarmState().addAcknowledgedAlarm(severity);

                            Node anotherNode = link.getFrom();

                            if (anotherNode.getAlarmState().isEmpty()) {
                                anotherNode.getAlarmState().addAcknowledgedAlarm(severity);
                                if (anotherNode != node) {
                                    propagate(anotherNode);//这里递归!
                     }
                            }
                        }
                    }
                }
            }
        }
    }
}
</pre>
<p style="text-align: left;"> </p>
<p>这里代码的逻辑主要是判断是不是跨层link,如果是就沿着它进行传播。噢吼!上面代码好像泄露了上面“穿透Layer”的秘密了,呵呵。最后,再来一个“告警模拟器”来模拟随机、随时发生告警,也就是用一个单独的线程在里面sleep然后生成Alarm并发送到拓扑图的节点上。直接上代码:</p>
<pre name="code" class="java">package demo;

import java.util.Iterator;
import javax.swing.SwingUtilities;
import twaver.AlarmSeverity;
import twaver.Element;
import twaver.TDataBox;
import twaver.TWaverUtil;

public class AlarmMocker extends Thread {

    private TDataBox box = null;
    private DemoPropagator propagator = new DemoPropagator();

    public AlarmMocker(TDataBox box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }

            SwingUtilities.invokeLater(new Runnable() {

                public void run() {

                    if (TWaverUtil.getRandomInt(5) == 1) {
                        //clear all alarm and propagation.
                        Iterator it = box.iterator();
                        while (it.hasNext()) {
                            Element e = (Element) it.next();
                            e.getAlarmState().clear();
                        }
                    }

                    Element element = box.getElementByID("4." + TWaverUtil.getRandomInt(10));
                    if (element != null) {
                        element.getAlarmState().addNewAlarm(AlarmSeverity.getRandomSeverity());
                        propagator.propagate(element);
                    }
                }
            });
        }
    }
}
</pre>
<p> </p>
<p>告警模拟器把最底层的里面的节点随机产生告警,再随机的清除,模拟现实网络的监控状态。然后运行demo,观察其告警传播的路线是否符合预期,也就是沿着层进行传播。</p>
<p> </p>
<p>注意一个细节:由于上面告警模拟器在一个单独的Thread线程中运行,在产生告警并更改界面时候,需要用SwingUtilities.invokeLater进行代码封装调用,保证它在Swing线程中执行,避免屏幕和界面“花”或不可预知的显示结果。唉,谁让Swing是单线程而且是线程不安全呢?这是个古老话题,就不罗嗦了。</p>
<p> </p>
<p>废话不多说,直接上最终效果图:</p>
<p> </p>
<p> </p>
<p style="text-align: center;"><img src="http://dl.iteye.com/upload/attachment/220475/ace1f308-4e06-356f-8bcb-4fd7851f29b4.png" alt=""></p>
<p style="text-align: center;">demo最终运行界面</p>
<p style="text-align: center;"> <img src="http://dl.iteye.com/upload/attachment/220541/135c90fa-0fb4-334c-8417-f5d33f926ffa.png" alt=""></p>
<p style="text-align: center;">双击层动画旋转并放大</p>
<p style="text-align: center;"> </p>
<p>看到告警跨层传播的效果了吗?最后,根据客户的要求,又增加了一些动画效果:双击平行四边形后,平行四边形会动画变成矩形、动画飞到屏幕中间,然后动画放大内部拓扑图,供用户查看细节;再次双击,平行四边形快速旋转缩小并回到原来位置。demo程序交付并演示后,获得客户高度评价。用我们商务人员的话来说就是:“我们的demo最出彩!”作为程序员,自己做的东西能为公司创造价值和利润就是最大的肯定和成就感!</p>
<p> </p>
<p>由于demo掺杂了不少公司的代码,我花了一点时间整理一下,弄出了一个干净的demo代码,请见附件,请感兴趣的朋友请直接下载,欢迎留言讨论。 解压demo.zip后,中有两个jar包和run.bat,双击run.bat就可以运行demo。我是用JDK6编译的,请各位确保首先安装了JAVA 6环境。如有任何运行问题请留言。</p>
<p> </p>
<p>demo所限内容仅供技术交流和参考,请慎作商业用途。</p>
<p> </p>
<p><strong>补充:</strong></p>
<p> </p>
<p><span style="color: #008000;">看到留言中很多朋友都说“第一次看到用程序做demo”,也把我弄“惊诧”了。因为自己从业这些年基本上都是用程序做demo,已经习以为常,甚至成为理所当然了。看到很多朋友说“用PPT和美工图片”做DEMO,确实感觉自己有点out了。在电信、电力这些行业里面,项目都比较大、复杂,没有一定的商务和技术实力以及经验是很难获得项目机会的。PPT作为产品介绍和各种交流肯定是没问题的;但如果说要做大项目的“系统演示”那可是够“空对空”的。可以想象一下:如果我们是局方、甲方或客户,要花几百万做一个项目,给各个厂商一个月的时间来准备一个技术交流和产品演示,我们是愿意把项目交给用精美PPT演示的厂商,还是愿意给效果同样漂亮但用实实在的程序或DEMO来演示的厂商呢?谁家更有实力?谁家更重视项目?谁家更有技术和人才?谁家更有想法?谁家更有经验?这个问题几乎不用回答。</span></p>
<p><span style="color: #008000;"> </span></p>
<p><span style="color: #008000;">一个有实力的软件公司,打大单不拿点真家伙,光靠美工弄几个花里胡哨的图片或者PPT就能拿下项目,那绝对是太不可思议了。我接触的这些公司哪个不是靠多年的行业积累和系统经验,哪次面对这样的大战役不是要抽出一两个技术好手来加班加点的做些真家伙(半真也行,老系统扣出一部分模块和代码,就算在此基础上修修改改去运行也可)去演示?甚至有的时候去演示的几乎就是可以上线的系统,或在另外一个老的类似项目紧急改进出来的半成品系统。如果没有这些项目和技术经验做沉淀和积累,一旦中标,很多时候系统上线只给3个月的时间,完全从头来个瀑布式的从需求分析、从头开发?做梦吧。现在行业竞争越来越惨烈,客户要求越来越高越复杂,技术变化快,你不能做到又快又好的提供系统和解决方案,只能靠边站了。</span></p>
<p><span style="color: #008000;"> </span></p>
<p><span style="color: #008000;">这是一个大的监控系统,涉及很多子系统和其他业务系统。这里仅仅是一个简洁的高层次全网监控界面,是很小一部分,是一个小UI而已。但是这个UI会被投射到一个2*4的大型液晶屏幕墙上,让全屋子的人抬头就能看到全网的链接情况、告警情况;让公司领导经常过来看到;让兄弟省市领导经常参观到。所以,它的作用不可小视(提高形象的作用不也是作用么)。这也是为什么客户这么重视的原因。另外麻雀虽小,五脏俱全;以管窥豹也可以时见一斑。我们搞技术的就是应当多思考多创新多学习多交流、踏踏实实在细节上下功夫,也许才能更好的体现我们的价值。</span></p>
<p><span style="color: #008000;"> </span></p>
<p><span style="color: #008000;">如果demo所体现出来的UI呈现思路和设计想法或者代码中的技巧能够给大家带来一点点启发,本人就感到非常非常满足了!</span></p>
<p> </p>
<p>谢谢各位!</p>
<p> </p>
<p style="text-align: center;"><br><img src="http://dl.iteye.com/upload/attachment/221291/1660ea7f-6682-3cfb-98b3-e795034c0ec3.png" alt=""></p>
<p style="text-align: center;">附:带窗口的Demo运行图</p>
<p><br> </p>
</div>
<p> </p>
198 楼 面向对象的猪 2010-08-16  
敢问你的年薪
有39K/m没?
高手
197 楼 raito_yagami 2010-05-18  
用java整这个?能做是能做,但用其他的做不是更好?
196 楼 she2shou 2010-05-18  
今天开眼界了
195 楼 she2shou 2010-05-18  
试试而已,不用鸟我。[b][/b][i][/i][u][/u]
引用
194 楼 shuiguozheng 2010-05-18  
    还算炫吧! 
193 楼 pollyduan 2010-05-18  
haole 写道
说实在的楼主做的确实不错,用Demo做为演示,确实更能说明系统框架,增加拿到项目的砝码。
不过,问一句,Demo做的好,到时上系统的时候有些功能实现不了怎么办?我的意思不是贬低你的公司的实力,因为无论你前期调研,需求做的多么详细,到真正开发的时候也会有想象不到的地方,有可能碰到的问题是一个关键问题,请问你的承诺如何来保证。
另外,你的补充中提到很多系统3个月上线,即使用公司为一个省做的系统,用到另外一个省里,针对中国的复杂性,不可能系统不调整,就直接上线,既然调整,又是大系统,3个月是不可能的。
这说明了中国仍然是一个皇权社会,不注重事实,不实事求是,一切依领导的意志为转移,也是一个奴性社会。
做技术的是最底层的,总是说做商务的拿一个项目多难,喝多少酒,却从来不重视技术人员付出的努力。


仔细看了楼主的说明,这个是一个系统的拓补和流的走向,说明系统架构的,不是吹用什么技术、有多牛的功能的,有什么实现不了之说?
192 楼 pollyduan 2010-05-18  
xiaozhonghua 写道
cqllang 写道
哦.我在linux用的jdk5.0..可能有点问题,

刚才报 UnsupportedClassVersionError: Bad version number in .class file
我现在改过后能运行了,不过界面没有任何显示,为白板.  未提示任何错误..

我再看看是不是图片路径的问题..

这个比较奇怪。Linux上没测试过,哪位有经验可以帮看一下?


Bad version number in .class file
很明显的错误提示,你的jdk版本太低了。
191 楼 wanta 2010-05-18  
楼主做的真不错,我都弄了两个月了还停留在那个svgdemo例子水平呢
不知道有没有详细文档啊,感觉里面都提供些什么效果都不清楚。
190 楼 yin_bp 2010-04-28  
176170847 写道
老实说,基于twaver的话,你的demo做的确实不怎么样。
但比那些没有基于twaver的系统做的好多了,
twaver本身已经很成熟,最近也在使用,flex版本的,
多交流!

你有flex版的吗,可以共享一份不
189 楼 176170847 2010-04-28  
老实说,基于twaver的话,你的demo做的确实不怎么样。
但比那些没有基于twaver的系统做的好多了,
twaver本身已经很成熟,最近也在使用,flex版本的,
多交流!
188 楼 benny27521 2010-04-13  
对于目前碰到的项目很有借鉴意义,谢谢LZ
187 楼 oakeye 2010-04-13  
摩卡那个产品确实好,不过很贵
186 楼 ellak 2010-04-13  
看起来效果非常炫!!!发一个吧楼主~~先谢谢了
enzo.lvi@gmail.com
185 楼 danni505 2010-04-12  
监控市场上似乎摩卡的市场份额还可以!产品也见过,好像比你这个好看多了!

相关推荐

    Unity3d 微信小程序(小游戏)项目实现接入广告(banner、插屏和激励广告等)Demo源码

    很早之前编写了Unity导出微信小游戏的博客,也尝试自己做了个Demo上线了,基本没更新过,不过几个月的时间,用户超过了一千,可以开通广告了,大概率是因为上篇的帖子浏览量大了,扫码体验的人多,基本没什么留存的...

    微信小程序Demo-环球小镇商城-附完整源代码.rar

    微信小程序Demo-环球小镇商城是一个集商品展示、购买、支付等功能于一体的小程序应用,适用于各类线上商城。本资源提供了完整的源代码,方便开发者进行二次开发和定制,以满足不同场景下的需求。 该小程序采用了...

    微信小程序-仿微信DEMO小程序项目源码-原生开发框架-含效果截图示例.zip

    微信小程序作为腾讯推出的一种轻量级应用,凭借其无需安装、触手可及、用...无论您是前端开发者、小程序爱好者,还是希望拓展业务的企业,这份资源都将为您带来极大的帮助和启示。快来查看吧,开启您的小程序开发之旅!

    微信小程序-微信小程序-仿豆瓣电影-demo小程序项目源码-原生开发框架-含效果截图示例.zip

    微信小程序,作为腾讯旗下的轻量级应用平台,凭借其独特的优势和特点,...无论您是前端开发者、小程序爱好者,还是希望拓展业务的企业,这份资源都将为您带来极大的帮助和启示。快来查看吧,开启您的小程序开发之旅!

    微信小程序-仿豆瓣电影-demo小程序项目源码-原生开发框架-含效果截图示例.zip

    微信小程序,作为腾讯旗下的轻量级应用平台,凭借其独特的优势和特点,...无论您是前端开发者、小程序爱好者,还是希望拓展业务的企业,这份资源都将为您带来极大的帮助和启示。快来查看吧,开启您的小程序开发之旅!

    tbl-demo-service-master.zip

    此项目为应用领域驱动开发DDD的分层架构的DEMO项目,仅供参考。 领域模型的职责是实现业务逻辑,如果领域模型只是用来处理简单的逻辑(比如贫血模型),那么领域模型的作用微乎其微,甚至可以忽略,数据转换的成本比...

    使用MyEclipse创建Spring Boot项目demo

    使用 Spring Boot 开发项目,会给我们带来非常美妙的开发体验,可以从以下几个方面展开来说明 Spring Boot 让开发变得更简单 Spring Boot 对开发效率的提升是全方位的,我们可以简单做一下对比: 在没有使用 ...

    爬虫的Demo

    本项目是基于Maven的,使用的是java语言开发的网络爬虫Demo,并且实现了分页爬取数据,采用多线程和线程池的技术,有效的加快了爬取资源的效率,内含有sql脚本,使用的是Mysql数据库,希望给大家带来帮助.....

    ssm crud项目 使用ssm bootstrap jquery等技术 入门小demo 员工管理crud.zip

    在这个平台上,我们为大家带来了一系列的 JavaSSM(Spring + SpringMVC + MyBatis)项目。这些项目旨在展示SSM框架在实际应用中的魅力,同时也为开发者提供了一个快速学习和实践的机会。通过下载和使用这些项目,您...

    微信小程序Demo-:黑市商城框架;适用1028版本-附完整源代码.rar

    微信小程序Demo - 黑市商城框架,是一个专为1028版本设计的完整源代码项目。该项目不仅提供了一个功能丰富的电商平台,还融入了独特的黑市元素,为用户带来全新的购物体验。 该框架基于最新的微信小程序技术构建,...

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...

    这是一个 基于maven的SSM整合 demo项目.zip

    在这个平台上,我们为大家带来了一系列的 JavaSSM(Spring + SpringMVC + MyBatis)项目。这些项目旨在展示SSM框架在实际应用中的魅力,同时也为开发者提供了一个快速学习和实践的机会。通过下载和使用这些项目,您...

    关于系统登录风险预测的小demo模型

    系统登录风险预测机器学习项目说明 一、项目背景和目标 随着互联网的普及和信息技术的快速发展,系统登录成为我们日常生活中的重要环节。然而,这也带来了安全风险。恶意用户可能会尝试非法登录,给系统带来威胁。...

    jquery mobile的所有版本库api文档简单示例demo下载jqm学习大全

    jquery mobile的所有版本库api文档简单示例demo下载jqm学习大全下载后评论反积分! jQuery Mobile是jQuery 在手机上和平板设备上的版本。jQuery Mobile 不仅会给主流移动平台带来jQuery核心库,而且会发布一个完整...

    微信小程序的后端Java Demo程序.zip

    微信小程序开发项目 微信小程序在产品功能设计上给用户更多控制力。在微信小程序的设置页,为用户提供了数据权限开关,一旦用户授权之后又关闭,微信小程序再次使用该用户数据时需要重新获得授权,为用户提供更方便的...

    成百上千个Java 源码DEMO 3(1-4是独立压缩包)

    Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...

    ssm 一对一 一对多demo

    项目中好久没有用mybatis一对一 一对多的查询了。都用map接收,今天同事遇到。我参考网上的写法。自己总结了一下。希望给码友带来帮助

    【WordPress插件】2022年最新版完整功能demo+插件v2.0.1.zip

    "【WordPress插件】2022年最新版完整功能demo+插件v2.0.1 Divi Table Maker Modules - Brings Beautiful Responsive Tables to The Divi Builder Divi表制造商模块 - 为Divi Builder带来美丽的响应表" ---------- ...

    【WordPress插件】2022年最新版完整功能demo+插件v3.6.4 Pro + v3.5.6 Free.zip

    "【WordPress插件】2022年最新版完整功能demo+插件v3.6.4 Pro + v3.5.6 Free Elementor Pro | Brings New Designs Experiences to Your WordPress Elementor Pro |为您的WordPress带来新的设计体验" ---------- ...

    demo_inxedu_open:免费开源网校系统源代码轻松构建在线教育平台

    demo_inxedu_open 免费开源网校系统源代码轻松构建在线教育平台网站功能模块:课程功能咨询功能问答功能首页banner推荐播放模块个人中心模块个人资料模块修改头像模块收藏课程模块问题总结:项目导入如果获取设置...

Global site tag (gtag.js) - Google Analytics