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

JavaScript系列(48)-- 3D渲染引擎实现详解

JavaScript 3D渲染引擎实现详解 🎮

今天,让我们深入探讨JavaScript的3D渲染引擎实现。通过WebGL和现代JavaScript技术,我们可以构建一个功能完整的3D渲染系统。

3D渲染基础概念 🌟

💡 小知识:3D渲染引擎的核心是将三维场景转换为二维图像的过程。这涉及到场景图管理、几何变换、光照计算、材质渲染等多个方面。通过WebGL,我们可以直接访问GPU,实现高性能的3D渲染。

基本实现 📊

// 1. 渲染引擎核心
class Engine3D {constructor(canvas) {this.canvas = canvas;this.gl = canvas.getContext('webgl2');if (!this.gl) {throw new Error('WebGL2 not supported');}this.scene = new Scene();this.camera = new Camera();this.renderer = new Renderer(this.gl);this.initGL();}// 初始化WebGL上下文initGL() {this.gl.enable(this.gl.DEPTH_TEST);this.gl.enable(this.gl.CULL_FACE);this.gl.cullFace(this.gl.BACK);}// 设置视口大小setViewport(width, height) {this.canvas.width = width;this.canvas.height = height;this.gl.viewport(0, 0, width, height);this.camera.updateAspect(width / height);}// 渲染循环render() {this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);this.renderer.render(this.scene, this.camera);}
}// 2. 场景管理
class Scene {constructor() {this.objects = [];this.lights = [];}// 添加对象add(object) {if (object.isLight) {this.lights.push(object);} else {this.objects.push(object);}object.parent = this;}// 移除对象remove(object) {const array = object.isLight ? this.lights : this.objects;const index = array.indexOf(object);if (index !== -1) {array.splice(index, 1);object.parent = null;}}// 遍历场景traverse(callback) {this.objects.forEach(object => {callback(object);if (object.children) {object.traverse(callback);}});}
}// 3. 相机系统
class Camera {constructor() {this.position = new Vector3(0, 0, 5);this.target = new Vector3(0, 0, 0);this.up = new Vector3(0, 1, 0);this.fov = 45;this.aspect = 1;this.near = 0.1;this.far = 1000;this.updateProjectionMatrix();this.updateViewMatrix();}// 更新投影矩阵updateProjectionMatrix() {this.projectionMatrix = Matrix4.perspective(this.fov,this.aspect,this.near,this.far);}// 更新视图矩阵updateViewMatrix() {this.viewMatrix = Matrix4.lookAt(this.position,this.target,this.up);}// 更新相机位置lookAt(target) {this.target.copy(target);this.updateViewMatrix();}
}

高级功能实现 🚀

// 1. 几何体系统
class Geometry {constructor() {this.vertices = [];this.indices = [];this.normals = [];this.uvs = [];this.vertexBuffer = null;this.indexBuffer = null;this.normalBuffer = null;this.uvBuffer = null;}// 设置顶点数据setVertices(vertices) {this.vertices = vertices;this.updateVertexBuffer();}// 设置索引数据setIndices(indices) {this.indices = indices;this.updateIndexBuffer();}// 计算法线computeNormals() {this.normals = new Array(this.vertices.length);for (let i = 0; i < this.indices.length; i += 3) {const i1 = this.indices[i];const i2 = this.indices[i + 1];const i3 = this.indices[i + 2];const v1 = new Vector3().fromArray(this.vertices, i1 * 3);const v2 = new Vector3().fromArray(this.vertices, i2 * 3);const v3 = new Vector3().fromArray(this.vertices, i3 * 3);const normal = Vector3.cross(Vector3.subtract(v2, v1),Vector3.subtract(v3, v1)).normalize();this.normals[i1] = normal;this.normals[i2] = normal;this.normals[i3] = normal;}this.updateNormalBuffer();}
}// 2. 材质系统
class Material {constructor() {this.uniforms = {diffuseColor: new Vector3(1, 1, 1),specularColor: new Vector3(1, 1, 1),shininess: 32.0,opacity: 1.0};this.vertexShader = null;this.fragmentShader = null;this.program = null;}// 编译着色器compile(gl) {const vertexShader = this.compileShader(gl, gl.VERTEX_SHADER, this.vertexShader);const fragmentShader = this.compileShader(gl, gl.FRAGMENT_SHADER, this.fragmentShader);this.program = gl.createProgram();gl.attachShader(this.program, vertexShader);gl.attachShader(this.program, fragmentShader);gl.linkProgram(this.program);if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {throw new Error('Program linking failed: ' + gl.getProgramInfoLog(this.program));}}// 编译单个着色器compileShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {throw new Error('Shader compilation failed: ' + gl.getShaderInfoLog(shader));}return shader;}
}// 3. 光照系统
class Light {constructor() {this.isLight = true;this.color = new Vector3(1, 1, 1);this.intensity = 1.0;}
}class DirectionalLight extends Light {constructor() {super();this.direction = new Vector3(0, -1, 0);}
}class PointLight extends Light {constructor() {super();this.position = new Vector3();this.distance = 0;this.decay = 1;}
}

