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

远程桌面组件AjaxVNC的应用和原理讲解

阅读更多

     最近由于项目需要学习远程监控软件,在网上查看网友关于监控软件的设计方案,同时查找是否有第三方软件提供自己集成到项目中应用组件。有童鞋说AjaxVNC开始显示监控远程桌面的功能,于是下载AjaxVNC学习使用。

   在SOurceForge网站下载AjaxVNC的组件。

 网址如下:

http://sourceforge.net/projects/ajaxvnc/files/ajaxvnc/ajaxvnc-r01.00/ajaxvnc-r01.00.zip

 

 

      查看AjaxVNC的源代码可以发现,AjaxVNC的原理如下,通过观察者模式TileObservable类观察桌面是否变化,如果变化,由主线程通过Swing抓取远程桌面信息形成一张JPG的图片,通过AJAX将图片的信息发送给监控者。

 

一个重点生成图片的类类如下

public class KVMManager {
    
    private boolean DEBUG = true;
    
    private Robot robot;
    private BufferedImage screenimage;
    private Rectangle screenbound;
    private HashMap keymap;
    
    /** Creates a new instance of KVMManager */
    public KVMManager() {
        if (DEBUG) System.out.println(getClass().getName() + ": KVMManager created.");
        setScreenBound(getDefaultScreenBound());
        robot = createRobot();
        screenimage = null;
        keymap = new HashMap();
        assignKeyMap();
    }
    
