自定义webIpad证件相机(webRTC)
该技术方案可用于各浏览器自定义相机开发
相机UI(index.html)
<!DOCTYPE html>
<html lang="zh" prew="-1"><head><meta charset="UTF-8"><meta name="viewport"content="user-scalable=no, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width" /><title>自定义相机</title><link rel="stylesheet" href="./style.css"><script src="./tools.js"></script><script src="./index.js"></script>
</head><body><div class="errTip"><p>Failed to obtain the rear camera of the device. Please try another solution to obtain resources!</p><button class="errBtn">GO Back</button></div><div class="takeOffTip"></div><div class="imgBoxDom"><div class="imgBox"><img src="./center.png" style="width: 4vw;"></div></div><div class="rightBtnBox"><div class="takeBtn"></div><div class="cancleBtn btn"></div></div><div class="bottomBtnBox"><div class="reTakeBtn btn bottonSize"></div><div class="nextBtn btn bottonSize"></div></div><div class="loading-css">Loading...</div>
</body></html>
相机UI样式(style.css)
* {margin: 0;padding: 0;box-sizing: border-box;border: 0;
}html,
body {width: 100%;height: 100%;overflow: hidden;background-color: #000;color: #fff;
}.cancleBtn {padding: 2vw 0;width: 100%;
}.takeOffTip {position: fixed;padding-top: 2vw;top: 0;left: 0;width: 100%;font-size: 1.8vw;text-align: center;color: #fff;
}.bottonSize {height: 100%;line-height: 6vw;line-height: 6dvw;padding: 0 1.5vw;
}.bottomBtnBox,
.rightBtnBox {position: fixed;right: 0;display: flex;justify-content: space-between;align-items: center;background-color: #000;z-index: 10;
}.bottomBtnBox {bottom: 0;width: 100%;height: 6vw;height: 6dvw;
}.rightBtnBox {flex-direction: column;top: 0;height: 100%;width: 6vw;width: 6dvw;
}html[prew='-1'] .bottomBtnBox,
html[prew='0'] .bottomBtnBox,
html[prew='-1'] .rightBtnBox,
html[prew='1'] .rightBtnBox,
html[prew='1'] .customer_carema {display: none;
}html[prew='1'] .imgBox {border: 0;font-size: 0;opacity: 0;
}.takeBtn {padding: 4px;width: 5vw;width: 5dvw;height: 5vw;height: 5dvw;background-color: #fff;border-radius: 50%;
}.takeBtn::before {content: '';display: block;width: 100%;height: 100%;border: 5px solid #000;background-color: #fff;border-radius: 50%;box-sizing: border-box;
}.rightBtnBox::before {content: '';display: block;
}.btn {background-color: #000;text-align: center;font-size: 1.5vw;color: #fff;
}.customer_video,
.carema_img,
.cuteImg {width: 100%;height: 100%;object-fit: cover;
}.imgBoxDom {position: fixed;top: 0;left: 0;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;z-index: 9;
}.imgBox {width: var(--carema-box-width);height: var(--carema-box-height);border: 2px solid #fff;display: flex;justify-content: center;align-items: center;font-size: 10vw;z-index: 10;border-radius: 2vw;
}.errTip {position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 8888;display: none;flex-direction: column;justify-content: center;align-items: center;background-color: #000;
}.errTip>p {padding-bottom: 20px;color: #fff;
}.errTip button {padding: 10px 30px;
}html[prew='2'] .errTip {display: flex;
}html[loaded='1'] .loading-css {display: none;
}.loading-css {position: fixed;top: 0;left: 0;width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;align-items: center;background-color: #000;z-index: 9999;
}.loading-css::before {margin-bottom: 10px;content: '';width: 50px;height: 50px;display: inline-block;border: 3px solid #f3f3f3;border-top: 3px solid rgb(160, 155, 155);border-radius: 50%;animation: loading-360 0.8s infinite linear;
}@keyframes loading-360 {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}
}
调试UI(carema.html)
<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8"><meta name="viewport"content="user-scalable=no, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width" /><title>调试相机</title><style>* {margin: 0;padding: 0;box-sizing: border-box;border: 0;}img {max-width: 100%;}.btnList {padding: 10px;}label[type='file'],button {padding: 0 10px;height: 32px;line-height: 32px;display: inline-block;font-size: 14px;appearance: auto;border: 1px solid #999;background-color: #dcdcdc;}label>input {font-size: 0;width: 0;height: 0;overflow: hidden;}.showImg {padding: 5px;display: flex;flex-wrap: wrap;}.showImg>.box {width: 33.33%;padding: 5px;}.showImg>.box>.img {width: 100%;height: 20vw;overflow: hidden;border-radius: 10px;border: 2px solid #888;}.showImg>.box>.img>img {width: 100%;height: 100%;object-fit: cover;}html,body {height: 100%;height: 100%;}body {display: flex;flex-direction: column;}.showImg {flex: 1;overflow-x: hidden;}</style>
</head><body><div class="btnList"><button onclick="openCarema('HK_ID')">COMM_ID_IMG</button><button onclick="openCarema('LANDING')">LANDING_IMG</button><label name="upload" type="file">LOCAL_IMG<input type="file" id="upload"></label></div><div class="showImg" id="showImg"></div>
</body>
<script>function fileToBase64(file) {return new Promise((resolve, reject) => {// 创建一个新的 FileReader 对象var reader = new FileReader();// 读取 File 对象reader.readAsDataURL(file);// 加载完成后reader.onload = function () {// 将读取的数据转换为 base64 编码的字符串var base64String = reader.result.split(",")[1];// 解析为 Promise 对象,并返回 base64 编码的字符串resolve(base64String);};// 加载失败时reader.onerror = function () {reject(new Error("Failed to load file"));};});}function showImg(url) {var showImgDom = document.getElementById('showImg');var img = document.createElement('img');img.src = `data:image/jpeg;base64,${url}`;var div = document.createElement('div');var cDiv = document.createElement('div');div.append(cDiv);cDiv.append(img);div.className = 'box';cDiv.className = "img";showImgDom.insertBefore(div, showImgDom.firstChild);}document.getElementById('upload').addEventListener('change', function ($event) {var file = $event.target.files[0];fileToBase64(file).then(showImg);})function openCarema(idType) {var openId = Date.now() + '';window.open(`./index.html?openId=${openId}&idType=${idType}&isDev=1`);window.addEventListener('message', function (res) {var resOpenId = res.data.openId;var mothod = res.data.mothod;var file = res.data.imgUrl;console.log(resOpenId, mothod, file);if (mothod === "success_file" && openId === resOpenId) fileToBase64(file).then(showImg);})}
</script></html>
相机逻辑基础(index.js)
function WbCRM() {this.body = document.body;this.html = document.documentElement;this.takeBtn = document.querySelector('.takeBtn');this.imgBox = document.querySelector('.imgBox');this.reTakeBtn = document.querySelector('.reTakeBtn');this.cancleBtn = document.querySelector('.cancleBtn');this.nextBtn = document.querySelector('.nextBtn');var errBtn = document.querySelector('.errBtn');this.video = null;this.err = null;this.fullImg = null;this.file = '';this.idType = '';this.isDev = false;this.stream = null;this.openId = '';this.ratio = window.devicePixelRatio || 1;this.videoWidth = this.body.clientWidth * this.ratio;this.videoHeight = this.body.clientHeight * this.ratio;this.html.setAttribute('prew', '-1');var isMp3 = !(navigator.userAgent.match(/Firefox/));var audio = new Audio();audio.autoplay = isMp3 ? './shutter.mp3' : './shutter.ogg';this.audio = audio;console.log(isMp3,audio);this.mediaDevices = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ?navigator.mediaDevices : ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? {getUserMedia: function (c) {return new Promise(function (y, n) {(navigator.mozGetUserMedia ||navigator.webkitGetUserMedia).call(navigator, c, y, n);});}} : null);this.setDom();this.setCarema();this.takeBtn.addEventListener('click', this.takePhoto.bind(this));this.nextBtn.addEventListener('click', this.next.bind(this));this.reTakeBtn.addEventListener('click', this.reTake.bind(this));this.cancleBtn.addEventListener('click', this.cancle.bind(this));errBtn.addEventListener('click', this.openErro.bind(this));
}
WbCRM.prototype.openErro = function () {this.sendMsg('open_erro');
}
WbCRM.prototype.cancle = function () {this.removeStream();this.sendMsg('off_carema');
}
WbCRM.prototype.next = function () {if (this.fullImg) this.fullImg.remove();this.removeStream();this.sendMsg('success_file');
}
WbCRM.prototype.reTake = function () {this.file = null;this.err = null;if (this.fullImg) this.fullImg.remove();this.html.setAttribute('loaded', 0);this.removeStream();this.setCarema();
}
WbCRM.prototype.cutImage = function () {var boxWidth = this.imgBox.clientWidth * this.ratio;var boxHeight = this.imgBox.clientHeight * this.ratio;var vLeft = (this.videoWidth - boxWidth) / 2;var vTop = (this.videoHeight - boxHeight) / 2;var nCanvas = wbCRMTools.drawHighDefinitionImg(boxWidth, boxHeight);var nCtx = nCanvas.getContext('2d');nCtx.drawImage(this.fullImg, -vLeft, -vTop);var cutImage = nCtx.getImageData(0, 0, boxWidth, boxHeight);wbCRMTools.changeImgData(cutImage?.data || [], this.idType || '');nCtx.putImageData(cutImage, 0, 0);reImgUrl = nCanvas.toDataURL('image/jpeg');var cImg = document.createElement('img');cImg.src = reImgUrl;this.file = wbCRMTools.canvas2File(reImgUrl);wbCRMTools.clearCanvas(nCtx, nCanvas);cImg.className = "cuteImg";this.imgBox.append(cImg);this.html.setAttribute('prew', '1');this.removeStream();
}
WbCRM.prototype.takePhoto = function () {var gCanvas = wbCRMTools.drawHighDefinitionImg(this.videoWidth, this.videoHeight);var originalCtx = gCanvas.getContext('2d');originalCtx.drawImage(this.video, 0, 0, this.videoWidth, this.videoHeight);var imgUrl = gCanvas.toDataURL('image/jpeg');var fullImg = document.createElement("img");fullImg.className = "carema_img";fullImg.src = imgUrl;this.fullImg = fullImg;this.body.append(fullImg);wbCRMTools.clearCanvas(originalCtx, gCanvas);this.audio.play();fullImg.onload = this.cutImage.bind(this);
}WbCRM.prototype.sendMsg = function (mothod) {this.audio.remove();const origin = this.isDev ? undefined : window.location.origin;window.opener.postMessage({ mothod: mothod, file: this.file, openId: this.openId, error: this.err }, origin);window.close();
}WbCRM.prototype.removeStream = function () {var self = this;if (self.stream) {self.stream.getTracks().forEach(function (track) {if (track.readyState === 'live') track.stop();self.stream.removeTrack(track);});}if (this.video) this.video.remove();var cuteImgList = document.querySelectorAll('.cuteImg');cuteImgList.forEach(function (dom) {dom.remove();})
}WbCRM.prototype.setDom = function () {this.openId = wbCRMTools.getUrlParam('openId');var okText = wbCRMTools.getUrlParam('continue');var cancelText = wbCRMTools.getUrlParam('cancel');var retakeText = wbCRMTools.getUrlParam('retake');var idType = wbCRMTools.getUrlParam('idType') || '';var takeOffTip = wbCRMTools.getUrlParam('takeOffTip');const isDev = wbCRMTools.getUrlParam('isDev');this.isDev = isDev === '1';this.nextBtn.innerText = okText || 'Cuntinue';this.cancleBtn.innerText = cancelText || 'Cancel';this.reTakeBtn.innerText = retakeText || 'Retake';document.querySelector('.takeOffTip').innerHTML = takeOffTip;this.html.setAttribute('loaded', 0);this.html.style.setProperty('--carema-box-width', '64.512vw');this.html.style.setProperty('--carema-box-height', '40.6789vw');if (idType === "LANDING") {this.html.style.setProperty('--carema-box-width', '51.2vw');this.html.style.setProperty('--carema-box-height', '44.5935vw');}this.idType = idType;
}WbCRM.prototype.setVideo = function (stream) {var video = document.createElement('video');video.setAttribute('autoplay', 'autoplay');video.setAttribute('playsinline', 'playsinline');video.className = 'customer_video';this.video = video;this.stream = stream;this.body.append(video);var self = this;video.onloadedmetadata = function (e) {self.stream = stream;self.loaded = true;self.html.setAttribute('loaded', 1);};video.onplay = function () {self.html.setAttribute('prew', '0');}// as window.URL.createObjectURL() is deprecated, adding a check so that it works in Safari.// older browsers may not have srcObjectif ("srcObject" in video) {video.srcObject = stream;} else {// using URL.createObjectURL() as fallback for old browsersvideo.src = window.URL.createObjectURL(stream);}
}WbCRM.prototype.setCarema = function () {const videoConf = this.isDev ? {} : {width: { min: 1024, ideal: 2360, max: 2732 },height: { min: 776, ideal: 1640, max: 2048 },facingMode: { exact: "environment" }}var self = this;this.mediaDevices.getUserMedia({audio: false,video: videoConf}).then(this.setVideo.bind(this)).catch(function (error) {self.err = error.toString();self.html.setAttribute('prew', '2');self.html.setAttribute('loaded', '1');})
}window.addEventListener('load', function () {var wbCRM = new WbCRM();window.addEventListener('visibilitychange', function () {wbCRM.removeStream();window.close();});
});
图片出路和文件生成工具(tools.js)
var wbCRMTools = {drawHighDefinitionImg: function (width, height) {const canvas = document.createElement('canvas');canvas.style.width = width + 'px';canvas.style.height = height + 'px';canvas.width = width;canvas.height = height;return canvas;},clearCanvas: function (ctx, canvas) {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();canvas.height = 0;canvas.width = 0;canvas.remove();canvas.parentNode?.removeChild(canvas);},changeImgData: function (data, idType) {const isGrayscale = ['PASSPORT', 'LANDING', 'ENTRYPERMIT', 'SUP_LEGAL_ID'].some(imgType => idType.indexOf(imgType) !== -1);let contrast = 35;const thereshold = 20;if ('LANDING' === idType) contrast = 45;// gaussBlur will use in the feature, cancel this fun now, don`t delete please// this.gaussBlur(imageData, 1);// If MacId and HK-LANDING change cavans-img-code.const factor = (255 + contrast) / (255.01 - contrast); //add .1 to avoid /0 errorconst denominator = 1 / (1 - contrast / 255) - 1;const setCV = cv => cv + (cv - thereshold) * denominator;const setCTV = cv => cv + (cv - thereshold) * contrast / 255;const getRGB = cv => factor * (cv - 128) + 128;// Data array data-length.const len = data?.length || 0;// loop value to change cavans imgData;for (let index = 0; index < len; index += 4) {let R = data[index]; //r valuelet G = data[index + 1]; //g valuelet B = data[index + 2] //b valueif (contrast || thereshold) {R = getRGB(R); //r valueG = getRGB(G); //g valueB = getRGB(B); //b value}const isColorNum = index % 4 === 0;if (isColorNum) {R = contrast ? setCV(R) : setCTV(R);G = contrast ? setCV(G) : setCTV(G);B = contrast ? setCV(B) : setCTV(B);if (isGrayscale) {const vNum = Math.round((R + G + B) / 3);R = vNum;G = vNum;B = vNum;data[index + 3] = 255;}data[index] = R;data[index + 1] = G;data[index + 2] = B;}}},getUrlParam: function (urlKey) {var url = window.location.search;var reg = new RegExp("(^|&)" + urlKey + "=([^&]*)(&|$)");var result = url.substring(1).match(reg);return result ? decodeURIComponent(result[2]) : null;},canvas2File: function (dataUrl) {let arr = dataUrl.split(','),mime = arr[0].match(/:(.*?);/)[1],bstr = atob(arr[1]),n = bstr.length,u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}const nowId = Date.now();const fileName = `takePhoto_${nowId}.jpeg`;const blob = new Blob([u8arr], { type: mime, name: fileName });blob.lastModifiedDate = new Date();return new File([blob], fileName, { type: "image/jpeg" });}
}
文件目录
效果图


相关文章:

自定义webIpad证件相机(webRTC)
该技术方案可用于各浏览器自定义相机开发 相机UI(index.html) <!DOCTYPE html> <html lang"zh" prew"-1"><head><meta charset"UTF-8"><meta name"viewport"content"user-sc…...
GO发票真伪批量查验方法、数电票查验接口
“教”给机器标注数据的正确率就决定了人工智能判断的正确率。翔云人工智能开放平台的OCR产品经过我们的开发人员精心调“教”,识别率高、识别速度快。 发票,是发生的成本、费用或收入的原始凭证。于公司来说,发票主要是公司做账的依据&…...

【Go系列】Go的UI框架Fyne
前言 总有人说Go语言是一门后端编程语言。 Go虽然能够很好地处理后端开发,但是者不代表它没有UI库,不能做GUI,我们一起来看看Go怎么来画UI吧。 正文 Go语言由于其简洁的语法、高效的性能和跨平台的编译能力,非常适合用于开发GUI…...
.NET MAUI:跨平台开发的未来
常用资源 (1).NET MAUI8构建应用文档。 Build your first .NET MAUI app - .NET MAUI | Microsoft Learn 一、什么是 .NET MAUI? .NET Multi-platform App UI (.NET MAUI) 是微软推出的一款跨平台开发框架。作为 Xamarin.Forms 的下一代产…...

VSCode切换默认终端
我的VSCode默认终端为PowerShell,每次新建都会自动打开PowerShell。但是我想让每次都变为cmd,也就是Command Prompt 更改默认终端的操作方法如下: 键盘调出命令面板(CtrlShiftP)中,输入Terminal: Select Default Prof…...
卫星观测叶绿素的相反信号
Contrasted Trends in Chlorophyll-a Satellite Products 运用卫星产品研究Chl的长时间序列变化时需要注意 Introduction (1)研究叶绿素的长期变化,需要至少40年的长时间序列; (2)Tian and Zhang 2023报告…...

2024年最新NVIDIA T4价格表及行业趋势!
英伟达(NVIDIA)作为目前全球T0级别的GPU制造商,其T4系列显卡以其卓越的计算性能和能效比,在数据中心、云计算及AI领域占据重要地位。 一、NVIDIA T4价格表概览 在探讨NVIDIA T4显卡的价格时,我们需要从直接购买和租赁…...
HTML + CSS编程规范
编程规范 HTML CSS 命名规范 HTML CSS 命名规范 1. 命名需要是具备语义性的单词,不能用 数字 拼音 数字,符号开头正确示范 : wrap description title content错误示范 : aaaa a1 $we 4tdds 2. 命名需要多个单词连接的情况下, 标记语言中可以使用 …...

机器学习之人脸识别-使用 scikit-learn 和人工神经网络进行高效人脸识别
文章摘要 本文将介绍如何使用 Python 的 scikit-learn 库和人工神经网络(ANN)来识别人脸。我们将使用 LFW 数据集(Labeled Faces in the Wild),这是一个广泛用于人脸识别基准测试的大型人脸数据库。我们将展示如何准备…...

【虚拟化】KVM概念和架构
目录 一、什么是KVM? 二、KVM的功能 2.1 主要的功能 2.2 其它功能 三、KVM核心组件及作用 四、KVM与VMware的优势 五、KVM架构 六、qemu介绍 七、创建虚拟机流程 一、什么是KVM? Kernel-based Virtual Machine的简称,KVM 是基于虚拟…...

【Linux】权限2
Linux文件要被执行满足两个条件: ①必须要具备可执行权限 x ②真的是一个可执行程序 1.权限的修改,文件强行给别人 权限就是拦住一批人,不让他做特定的一件事情 a.更改人,更改文件所隶属的人 如果把文件强行给别人, chown xxx(普通用户) xxx(文件名) 会出现下面的情况 很明显…...

汽车长翅膀:GPU 是如何加速深度学习模型的训练和推理过程的?
编者按:深度学习的飞速发展离不开硬件技术的突破,而 GPU 的崛起无疑是其中最大的推力之一。但你是否曾好奇过,为何一行简单的“.to(‘cuda’)”代码就能让模型的训练速度突飞猛进?本文正是为解答这个疑问而作。 作者以独特的视角&…...

怀旧必玩!重返童年,扫雷游戏再度登场!
Python提供了一个标准的GUI(图形用户界面)工具包:Tkinter。它可以用来创建各种窗口、按钮、标签、文本框等图形界面组件。 而且Tkinter 是 Python 自带的库,无需额外安装。 Now,让我们一起来回味一下扫雷小游戏吧 扫…...
Avalonia中的路由事件
文章目录 一、路由事件的基本概念事件路由机制事件的生命周期二、创建路由事件定义路由事件触发路由事件处理路由事件三、使用路由事件的场景用户输入控件交互动画和样式数据绑定和验证四、路由事件的优缺点优点:缺点:五、总结在Avalonia中,路由事件是处理用户交互和控件之间…...
ubuntu20.04安装RabbitMQ +Erlang
ubuntu20.04安装RabbitMQ 3.11.19Erlang 25.3.1_ubuntu20.04.6 安装 rabbitmq-CSDN博客 LINUX下载编译libpng_linux libpng下载-CSDN博客 Ubuntu20.04 安装 Nginx 软件报错:libgd3 缺少 libpng12-0 依赖 Ubuntu安装RabbitMq(保姆级教学,直…...

【word转pdf】【最新版本jar】Java使用aspose-words实现word文档转pdf
【aspose-words-22.12-jdk17.jar】word文档转pdf 前置工作1、下载依赖2、安装依赖到本地仓库 项目1、配置pom.xml2、配置许可码文件(不配置会有水印)3、工具类4、效果 踩坑1、pdf乱码2、word中带有图片转换 前置工作 1、下载依赖 通过百度网盘分享的文…...

分布式:RocketMQ/Kafka总结(附下载链接)
文章目录 下载链接思维导图 本文总结的是关于消息队列的常见知识总结。消息队列和分布式系统息息相关,因此这里就将消息队列放到分布式中一并进行处理关联 下载链接 链接: https://pan.baidu.com/s/1hRTh7rSesikisgRUO2GBpA?pwdutgp 提取码: utgp 思维导图...

Air780EP模块 LuatOS开发-MQTT接入阿里云应用指南
简介 本文简单讲述了利用LuatOS-Air进行二次开发,采用一型一密、一机一密两种方式认证方式连接阿里云。整体结构如图 关联文档和使用工具:LuatOS库阿里云平台 准备工作 Air780EP_全IO开发板一套,包括天线SIM卡,USB线 PC电脑&…...
【算法】插入区间
难度:中等 题目: 给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInte…...
C++ 代码实现socket 类使用TCP/IP进行通信 (windows 系统)
C 代码实现socket 类使用TCP/IP进行通信 (windows 系统) TCP客户端通信常规步骤: 1.初始换socket环境 2.socket()创建TCP套接字。 3.connect()建立到达服务器的连接。 4.与客户端进行通信,recv()/send()接受/发送信息࿰…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...