实际应用场景 💼

// 1. 3D模型加载器
class ModelLoader {constructor(engine) {this.engine = engine;}// 加载OBJ格式模型async loadOBJ(url) {const response = await fetch(url);const text = await response.text();const vertices = [];const normals = [];const uvs = [];const indices = [];const lines = text.split('\n');for (const line of lines) {const parts = line.trim().split(/\s+/);switch (parts[0]) {case 'v':  // 顶点vertices.push(parseFloat(parts[1]),parseFloat(parts[2]),parseFloat(parts[3]));break;case 'vn':  // 法线normals.push(parseFloat(parts[1]),parseFloat(parts[2]),parseFloat(parts[3]));break;case 'vt':  // 纹理坐标uvs.push(parseFloat(parts[1]),parseFloat(parts[2]));break;case 'f':  // 面for (let i = 1; i <= 3; i++) {const vertexData = parts[i].split('/');indices.push(parseInt(vertexData[0]) - 1);}break;}}const geometry = new Geometry();geometry.setVertices(vertices);geometry.setIndices(indices);if (normals.length > 0) {geometry.normals = normals;} else {geometry.computeNormals();}if (uvs.length > 0) {geometry.uvs = uvs;}return geometry;}
}// 2. 动画系统
class AnimationSystem {constructor() {this.animations = new Map();this.currentTime = 0;}// 添加关键帧动画addKeyframeAnimation(name, keyframes) {this.animations.set(name, {keyframes,duration: keyframes[keyframes.length - 1].time});}// 更新动画update(deltaTime) {this.currentTime += deltaTime;for (const [name, animation] of this.animations) {const time = this.currentTime % animation.duration;// 查找当前关键帧let frame1, frame2;for (let i = 0; i < animation.keyframes.length - 1; i++) {if (time >= animation.keyframes[i].time && time < animation.keyframes[i + 1].time) {frame1 = animation.keyframes[i];frame2 = animation.keyframes[i + 1];break;}}if (frame1 && frame2) {const t = (time - frame1.time) / (frame2.time - frame1.time);this.interpolate(frame1, frame2, t);}}}// 插值计算interpolate(frame1, frame2, t) {// 实现关键帧插值return {position: Vector3.lerp(frame1.position, frame2.position, t),rotation: Quaternion.slerp(frame1.rotation, frame2.rotation, t),scale: Vector3.lerp(frame1.scale, frame2.scale, t)};}
}// 3. 物理系统
class PhysicsSystem {constructor() {this.objects = [];this.gravity = new Vector3(0, -9.81, 0);}// 添加物理对象addObject(object, mass = 1) {this.objects.push({object,mass,velocity: new Vector3(),acceleration: new Vector3()});}// 更新物理update(deltaTime) {for (const obj of this.objects) {// 应用重力obj.acceleration.add(this.gravity);// 更新速度obj.velocity.add(Vector3.multiply(obj.acceleration, deltaTime));// 更新位置obj.object.position.add(Vector3.multiply(obj.velocity, deltaTime));// 重置加速度obj.acceleration.set(0, 0, 0);}// 碰撞检测this.detectCollisions();}// 碰撞检测detectCollisions() {for (let i = 0; i < this.objects.length; i++) {for (let j = i + 1; j < this.objects.length; j++) {const obj1 = this.objects[i];const obj2 = this.objects[j];if (this.checkCollision(obj1, obj2)) {this.resolveCollision(obj1, obj2);}}}}
}