    private void assignKeyMap() {
        // First row
        keymap.put(new Integer(27 	), new Integer(KeyEvent.VK_ESCAPE)); // Esc
        keymap.put(new Integer(192	), new Integer(KeyEvent.VK_BACK_QUOTE)); // `
        keymap.put(new Integer(49	), new Integer(KeyEvent.VK_1)); // 1
        keymap.put(new Integer(50	), new Integer(KeyEvent.VK_2)); // 2
        keymap.put(new Integer(51	), new Integer(KeyEvent.VK_3)); // 3
        keymap.put(new Integer(52	), new Integer(KeyEvent.VK_4)); // 4
        keymap.put(new Integer(53	), new Integer(KeyEvent.VK_5)); // 5
        keymap.put(new Integer(54	), new Integer(KeyEvent.VK_6)); // 6
        keymap.put(new Integer(55	), new Integer(KeyEvent.VK_7)); // 7
        keymap.put(new Integer(56	), new Integer(KeyEvent.VK_8)); // 8
        keymap.put(new Integer(57	), new Integer(KeyEvent.VK_9)); // 9
        keymap.put(new Integer(48	), new Integer(KeyEvent.VK_0)); // 0
        keymap.put(new Integer(189	), new Integer(KeyEvent.VK_MINUS)); // -
        keymap.put(new Integer(187	), new Integer(KeyEvent.VK_EQUALS)); // =
        keymap.put(new Integer(8	), new Integer(KeyEvent.VK_BACK_SPACE)); // Backspace
        // Second row
        keymap.put(new Integer(9	), new Integer(KeyEvent.VK_TAB)); // Tab
        keymap.put(new Integer(81	), new Integer(KeyEvent.VK_Q)); // Q
        keymap.put(new Integer(87	), new Integer(KeyEvent.VK_W)); // W
        keymap.put(new Integer(69	), new Integer(KeyEvent.VK_E)); // E
        keymap.put(new Integer(82	), new Integer(KeyEvent.VK_R)); // R
        keymap.put(new Integer(84	), new Integer(KeyEvent.VK_T)); // T
        keymap.put(new Integer(89	), new Integer(KeyEvent.VK_Y)); // Y
        keymap.put(new Integer(85	), new Integer(KeyEvent.VK_U)); // U
        keymap.put(new Integer(73	), new Integer(KeyEvent.VK_I)); // I
        keymap.put(new Integer(79	), new Integer(KeyEvent.VK_O)); // O
        keymap.put(new Integer(80	), new Integer(KeyEvent.VK_P)); // P
        keymap.put(new Integer(219	), new Integer(KeyEvent.VK_OPEN_BRACKET)); // [
        keymap.put(new Integer(221	), new Integer(KeyEvent.VK_CLOSE_BRACKET)); // ]
        keymap.put(new Integer(220	), new Integer(KeyEvent.VK_BACK_SLASH)); // \
        keymap.put(new Integer(65	), new Integer(KeyEvent.VK_A)); // A
        keymap.put(new Integer(83	), new Integer(KeyEvent.VK_S)); // S
        keymap.put(new Integer(68	), new Integer(KeyEvent.VK_D)); // D
        keymap.put(new Integer(70	), new Integer(KeyEvent.VK_F)); // F
        keymap.put(new Integer(71	), new Integer(KeyEvent.VK_G)); // G
        keymap.put(new Integer(72	), new Integer(KeyEvent.VK_H)); // H
        keymap.put(new Integer(74	), new Integer(KeyEvent.VK_J)); // J
        keymap.put(new Integer(75	), new Integer(KeyEvent.VK_K)); // K
        keymap.put(new Integer(76	), new Integer(KeyEvent.VK_L)); // L
        keymap.put(new Integer(186	), new Integer(KeyEvent.VK_SEMICOLON)); // ;
        keymap.put(new Integer(222	), new Integer(KeyEvent.VK_QUOTE)); // '
        keymap.put(new Integer(13	), new Integer(KeyEvent.VK_ENTER)); // Enter
        keymap.put(new Integer(90	), new Integer(KeyEvent.VK_Z)); // Z
        keymap.put(new Integer(88	), new Integer(KeyEvent.VK_X)); // X
        keymap.put(new Integer(67	), new Integer(KeyEvent.VK_C)); // C
        keymap.put(new Integer(86	), new Integer(KeyEvent.VK_V)); // V
        keymap.put(new Integer(66	), new Integer(KeyEvent.VK_B)); // B
        keymap.put(new Integer(78	), new Integer(KeyEvent.VK_N)); // N
        keymap.put(new Integer(77	), new Integer(KeyEvent.VK_M)); // M
        keymap.put(new Integer(188	), new Integer(KeyEvent.VK_COMMA)); // ,
        keymap.put(new Integer(190	), new Integer(KeyEvent.VK_DECIMAL)); // .
        keymap.put(new Integer(191	), new Integer(KeyEvent.VK_SLASH)); // /
        keymap.put(new Integer(32	), new Integer(KeyEvent.VK_SPACE)); // Space
        keymap.put(new Integer(112	), new Integer(KeyEvent.VK_F1)); // F1
        keymap.put(new Integer(113	), new Integer(KeyEvent.VK_F2)); // F2
        keymap.put(new Integer(114	), new Integer(KeyEvent.VK_F3)); // F3
        keymap.put(new Integer(115	), new Integer(KeyEvent.VK_F4)); // F4
        keymap.put(new Integer(116	), new Integer(KeyEvent.VK_F5)); // F5
        keymap.put(new Integer(117	), new Integer(KeyEvent.VK_F6)); // F6
        keymap.put(new Integer(118	), new Integer(KeyEvent.VK_F7)); // F7
        keymap.put(new Integer(119	), new Integer(KeyEvent.VK_F8)); // F8
        keymap.put(new Integer(120	), new Integer(KeyEvent.VK_F9)); // F9
        keymap.put(new Integer(121	), new Integer(KeyEvent.VK_F10)); // F10
        keymap.put(new Integer(122	), new Integer(KeyEvent.VK_F11)); // F11
        keymap.put(new Integer(123	), new Integer(KeyEvent.VK_F12)); // F12
        keymap.put(new Integer(111	), new Integer(KeyEvent.VK_SLASH)); // /
        keymap.put(new Integer(42	), new Integer(KeyEvent.VK_ASTERISK)); // *
        keymap.put(new Integer(45	), new Integer(KeyEvent.VK_MINUS)); // -
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_7)); // 7
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_8)); // 8
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_9)); // 9
        keymap.put(new Integer(43	), new Integer(KeyEvent.VK_PLUS)); // +
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_4)); // 4
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_5)); // 5
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_6)); // 6
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_1)); // 1
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_2)); // 2
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_3)); // 3
        keymap.put(new Integer(0	), new Integer(KeyEvent.VK_0)); // 0
        keymap.put(new Integer(46	), new Integer(KeyEvent.VK_DELETE)); // Del
        keymap.put(new Integer(13	), new Integer(KeyEvent.VK_ENTER)); // Enter
        keymap.put(new Integer(36	), new Integer(KeyEvent.VK_HOME)); // Home
        keymap.put(new Integer(38	), new Integer(KeyEvent.VK_UP)); // Up
        keymap.put(new Integer(33	), new Integer(KeyEvent.VK_PAGE_UP)); // PgUp
        keymap.put(new Integer(37	), new Integer(KeyEvent.VK_LEFT)); // Left
        keymap.put(new Integer(39	), new Integer(KeyEvent.VK_RIGHT)); // Right
        keymap.put(new Integer(35	), new Integer(KeyEvent.VK_END)); // End
        keymap.put(new Integer(40	), new Integer(KeyEvent.VK_DOWN)); // Down
        keymap.put(new Integer(34	), new Integer(KeyEvent.VK_PAGE_DOWN)); // PgDn
        keymap.put(new Integer(45	), new Integer(KeyEvent.VK_INSERT)); // Ins
        keymap.put(new Integer(46	), new Integer(KeyEvent.VK_DELETE)); // Del
    }
    
    private int convAscii(int ascii) {
        Integer scancode;
        scancode = (Integer)keymap.get(new Integer(ascii));
        if (scancode==null)
            return -1;
        else
            return scancode.intValue();
    }
    
    private Robot createRobot() {
        try {
            return new Robot();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public void keyPress(int keycode) {
        int scancode = convAscii(keycode);
        if (scancode >= 0)
            robot.keyPress(scancode);
    }
    
    public void keyRelease(int keycode) {
        int scancode = convAscii(keycode);
        if (scancode >= 0)
            robot.keyRelease(scancode);
    }
    
    public void keyStroke(int keycode) {
        keyPress(keycode);
        keyRelease(keycode);
    }
    
    public void mouseMove(int x, int y) {
        robot.mouseMove(x, y);
    }
    
    public void mousePress(int buttons) {
        int mask = 0;
        System.out.println("mousePress:"+buttons);
        if ((buttons & 1) != 0) mask |= InputEvent.BUTTON1_MASK;
        if ((buttons & 2) != 0) mask |= InputEvent.BUTTON3_MASK;
        if ((buttons & 4) != 0) mask |= InputEvent.BUTTON2_MASK;
        robot.mousePress(mask);
    }
    
    public void mouseRelease(int buttons) {
        int mask = 0;
        if ((buttons & 1) != 0) mask |= InputEvent.BUTTON1_MASK;
        if ((buttons & 2) != 0) mask |= InputEvent.BUTTON3_MASK;
        if ((buttons & 4) != 0) mask |= InputEvent.BUTTON2_MASK;
        robot.mouseRelease(mask);
    }
    
    public void mouseStroke(int buttons) {
        mousePress(buttons);
        mouseRelease(buttons);
    }
    
    public void setScreenBound(Rectangle bound) {
        screenbound = bound;
        if (DEBUG) System.out.println(getClass().getName() + ": Screen setup at " + screenbound.width + "x" + screenbound.height);
    }
    
    public Rectangle getScreenBound() {
        return screenbound;
    }
    
    public Rectangle getDefaultScreenBound() {
        // get screen bound
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        return gd.getDefaultConfiguration().getBounds();
    }
    
    public BufferedImage captureScreen() {
        screenimage = robot.createScreenCapture(screenbound);
        return screenimage;
        
    }
    
    
    
}

 

 由上面AjaxVNC的源代码可以在需要监控的服务中生成一张图片。

 

 

一个线程监控类ScreenScanner 如下:

public class ScreenScanner implements Runnable

 

 public void run() {
        
        tileman.setSize((int)kvmman.getScreenBound().getWidth(), (int)kvmman.getScreenBound().getHeight());
        starttime = System.currentTimeMillis();
        while(true) {
            try {
                
                tileman.processImage(kvmman.captureScreen(), 6,6);
                notifyObservers();
                numscan++;
                //Thread.sleep(300);
                currenttime = System.currentTimeMillis();
                if (DEBUG) {
                    System.out.println(getClass().getName() + ": FPS = " + (double)numscan/(currenttime-starttime)*1000);
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    private void notifyObservers()
    {
        for (int i=0; i<tileman.getNumXTile(); i++) {
            for (int j=0; j<tileman.getNumYTile(); j++) {
                if (tileman.getTile(i, j).isDirty()) {
                    tileobs.tileChanged(i, j);
                }
                tileman.getTile(i, j).clearDirty();
            }
        }
    }

 

 

 

使用如下:

1。将AjaxVNC组件中MinioHttpd.jar放在classpath路径下。

2。将AjaxVNC组件中源代码拷到,项目Src目录中。

3。将AjaxVNC组件中Bin中的几个文件拷贝到bin目录(重点是run.bat的批处理文件)

4。将相关的AJAX文件 md5.js,ajaxvnc.js,以及remotedeshtop.jsp放在web目录中。

 

启动web服务器,双击run.bat启动AjaxVNC的远程监控(其实也可以通过java程序在项目启动时自动run.bat的命令即可,减少客户操作)。远程即可看到桌面。

 在特定的文件中。

远程监控的桌面如下:http://<server ip address>:8086/remotedesktop.html

其中:server ip address:为特定的监控IP,

         8086:特定的端口。

 

 

 

分享到:
评论

相关推荐

    AjaxVNC 一个简单的远程控制服务器

    AjaxVNC 是一款轻量级的远程控制服务器,它利用了 AJAX(Asynchronous JavaScript and XML)技术和 VNC(Virtual Network ...了解和掌握AjaxVNC的原理和使用方法,对于提升IT运维效率和远程协作体验具有重要意义。

    UseGithubWorkflowsAsYourPC:借助AjaxVNC和ngrok,现在可以拥有Free Cloud PC

    它包含Ngrok,用于为远程桌面公开localhost AjaxVNC(其无密码,无客户端(仅需要浏览器))JRE,用于运行AjaxVNC 注意1:使用github工作流处理更复杂的事情是@ Google61(检查他的项目!他甚至使用工作流来挖掘...

    chrome-headless-shell-win64-135.0.7004.0 (Canary).zip

    chrome-headless-shell-win64-135.0.7004.0 (Canary).zip

    建筑工地个人防护装备分割系统源码&数据集分享.zip

    建筑工地个人防护装备分割系统源码&数据集分享

    车牌字符识别系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示].zip

    车牌字符识别系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

    实时可调的DDS信号发生器设计:基于FPGA的Verilog编程,灵活控制波形与频率的调制系统,(可实时切波形并控制频率)DDS信号发生器设计 FPGA设计,verilog设计,DDS信号发生器:基

    实时可调的DDS信号发生器设计:基于FPGA的Verilog编程,灵活控制波形与频率的调制系统,(可实时切波形并控制频率)DDS信号发生器设计。 FPGA设计,verilog设计,DDS信号发生器:基于调制方式灵活可控的信号发生器DDS。 可生成ASK调制波、FSK调制波、正弦波、矩形波、三角波、锯齿波等多种波形。 方案采用三个按键控制,可按键控制不同信号、不同频率输出。 按键1:控制切六种波形(ASK调制波,FSK调制波,正弦波,矩形波,三角波,锯齿波)。 按键2:每按下一次,频率增加1 10。 按键3:每按下一次,频率减小1 10。 频率相位可灵活调整。 ,核心关键词: 1. DDS信号发生器设计 2. FPGA设计 3. Verilog设计 4. 调制方式 5. 波形切换 6. 频率控制 7. 按键控制 8. 频率相位调整 用分号分隔的关键词结果为:DDS信号发生器设计;FPGA设计;Verilog设计;调制方式;波形切换;频率控制;按键控制;频率相位调整;,灵活控制的FPGA设计:基于DDS技术的信号发生器实现方案

    FIRC资源搜索工具.zip

    一个windows上使用的搜索小工具

    在线考试系统 2024免费JAVA毕设

    2024免费毕业设计成品,包括源码+数据库+往届论文资料 启动教程:https://www.bilibili.com/video/BV11ktveuE2d 讲解视频:https://www.bilibili.com/video/BV1YfkHYwEME 二次开发教程:https://www.bilibili.com/video/BV1Cw2rY1ErC

    南大洋印度洋扇区沉积物核心记录的新见解

    标题:基于放射虫和硅藻的温度重建揭示南大洋印度洋扇区表面及亚表面温度变化 内容: 一项最新的研究在南大洋印度洋扇区的四个沉积物核心中提出了五项新的温度记录。这项研究使用了名为SORAD的数据集作为训练集,应用Imbrie和Kipp方法对经过对数转换的数据进行分析,重建了基于放射虫的亚表面温度(subST)。此外,通过利用Crosta等人于2020年编制的包含249个样本的数据集,并采用现代类似种技术应用于33个硅藻分类单元的相对丰度,预测了基于硅藻的海表温度(SST)。 特别地,来自DCR-1PC(Crosta等人,2020年)和MD19-3580(本研究)的核心SST记录以及来自MD19-3575CQ和MD19-3580(本研究)的核心subST记录被重采样至每3千年一个间隔,以便从SST中减去subST在这两个核心站点的值(DCR-1PC/MD19-3575CQ和MD19-3580),从而重建表面与亚表面之间温度差(deltaT)的演变过程。本研究包括了这些重采样数据和deltaT记录,为理解该地区过去气候条件提供了重要线索。

    人脸表情行为识别系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示].zip

    人脸表情行为识别系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

    zabbix监控系统软件包

    Zabbix 适用于各种规模的企业和组织,特别是那些需要监控复杂 IT 基础设施的用户。以下是一些使用 Zabbix 的优势: 开源免费:Zabbix 是开源软件,可以免费使用,且社区活跃,文档丰富。 高度可定制:用户可以根据需求自定义监控项、告警规则和报表。 跨平台支持:支持多种操作系统(Linux、Windows、Unix等)和数据库(MySQL、PostgreSQL、Oracle等)。 强大的社区支持:Zabbix 拥有庞大的用户社区,用户可以轻松找到解决方案和最佳实践。 企业级功能:支持高可用性、分布式监控和自动化运维,适合企业级应用。 该软件包使用rockylinux8、mysql、nginx。 软件使用:解压,createrepo创建本地yum仓库,创建仓库文件,可yum或dnf 下载,详细见:https://blog.csdn.net/m0_74744227/article/details/145389193

    豆类苗叶检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示].zip

    豆类苗叶检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

    PHP魔众视频管理系统 v4.1.0.zip

    魔众视频管理系统是由国内领先的技术团队开发商进行开发的,是搭建视频系统最佳选择的程序源码,支持多端同步浏览。 魔众视频管理系统,轻量级视频管理系统。 2022年08月19日魔众视频管理系统发布v4.1.0版本,增加了以下27个特性: ·[新功能] 文件上传切片最大2M,分片文件根据配置动态清除 ·[新功能] 图标库中新增cube图标 ·[新功能] UEditorPlus升级到2.3.0 ·[新功能] bodyProperties可为body标签增加属性 ·[新功能] 图标库新增 check-simple ·[新功能] Cookie库新增属性参数 ·[新功能] 后台管理员角色新增备注字段 ·[新功能] Grid编辑操作名称可自定义 ·[新功能] FileUtil新增文件大小精简格式化 ·[新功能] 登录界面全新改版大气美观 ·[新功能] Type类型数据新增导出JS配置文件 ·[新功能] 富文本组件新增 editor-ready 自定义事件 ·[新功能] Response新增停止执行抛出消息的方法 ·[新功能] Grid新增批量弹窗快捷操作方式 ·[新功能] Json组件增加高度可配

    永磁同步电机Q15焦点:无传感自适应滑膜算法及SMO-C语言定点代码与仿真模型示例解析,永磁同步电机无传感自适应滑模 滑膜观测器(SMO)-示例C语言定点代码和仿真模型,Q15 foc pmsm 使用

    永磁同步电机Q15焦点:无传感自适应滑膜算法及SMO_C语言定点代码与仿真模型示例解析,永磁同步电机无传感自适应滑模 滑膜观测器(SMO)_示例C语言定点代码和仿真模型,Q15 foc pmsm 使用“自适应”滑模算法消除一阶滤波器 ,核心关键词:永磁同步电机; 无传感自适应滑模; 滑膜观测器(SMO); C语言定点代码; Q15; foc pmsm; 自适应滑模算法; 一阶滤波器。 关键词用分号分隔为:永磁同步电机; 无传感; 自适应滑模; 滑膜观测器(SMO); C语言定点代码; Q15; foc pmsm; 一阶滤波器。,"自适应滑模算法在永磁同步电机中的应用:Q15定点代码与仿真模型示例"

    Python泰勒图实现多模型可视化对比:源码高逼格版展示与使用教程,Python高逼格泰勒图及源码~,多模型对比~可视化工具 ,Python; 泰勒图; 高逼格; 多模型对比; 可视化工具,Pytho

    Python泰勒图实现多模型可视化对比:源码高逼格版展示与使用教程,Python高逼格泰勒图及源码~,多模型对比~可视化工具 ,Python; 泰勒图; 高逼格; 多模型对比; 可视化工具,Python泰勒图高逼格展示:多模型对比可视化工具源码

    基于WOA-XGBoost的回归预测:以优化XGBoost树参数的改进算法研究(基于MATLAB代码,包括评价指标),基于鲸鱼算法优化极限梯度提升树的数据回归预测(WOA-XGBoost) 鲸鱼算法W

    基于WOA-XGBoost的回归预测:以优化XGBoost树参数的改进算法研究(基于MATLAB代码,包括评价指标),基于鲸鱼算法优化极限梯度提升树的数据回归预测(WOA-XGBoost) 鲸鱼算法WOA优化极限梯度提升树XGBoost树的数量、树的深度和学习率 基于MATLAB环境 替自己的数据即可 代码注释清晰 适合学习 回归预测的评价指标包括平均绝对误差 均方误差 均方根误差 平均绝对百分比误差以及关联系数 ,WOA-XGBoost; 鲸鱼算法优化; 树的数量; 树的深度; 学习率优化; MATLAB环境; 代码注释清晰; 回归预测; 评价指标,基于WOA-XGBoost算法的数据回归预测模型

    2025最新唯美社区StarFree源码

    介绍: 在2025年,随着技术的不断进步和用户需求的多样化,社区平台的设计与功能也在不断演变。StarFree作为一款全新的唯美风格社区源码,凭借其独特的设计理念和强大的功能,迅速成为了开发者与用户关注的焦点。本文将为您详细介绍StarFree源码的特点、功能以及应用场景。 1. 唯美设计,极致视觉体验 StarFree源码以“唯美”为核心设计理念,采用了简约而不失优雅的UI设计。整体界面以柔和的色调为主,搭配流畅的动画效果,为用户带来极致的视觉享受。无论是社区主页、个人中心,还是帖子详情页,每一个细节都经过精心打磨,确保用户在浏览时感受到舒适与愉悦。 2. 模块化架构,灵活扩展 StarFree采用模块化架构设计,开发者可以根据需求自由组合或扩展功能模块。无论是社交互动、内容发布,还是用户管理,每个模块都独立且高效,便于二次开发与定制。这种设计不仅降低了开发难度,还提高了系统的可维护性。 3. 多端适配,无缝体验 StarFree源码支持多端适配,包括Web端、移动端(iOS/Android)以及小程序。无论用户通过哪种设备访问社区,都能获得一致且流畅的体验。源码内置响应式布局

    open3d qt6demo示例

    open3d qt6demo示例

    武理多媒体信息共享平台 2024免费JAVA毕设

    2024免费毕业设计成品,包括源码+数据库+往届论文资料 启动教程:https://www.bilibili.com/video/BV11ktveuE2d 讲解视频:https://www.bilibili.com/video/BV1YfkHYwEME 二次开发教程:https://www.bilibili.com/video/BV1Cw2rY1ErC

Global site tag (gtag.js) - Google Analytics