当前位置: 首页 > news >正文

Springboot+Vue3 整合海康获取视频流并展示

目录

1.后端

1.1 导入依赖

1.2 代码实战

2.前端

2.1 首先安装海康的web插件,前端vue3代码如下:


1.后端

1.1 导入依赖

        <dependency><groupId>com.hikvision.ga</groupId><artifactId>artemis-http-client</artifactId><version>1.1.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.51</version></dependency>

1.2 代码实战

新建ArtemisPostTest类,代码如下,设置认证信息,设置ArtemisConfig的host,appKey(AK),appSecret(SK)。设置接口URL。.设置接口入参。接口调用,调用doPostStringArtemis方法(该方法内部实现了登入认证逻辑),传入接口URL,接口入参,数据提交类型等信息,完成接口调用。

public class ArtemisPostTest {static {ArtemisConfig.host = "";// 代理API网关nginx服务器ip端口ArtemisConfig.appKey = "";// 秘钥appkeyArtemisConfig.appSecret = "";// 秘钥appSecret}/*** 能力开放平台的网站路径* TODO 路径不用修改,就是/artemis*/private static final String ARTEMIS_PATH = "/artemis";public static String path="";public static JSONObject jsonBody=new JSONObject();/*** 调用POST请求类型(application/json)接口,这里以入侵报警事件日志为例* https://open.hikvision.com/docs/918519baf9904844a2b608e558b21bb6#e6798840** @return*/public static String callPostStringApi(){/*** http://10.33.47.50/artemis/api/scpms/v1/eventLogs/searches* 根据API文档可以看出来,这是一个POST请求的Rest接口,而且传入的参数值为一个json* ArtemisHttpUtil工具类提供了doPostStringArtemis这个函数,一共六个参数在文档里写明其中的意思,因为接口是https,* 所以第一个参数path是一个hashmap类型,请put一个key-value,query为传入的参数,body为传入的json数据* 传入的contentType为application/json,accept不指定为null* header没有额外参数可不传,指定为null**/final String getCamsApi = ARTEMIS_PATH +ArtemisPostTest.path;Map<String, String> path = new HashMap<String, String>(2) {{put("https://", getCamsApi);//根据现场环境部署确认是http还是https}};String body = jsonBody.toJSONString();String result = ArtemisHttpUtil.doPostStringArtemis(path,body,null,null,"application/json",null);// post请求application/json类型参数return result;}}

新建实体类Camera代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Camera {String cameraIndexCode;String camereName;String url;
}

在Controller中进行调用,代码如下:

首先根据分页获取监控点列表,这里获取的是第三页,每一页为六个监控点。

查询监控点列表v2

接口说明

根据条件查询目录下有权限的监控点列表

接口版本

v2

接口地址

/api/resource/v2/camera/search

请求方法

POST

数据提交方式

application/json

 然后根据监控点唯一的标识获取预览url

接口说明

1.平台正常运行;平台已经添加过设备和监控点信息。
2.平台需要安装mgc取流服务。
3.三方平台通过openAPI获取到监控点数据,依据自身业务开发监控点导航界面。
4.调用本接口获取预览取流URL,协议类型包括:hik、rtsp、rtmp、hls。
5.通过开放平台的开发包进行实时预览或者使用标准的GUI播放工具进行实时预览。
6.为保证数据的安全性,取流URL设有有效时间,有效时间为5分钟。

接口版本

v2

接口地址

/api/video/v2/cameras/previewURLs

请求方法

POST

数据提交方式

application/json

最后就是进行属性的赋值,赋值给建立的Camera 实体类。