性能优化技巧 ⚡

// 1. 渲染优化
class RenderOptimizer {constructor(engine) {this.engine = engine;this.frustumCuller = new FrustumCuller();this.occlusionCuller = new OcclusionCuller();}// 视锥体剔除cullFrustum(scene, camera) {this.frustumCuller.updateFrustum(camera);return scene.objects.filter(object => this.frustumCuller.isVisible(object));}// 遮挡剔除cullOcclusion(objects) {return this.occlusionCuller.getVisibleObjects(objects);}// LOD管理updateLOD(objects, camera) {for (const object of objects) {if (object.lod) {const distance = Vector3.distance(object.position,camera.position);object.updateLOD(distance);}}}
}// 2. 内存管理
class ResourceManager {constructor() {this.geometries = new Map();this.textures = new Map();this.materials = new Map();}// 加载几何体async loadGeometry(url) {if (this.geometries.has(url)) {return this.geometries.get(url);}const loader = new ModelLoader();const geometry = await loader.loadOBJ(url);this.geometries.set(url, geometry);return geometry;}// 加载纹理async loadTexture(url) {if (this.textures.has(url)) {return this.textures.get(url);}return new Promise((resolve, reject) => {const image = new Image();image.onload = () => {const texture = new Texture(image);this.textures.set(url, texture);resolve(texture);};image.onerror = reject;image.src = url;});}// 释放资源unload(url) {if (this.geometries.has(url)) {const geometry = this.geometries.get(url);geometry.dispose();this.geometries.delete(url);}if (this.textures.has(url)) {const texture = this.textures.get(url);texture.dispose();this.textures.delete(url);}}
}// 3. 渲染批处理
class BatchRenderer {constructor(gl) {this.gl = gl;this.batches = new Map();}// 添加到批次addToBatch(object) {const key = this.getBatchKey(object);if (!this.batches.has(key)) {this.batches.set(key, {objects: [],vertices: [],indices: []});}const batch = this.batches.get(key);batch.objects.push(object);// 合并几何数据this.mergeGeometry(batch, object);}// 获取批次键getBatchKey(object) {return `${object.material.id}_${object.geometry.id}`;}// 合并几何数据mergeGeometry(batch, object) {const geometry = object.geometry;const baseVertex = batch.vertices.length / 3;// 添加顶点batch.vertices.push(...geometry.vertices);// 添加索引for (const index of geometry.indices) {batch.indices.push(index + baseVertex);}}// 渲染批次render() {for (const batch of this.batches.values()) {if (batch.objects.length === 0) continue;// 使用第一个对象的材质const material = batch.objects[0].material;material.use(this.gl);// 渲染合并后的几何体this.renderBatch(batch);}}
}

最佳实践建议 💡

  1. 性能优化策略
// 1. 渲染状态管理
class RenderStateManager {constructor(gl) {this.gl = gl;this.currentState = {program: null,texture: null,blending: false};}// 设置着色器程序useProgram(program) {if (this.currentState.program !== program) {this.gl.useProgram(program);this.currentState.program = program;}}// 设置纹理bindTexture(texture) {if (this.currentState.texture !== texture) {this.gl.bindTexture(this.gl.TEXTURE_2D, texture);this.currentState.texture = texture;}}// 设置混合setBlending(enable) {if (this.currentState.blending !== enable) {if (enable) {this.gl.enable(this.gl.BLEND);} else {this.gl.disable(this.gl.BLEND);}this.currentState.blending = enable;}}
}// 2. 着色器管理
class ShaderManager {constructor() {this.shaders = new Map();}// 注册着色器register(name, vertexSource, fragmentSource) {this.shaders.set(name, {vertex: vertexSource,fragment: fragmentSource});}// 获取着色器get(name) {return this.shaders.get(name);}// 编译着色器compile(gl, name) {const shader = this.get(name);if (!shader) return null;const program = new ShaderProgram(gl);program.compile(shader.vertex, shader.fragment);return program;}
}// 3. 调试工具
class DebugTools {constructor(engine) {this.engine = engine;this.stats = {drawCalls: 0,vertices: 0,triangles: 0};}// 开始性能分析beginProfile() {this.stats.drawCalls = 0;this.stats.vertices = 0;this.stats.triangles = 0;}// 记录绘制调用recordDrawCall(vertices, triangles) {this.stats.drawCalls++;this.stats.vertices += vertices;this.stats.triangles += triangles;}// 获取性能报告getStats() {return {...this.stats,fps: this.calculateFPS()};}// 显示调试信息showDebugInfo() {const stats = this.getStats();console.log(`FPS: ${stats.fps}Draw Calls: ${stats.drawCalls}Vertices: ${stats.vertices}Triangles: ${stats.triangles}`);}
}

结语 📝

JavaScript的3D渲染引擎实现是一个复杂但有趣的主题。通过本文,我们学习了:

  1. 3D渲染引擎的基本架构和实现
  2. 场景管理和相机系统
  3. 几何体、材质和光照系统
  4. 动画和物理系统
  5. 性能优化技巧和最佳实践

💡 学习建议:在实现3D渲染引擎时,要注意性能优化和内存管理。合理使用批处理、剔除和LOD等技术可以显著提升渲染性能。同时,要充分利用WebGL的特性,避免不必要的状态切换。


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

JavaScript系列(48)-- 3D渲染引擎实现详解

JavaScript 3D渲染引擎实现详解 &#x1f3ae; 今天&#xff0c;让我们深入探讨JavaScript的3D渲染引擎实现。通过WebGL和现代JavaScript技术&#xff0c;我们可以构建一个功能完整的3D渲染系统。 3D渲染基础概念 &#x1f31f; &#x1f4a1; 小知识&#xff1a;3D渲染引擎的…...

jmeter中对接口进行循环请求后获取相应数据

1、工作中遇到一个场景就是对某个单一接口进行循环请求&#xff0c;并需要获取每次请求后返回的相应数据&#xff1b; 2、首先就在jmeter对接口相关组件进行配置&#xff0c;需要组件有&#xff1a;循环控制器、CSV数据文件设置、计数器、访问接口、HTTP信息头管理器、正则表达…...

网络工程师 (4)存储系统

一、多级存储结构 &#xff08;一&#xff09;组成 寄存器&#xff1a; 寄存器是与CPU直接协调工作的高速存储器&#xff0c;用于加速存储器的访问速度。它通常用于存放操作数或作为地址寄存器&#xff0c;以加快地址转换速度。寄存器的数量有限&#xff0c;一般在几个到几百个…...

论文笔记(六十三)Understanding Diffusion Models: A Unified Perspective(六)(完结)

Understanding Diffusion Models: A Unified Perspective&#xff08;六&#xff09;&#xff08;完结&#xff09; 文章概括指导&#xff08;Guidance&#xff09;分类器指导无分类器引导&#xff08;Classifier-Free Guidance&#xff09; 总结 文章概括 引用&#xff1a; …...

oracle比较一下统计信息差异吧

统计信息发生了哪些变化&#xff1f; 从上次收集到最近一次收集有什么不同&#xff1f; set long 999999 longc 99999 line 100 select report, maxdiffpct from table(dbms_stats.diff_table_stats_in_history(SYS,T1,to_timestamp(2025-01-22 09:01:46,YYYY-MM-DD hh24:mi:s…...

Hive:内部表和外部表,内外转换

内部表和外部表 内部表示例 给表添加数据 外部表示例 给表添加数据 外部表示例 用location指定表目录位置,那么表的位置在实际指定的位置,但是可以被映射 外部表和内部表的区别 删除表后使用show tables in shao; 已经没有被删除的表,说明元数据已经被删除(mysql里面存放),但是…...

P1030 [NOIP2001 普及组] 求先序排列(c++)详解

题目链接&#xff1a;P1030 [NOIP2001 普及组] 求先序排列 - 洛谷 | 计算机科学教育新生态 思路&#xff1a; 1.先确定跟节点 2.根据根节点&#xff0c;划分出左右子树 中&#xff1a;BADC 后&#xff1a;BDCA 分析&#xff1a; 根据后序遍历&#xff0…...

Mac cursor设置jdk、Maven版本