    @GetMapping("/test")public List<Camera> test(){ArtemisPostTest.path="/api/resource/v1/cameras";ArtemisPostTest.jsonBody.clear();ArtemisPostTest.jsonBody.put("pageNo", 3);ArtemisPostTest.jsonBody.put("pageSize", 6);String StringeResult = ArtemisPostTest.callPostStringApi();JSONObject result = (JSONObject) JSONObject.parse(StringeResult);String data = result.get("data").toString();JSONObject resultdata = (JSONObject) JSONObject.parse(data);JSONArray list = resultdata.getJSONArray("list");
//        System.out.println("StringeResult结果示例: "+JSONObject.toJSON(StringeResult));
//		callPostImgStringApi();ArtemisPostTest.path="/api/video/v2/cameras/previewURLs";List<Camera> cameraList=new ArrayList<>();for (Object i : list) {Camera camera=new Camera();JSONObject jsonObject = (JSONObject) JSONObject.parse(i.toString());String cameraIndexCode = jsonObject.get("cameraIndexCode").toString();String cameraName = jsonObject.get("cameraName").toString();ArtemisPostTest.jsonBody.clear();ArtemisPostTest.jsonBody.put("cameraIndexCode",cameraIndexCode);ArtemisPostTest.jsonBody.put("protocol","rtsp");String m = ArtemisPostTest.callPostStringApi();JSONObject jsonObject1 = (JSONObject) JSONObject.parse(m);JSONObject data1 = (JSONObject) jsonObject1.get("data");String url = data1.get("url").toString();camera.setCameraIndexCode(cameraIndexCode).setCamereName(cameraName).setUrl(url);cameraList.add(camera);}return cameraList;}

2.前端

2.1 首先安装海康的web插件,前端vue3代码如下:

<template><div style="display: flex;flex-direction: row;flex-wrap: wrap"><div v-for="item in state.list"><el-button @click="go(item.cameraIndexCode)">预览{{item.camereName}}</el-button></div></div><div class="main" ref="playWndBox"><divid="playWnd"class="playWnd":style="{height: playWndHeight + 'px',width: playWndWidth + 'px',}"></div></div>
</template><script setup>import { ref, onMounted, onBeforeUnmount, getCurrentInstance, nextTick,reactive } from 'vue'import axios from "../utils/axios.js"const playWndBox = ref(null)let playWndHeight = ref('')let playWndWidth = ref('')let pubKey = ref('')let oWebControl = ref(null)let objData = ref({appkey: "",                 //海康提供的appkeyip: "",                     //海康提供的ipsecret: "",                 //海康提供的secretport: 443,playMode: 0,        // 0 预览 1回放layout: "1x1",      //页面展示的模块数【16】})const go=(data)=>{previewVideo(data)}const state=reactive({list:[]})const load=()=>{axios.get("/test").then(res=>{state.list=resconsole.log(res)})}load()onMounted(() => {// 获取页面的实例对象const pageInstance = getCurrentInstance();// 获取dom节点对象const tagDomObj = pageInstance.refs.playWndBox;playWndHeight.value = tagDomObj.clientHeight;playWndWidth.value = tagDomObj.clientWidth;// 监听scroll事件,使插件窗口尺寸跟随DIV窗口变化window.addEventListener("scroll", () => {console.log(5);// returnif (oWebControl.value != null) {oWebControl.JS_Resize(tagDomObj.clientWidth,tagDomObj.clientHeight);this.setWndCover();}});// 监听resize事件,使插件窗口尺寸跟随DIV窗口变化window.addEventListener("resize", (e) => {console.log(0);if (oWebControl.value != null) {oWebControl.JS_Resize(tagDomObj.clientWidth,tagDomObj.clientHeight);this.setWndCover();}});// 初始化播放器插件nextTick(() => {initPlugin();})})onBeforeUnmount(() => {if (oWebControl.value != null) {// 先让窗口隐藏,规避可能的插件窗口滞后于浏览器消失问题oWebControl.JS_HideWnd();// 销毁当前播放的视频oWebControl.JS_RequestInterface({ funcName: "destroyWnd" });// 断开与插件服务连接oWebControl.JS_Disconnect();}})const initPlugin = () => {oWebControl = new WebControl({szPluginContainer: "playWnd", // 指定容器idiServicePortStart: 15900, // 指定起止端口号,建议使用该值iServicePortEnd: 15900,szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11", // 用于IE10使用ActiveX的clsidcbConnectSuccess: () => {// 创建WebControl实例成功oWebControl.JS_StartService("window", {// WebControl实例创建成功后需要启动服务// 值"./VideoPluginConnect.dll"写死dllPath: "./VideoPluginConnect.dll",}).then(function () {// 设置消息回调oWebControl.JS_SetWindowControlCallback({// cbIntegrationCallBack: cbIntegrationCallBack,});//JS_CreateWnd创建视频播放窗口,宽高可设定oWebControl.JS_CreateWnd("playWnd", 1152, 581, { bEmbed: true })//这一部分很重要,两个参数为你盒子的宽高,这样是写死是防止组件加载之前出现白屏;bEmbed: true 防止窗口闪烁.then(function () {// 创建播放实例成功后初始化init();});},function () {// 启动插件服务失败});},// 创建WebControl实例失败cbConnectError: function () {// 这里写创建WebControl实例失败时的处理步骤,下面的代码仅做参看,具体实现步骤根据个人需求进行编写!!!!!!!!// console.log(0);// oWebControl.value = null;// // 程序未启动时执行error函数,采用wakeup来启动程序// window.WebControl.JS_WakeUp("VideoWebPlugin://");// initCount++;// if (initCount < 3) {//   setTimeout(function () {//     initPlugin();//   }, 3000);// } else {//   setTimeout(function () {//     setTimeout(function () {//       $router.push('/home/PlugDown')//     }, 4000)//   }, 4000)// }},cbConnectClose: () => {// 异常断开:bNormalClose = false// JS_Disconnect正常断开:bNormalClose = true// console.log("cbConnectClose");oWebControl.value = null;},});// oWebControl.JS_CuttingPartWindow(500, 500, 500, 500);}// 初始化const init = (callback) => {getPubKey(() => {let appkey = objData.value.appkey;                   //综合安防管理平台提供的appkey,必填let secret = setEncrypt(objData.value.secret); //综合安防管理平台提供的secret,必填let ip = objData.value.ip;                           //综合安防管理平台IP地址,必填let playMode = objData.value.playMode;                //初始播放模式:0-预览,1-回放let port = objData.value.port;                        //综合安防管理平台端口,若启用HTTPS协议,默认443let snapDir = "D:\\SnapDir";                        //抓图存储路径let videoDir = "D:\\VideoDir";                      //紧急录像或录像剪辑存储路径let layout = objData.value.layout;                   //playMode指定模式的布局let enableHTTPS = 1;                                //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1let encryptedFields = "secret";                     //加密字段,默认加密领域为secretlet showToolbar = 1;                                //是否显示工具栏,0-不显示,非0-显示let showSmart = 0;                                  //是否显示移动框线框,0-不显示,非0-显示let buttonIDs ="0,16,256,257,258,259,260,512,513,514,515,516,517,768,769"; //自定义工具条按钮// var toolBarButtonIDs = "2049,2304" // 工具栏上自定义按钮oWebControl.JS_RequestInterface({funcName: "init",argument: JSON.stringify({appkey: appkey,         //API网关提供的appkeysecret: secret,         //API网关提供的secretip: ip,                 //API网关IP地址playMode: playMode,      //播放模式(决定显示预览还是回放界面)port: port,             //端口snapDir: snapDir,       //抓图存储路径videoDir: videoDir,     //紧急录像或录像剪辑存储路径layout: layout,         //布局enableHTTPS: enableHTTPS,   //是否启用HTTPS协议encryptedFields: encryptedFields, //加密字段showToolbar: showToolbar, //是否显示工具栏showSmart: showSmart,   //是否显示智能信息buttonIDs,              //自定义工具条按钮}),}).then(function (oData) {oWebControl.JS_Resize(playWndWidth.value, playWndHeight.value); // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题if (callback) {callback();}// 隐藏// oWebControl.JS_HideWnd()});});}// RSA 加密let setEncrypt = (value) => {let encrypt = new window.JSEncrypt();encrypt.setPublicKey(pubKey);return encrypt.encrypt(value);}// 获取公钥const getPubKey = (callback) => {oWebControl.JS_RequestInterface({funcName: "getRSAPubKey",argument: JSON.stringify({keyLength: 1024,}),}).then(function (oData) {if (oData.responseMsg.data) {pubKey = oData.responseMsg.data;callback();}});}// 调用这个函数可进行视频播放// 视频预览功能const previewVideo = (data) => {let cameraIndexCode = data;  // 获取输入的监控点编号值,必填let streamMode = 0;          // 主子码流标识:0-主码流,1-子码流let transMode = 1;           // 传输协议:0-UDP,1-TCPlet gpuMode = 0;             // 是否启用GPU硬解,0-不启用,1-启用let wndId = -1;              // 播放窗口序号(在2x2以上布局下可指定播放窗口)oWebControl.JS_RequestInterface({funcName: "startPreview",argument: JSON.stringify({cameraIndexCode: cameraIndexCode, // 监控点编号streamMode: streamMode,                 // 主子码流标识transMode: transMode,                   // 传输协议gpuMode: gpuMode,                       // 是否开启GPU硬解wndId: wndId,                           // 可指定播放窗口}),}).then(function () {oWebControl.JS_SetWindowControlCallback({});});}</script><style scoped>.main {position: fixed;top: 50%;left: 20%;transform: translateY(-50%);width: 60vw;height: 60vh;margin: auto;background-color: #ccc;}
</style>

相关文章:

Springboot+Vue3 整合海康获取视频流并展示

目录 1.后端 1.1 导入依赖 1.2 代码实战 2.前端 2.1 首先安装海康的web插件&#xff0c;前端vue3代码如下&#xff1a; 1.后端 1.1 导入依赖 <dependency><groupId>com.hikvision.ga</groupId><artifactId>artemis-http-client</artifactId&g…...

Linux——进程退出

目录 一.进程退出时有三种选择&#xff1a; 1.1 echo $?命令&#xff1a; 功能&#xff1a; 打印距离现在最近一次执行某进程的退出码 例2代码&#xff1a; 例3&#xff1a; 例4代码&#xff1a; 1.3 进程运行过程中可能会出现的错误种类&#xff1a; 二.总结&#xff…...

组长给组员派活,把组长自己的需求和要改的bug派给组员,合理吗?

组长把自己的工作派给手下&#xff0c;合理吗&#xff1f; 一位程序员问&#xff1a; 组长给他派活&#xff0c;把组长自己的需求或者要改的bug派给他。组长分派完需求之后&#xff0c;他一个人干两个项目&#xff0c;组长却无所事事&#xff0c;这样合理吗&#xff1f; 有人说…...

Spring注解开发——bean的作用范围与生命周期管理

文章目录 1.bean管理1.1 bean作用范围Scope注解 1.2 bean生命周期PostConstructPreDestroy 2.小结 1.bean管理 1.1 bean作用范围 Scope注解 不写或者添加Scope(“singleton”)表示的是单例 如何配置多例&#xff1f; 在Scope(“prototype”)表示的是多例 1.2 bean生命周…...

C++ > Cmake

目录 编译器 多文件编译与链接 Makefile构建系统 编译器 厂商 C C GNU gcc g main.cpp #include <cstdio>int main() {printf("Hello, world!\n");return 0; }编译器, 是一个根据源代码生成机器码的程序 g main.cpp -o a.out调用编译器程序g, 读…...

Spring的Bean的生命周期

Spring的Bean的生命周期 Spring的Bean的生命周期 Spring的Bean的生命周期 Spring的Bean的生命周期包括以下阶段&#xff1a; &#xff08;1&#xff09;实例化Instantiation&#xff08;2&#xff09;填充属性Populate properties&#xff08;3&#xff09;处理Aware接口的回调…...

在树莓派上搭建WordPress博客网站,并内网穿透发布到公网

✨个人主页&#xff1a;bit me&#x1f447; 目 录 &#x1f43e;概述&#x1f490;安装 PHP&#x1f338;安装MySQL数据库&#x1f337;安装 Wordpress&#x1f340;设置您的 WordPress 数据库&#x1f339;设置 MySQL/MariaDB&#x1f33b;创建 WordPress 数据库 &#x1f33…...

跨平台C++ Qt数据库管理系统设计与实战:从理论到实践的全面解析

跨平台C Qt数据库管理系统设计与实战&#xff1a;从理论到实践的全面解析 一、引言&#xff08;Introduction&#xff09;1.1 数据库管理系统的重要性&#xff08;Importance of Database Management Systems&#xff09;1.2 C和Qt在数据库管理系统中的应用&#xff08;Applica…...

Ubuntu crontab定时任务

1. crontab 相关的命令&#xff1a; 安装&#xff1a;apt-get install cron 启动&#xff1a;service cron start 重启&#xff1a;service cron restart 停止&#xff1a;service cron stop 检查状态&#xff1a;service cron status 查询cron可用的命令&#xff1a;service …...

ChatGPT Prompt Engineering for Developers 大语言模型引导词指导手册

以下内容均整理来自deeplearning.ai的同名课程 Location 课程访问地址 https://learn.deeplearning.ai/chatgpt-prompt-eng 一、Guidelines for Prompting 引导语的编写原则 Prompting Principles 引导语编写原则 Principle 1: Write clear and specific instructions编写清晰…...

【Vue】二:Vue核心处理---模板语法

文章目录 1.模板语法---插值2.模板语法---指令语法2.1v-once2.2 v-bind2.3 v-model2.4 v-on 3.MVVM4.事件回调函数中的this 1.模板语法—插值 {{可以写什么}} &#xff08;1&#xff09;在data中声明的变量&#xff0c;函数 &#xff08;2&#xff09;常量 &#xff08;3&…...

windows环境下nginx+ftp服务器搭建简易文件服务器

这里写目录标题 1&#xff0c;前言2&#xff0c;FTP服务器搭建3&#xff0c;nginx安装 1&#xff0c;前言 几种文件服务器的对比 1&#xff0c;直接使用ftp服务器&#xff0c;访问图片路径为 ftp://账户:密码192.168.0.106/31275-105.jpg不采用这种方式&#xff0c;不安全容易…...

【数据结构与算法】图的概述(内含源码)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️数据结构与算法】 学习名言&#xff1a;天子重英豪&#xff0c;文章教儿曹。万般皆下品&#xff0c;惟有读书高——《神童诗劝学》 系列文章目录 第一章 ❤️ 学前知识 第二章 ❤️ 单向链表 第三章…...

SAP MM 根据采购订单反查采购申请

如何通过采购订单号查询到其前端的采购申请号。 首先从采购申请的相关报表着手&#xff0c;比如ME5A, 发现它是可以满足需求的。 例如&#xff1a;如下的采购订单&#xff0c; 该订单是由采购申请10003364转过来的。 如果想通过这个采购订单找到对应的采购申请&#xff0c;在…...

C语言程序设计题/C语言计算机二级考前押题版

C语言程序设计题/C语言计算机二级考试押题版 与 数位 和 数 有关 求max与min 任意四个数 运算符和表达式版本 #include <stdio.h> int main( ) {int a,b,c,d;int max,min;printf("please input 4 integers:");scanf("%d%d%d%d", &a, &b, …...

Canonical标签在SEO中重要作用

canonical标签是很多搜索引擎都支持的一个标签&#xff0c;它的作用是标记某一网页的唯一url地址。这样做的目的是保证我们的某一网页在搜索引擎中只有一个唯一的地址。 Canonical标签对于一些入行不久的人来说&#xff0c;可能会有些陌生。但这个标签是很多搜索引擎都支持的标…...

【Linux之进程间通信】06.Linux进程通信 - 共享内存

【Linux之进程间通信】 项目代码获取&#xff1a;https://gitee.com/chenshao777/linux-processes.git &#xff08;麻烦点个免费的Star哦&#xff0c;您的Star就是我的写作动力&#xff01;&#xff09; 06.共享内存 共享内存是Linux进程间的通信方式之一 创建共享内存函数…...

oracle安装

服务端安装&#xff08;公司中不需要&#xff0c;只安装客户端就行&#xff09; 1、挂载一个Windows系统 双击vmx文件 启动 2、网络配置 添加一个网络 自己电脑看控制面板是否添加虚拟网卡 查看连接的网络&#xff0c;ip地址不能为1&#xff0c;为1就自己修改&#xff0c;…...

CSS样式的三种引入方式及优先级

说明&#xff1a;网页开发有三种技术&#xff0c;分别是html、css和js&#xff0c;分别对应页面的结构、表现和动作。css样式引入&#xff0c;是指把对页面的渲染作用到html上&#xff0c;有以下三种方式&#xff1a;行内式、内嵌式和外联式。 第一种&#xff1a;行内式&#…...

Linux第二天

上传 scp -r 本地文件路劲 用户名目标主机地址&#xff1a;路径 下载&#xff1a;scp -r 用户名目标主机地址&#xff1a;路径 本地目录 ls -A /root //查看root文件下所有的隐藏文件 命令&#xff1a;ls 选项&#xff1a; -l&#xff1a;查看文件属性 -h&#xff1a;文…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势

一、WebRTC与智能硬件整合趋势​ 随着物联网和实时通信需求的爆发式增长&#xff0c;WebRTC作为开源实时通信技术&#xff0c;为浏览器与移动应用提供免插件的音视频通信能力&#xff0c;在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能&#xff0c;对实时…...

第22节 Node.js JXcore 打包

Node.js是一个开放源代码、跨平台的、用于服务器端和网络应用的运行环境。 JXcore是一个支持多线程的 Node.js 发行版本&#xff0c;基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。 本文主要介绍JXcore的打包功能。 JXcore 安装 下载JXcore安装包&a…...