基本配置 – Cursor 使用文档 首先是系统用户级别的设置参数&#xff0c;运行cursor&#xff0c;按下ctrlshiftp&#xff0c;输入Open User Settings(JSON)&#xff0c;在弹出的下拉菜单中选中下面这样的&#xff1a; 在打开的json编辑器中追加下面的内容&#xff1a; {"…...

提升企业内部协作的在线知识库架构与实施策略

内容概要 在当前快速变化的商业环境中&#xff0c;企业对于提升内部协作效率的需求愈显迫切。在线知识库作为信息存储与共享的平台&#xff0c;成为了推动企业数字化转型的重要工具。本文将深入探讨如何有效打造与实施在线知识库&#xff0c;强调架构设计、知识资产分类管理及…...

单链表专题(上)

链表的定义与创建 线性表&#xff1a; 1. 物理结构上不一定是线性的 2. 逻辑结构上一定是线性的 链表是一种物理存储结构上非连续&#xff0c;非顺序的存储结构 链表也是线性表的一种&#xff0c;但是在物理结构上不是连续的 链表是由一个一个的节点组成&#xff0c;需要数…...

.NET MAUI 入门学习指南

引言 在当今移动应用和跨平台开发的热潮中,.NET MAUI(Multi - platform App UI)应运而生,为开发者提供了一种高效、统一的方式来构建跨多个平台(如 iOS、Android、Windows 等)的原生应用。它整合了 Xamarin.Forms 的优点,并在此基础上进行了诸多改进和创新,使得开发者…...

Vue3.5 企业级管理系统实战(三):页面布局及样式处理 (Scss UnoCSS )

本章主要是关于整体页面布局及样式处理&#xff0c;在进行这一章代码前&#xff0c;先将前两章中的示例代码部分删除&#xff08;如Home.vue、About.vue、counter.ts、App.vue中引用等&#xff09; 1 整体页面布局 页面整体布局构成了产品的框架基础&#xff0c;通常涵盖主导…...

Excel中LOOKUP函数的使用

文章目录 VLOOKUP&#xff08;垂直查找&#xff09;&#xff1a;HLOOKUP&#xff08;水平查找&#xff09;&#xff1a;LOOKUP&#xff08;基础查找&#xff09;&#xff1a;XLOOKUP&#xff08;高级查找&#xff0c;较新版本Excel提供&#xff09;&#xff1a; 在Excel中&…...

【Unity】cinemachine核心知识

cinemachine核心知识 cinemachineVirtualCamera中body参数作用cinemachineVirtualCamera中body有哪些选项cinemachineVirtualCamera中am参数作用以及选项 cinemachineVirtualCamera中body参数作用 在 Unity 的 Cinemachine Virtual Camera 中&#xff0c;Body 参数模块主要负责…...

CMake常用命令指南(CMakeList.txt)

CMakeList从入门到精通的文章有很多不再赘述&#xff08; 此处附带一篇优秀的博文链接&#xff1a;一个简单例子&#xff0c;完全入门CMake语法与CMakeList编写 &#xff09;。 本文主要列举 CMake 中常用命令的详细说明、优缺点分析以及推荐做法&#xff0c;以更好地理解和灵…...

美创科技获浙江省网络空间安全协会年度表彰

近日&#xff0c;浙江省网络空间安全协会第二届理事会第三次会议在杭州隆重召开&#xff0c;会议总结部署工作、表彰先进、分享创新实践成果。 会上&#xff0c;省委网信办副主任马晓军出席会议并致辞、宋皆荣理事长向第二届理事会报告2024年协会工作、常务副理事长单位浙江联通…...

UE学习日志#14 GAS--ASC源码简要分析10 GC相关

注&#xff1a;1.这个分类是按照源码里的注释分类的 2.本篇是通读并给出一些注释形式的&#xff0c;并不涉及结构性的分析 3.看之前要对UE的GAS系统的定义有初步了解 4.因为都是接口函数&#xff0c;有些没细看的研究那一部分的时候会细看 1 一些接口函数&#xff0c;但是…...

Ubuntu 18.04安装Emacs 26.2问题解决

个人博客地址&#xff1a;Ubuntu 18.04安装Emacs 26.2问题解决 | 一张假钞的真实世界 no X development libraries were found checking for X... no checking for X... true configure: error: You seem to be running X, but no X development libraries were found. You …...

游戏引擎介绍:Game Engine

简介 定义&#xff1a;软件框架&#xff0c;一系列为开发游戏的工具的集合 可协作创意生产工具&#xff0c;复杂性艺术&#xff0c;注重realtime实时 目的 为艺术家&#xff0c;设计师&#xff0c;程序员设计工具链 游戏引擎开发参考书 推荐&#xff1a;Game Engine Archite…...

[A-29]ARMv8/v9-GIC-中断子系统的安全架构设计(Security/FIQ/IRQ)

ver0.1 前言 打开这篇文章的时候,我们已经为每一个中断信号规划一条路径,在外设和PE-Core之间建立了消息通道,外设有紧急的情况下可以给SOC中的大哥打报告了。下面就把接力棒就交到了CPU手里了,但是PE-Core要交给那个Exception Level以及Security下运行的软件处理呢?本文…...

启元世界(Inspir.ai)技术浅析(二):深度强化学习

深度强化学习(Deep Reinforcement Learning, DRL)是启元世界在人工智能领域的一项核心技术,广泛应用于游戏AI、智能决策等领域。 一、状态(State) 1.1 概念与作用 **状态(State)**是指智能体对环境的感知,是智能体进行决策的基础。在深度强化学习中,状态通常是一个高…...

能够对设备的历史数据进行学习与分析,通过与设备当前状态的比对,识别潜在故障并做出预判的名厨亮灶开源了。

明厨亮灶视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。AI技术可以24小时…...

Zookeeper(31)Zookeeper的事务ID(zxid)是什么?

在 Zookeeper 中&#xff0c;事务 ID&#xff08;zxid&#xff0c;ZooKeeper Transaction ID&#xff09;是一个全局唯一的标识符&#xff0c;用于标识每一个事务操作。每个写操作&#xff08;如创建节点、删除节点、更新节点数据等&#xff09;都会生成一个新的 zxid。zxid 是…...

Linux进程调度与等待:背后的机制与实现

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; 当一个进程发起某种操作&#xff08;如I/O请求、信号、锁的获取等&#xff09;&#xff0c;但该操作需要的资源暂时不可用时&#xff0c;进程会被操作系统挂起&#xff0c;进入“等待队列”或“阻塞状态”。…...

寒假1.25

题解 web:[极客大挑战 2019]Upload 打开环境 上传一个一句话木马试试 只能上传图片那就再上传一次&#xff0c;bp抓包修改type-content为image/jpeg试试 不行 看来是文件后缀被绕过了&#xff0c;上传一个.html然后抓包改类型试试 上传成功了&#xff0c;但是提示‘<&…...

28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正

这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能&#xff1a;报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...

C++/stack_queue

目录 1.stack 1.1stack的介绍 1.2stack的使用 练习题&#xff1a; 1.3stack的模拟实现 2.queue的介绍和使用 2.1queue的介绍 2.2queue的使用 2.3queue的模拟实现 3.priority_queue的介绍和使用 3.1priority_queue的介绍 3.2priority_queue的使用 欢迎 1.stack 1.1stack…...

【Java】微服务找不到问题记录can not find user-service

一、问题描述 运行网关微服务与用户微服务后&#xff0c;nacos服务成功注册 但是测试接口的时候网关没有找到相关服务 二、解决方案 我先检查了pom文件确定没问题后查看配置文件 最后发现是配置里spring.application.namexxx-user里面服务的名字后面多了一个空格 三、总结…...

QT:图像上绘制图形

需求描述 1、展示一张图像 2、在图像上可以使用数据绘制图像&#xff1a;矩形、不规则图形、线条 3、有按键可以选择 概要设计 规划布局如下 1、左边是Qlabel 用于展示图片 2、右边是三个按钮 具体实现 1、 首先设计 UI 界面&#xff0c;对控件进行布局 在 mainwindow.u…...

基于java线程池和EasyExcel实现数据异步导入

基于java线程池和EasyExcel实现数据异步导入 2.代码实现 2.1 controller层 PostMapping("import")public void importExcel(MultipartFile file) throws IOException {importService.importExcelAsync(file);}2.2 service层 Resource private SalariesListener sa…...