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

基于Node.js和Three.js的3D模型网页预览器

🎯 基于Node.js和Three.js的3D模型网页预览器

本文将详细介绍如何使用Node.js后端和Three.js前端技术栈,构建一个功能完整的3D模型在线预览工具。支持GLB/GLTF、OBJ、STL、PLY等多种3D模型格式的加载、预览和交互操作。

3d

📖 目录

  • 1. 项目概述
  • 2. 技术选型与架构
  • 3. 3D渲染原理详解
  • 4. 后端服务器实现
  • 5. 前端3D查看器核心
  • 6. 文件上传与管理
  • 7. 交互控制系统
  • 8. 光照与材质系统
  • 9. 性能优化策略
  • 10. 部署与扩展

1. 项目概述

1.1 功能特性

本项目实现了一个现代化的3D模型网页预览器,具备以下核心功能:

  • 多格式支持: GLB/GLTF、OBJ、STL、PLY等主流3D模型格式
  • 实时交互: 鼠标/触控控制的3D场景交互
  • 高质量渲染: 基于WebGL的硬件加速渲染
  • 文件管理: 完整的文件上传、存储、删除功能
  • 响应式设计: 适配桌面和移动设备

1.2 技术亮点

  • 🚀 现代Web技术栈: Node.js + Express + Three.js
  • 🎨 专业级渲染: PBR材质、实时阴影、抗锯齿
  • 📱 跨平台兼容: 支持主流浏览器和移动设备
  • 🔧 可扩展架构: 模块化设计,易于功能扩展

2. 技术选型与架构

2.1 整体架构图

用户浏览器
前端界面 HTML/CSS/JS
Three.js 3D引擎
WebGL渲染层
文件上传模块
Node.js Express服务器
Multer文件处理
文件存储系统
RESTful API

2.2 技术栈详解

后端技术栈
{"runtime": "Node.js 14+","framework": "Express.js","fileUpload": "Multer","cors": "CORS中间件","storage": "本地文件系统"
}
前端技术栈
{"3dEngine": "Three.js r128+","graphics": "WebGL 2.0","ui": "原生HTML5/CSS3","interactions": "OrbitControls","loaders": "GLTFLoader, OBJLoader, STLLoader, PLYLoader"
}

2.3 项目目录结构

3DWeb/
├── server.js              # Express服务器主文件
├── package.json           # 项目配置和依赖管理
├── public/                # 前端静态资源目录
│   ├── index.html         # 主页面结构
│   ├── styles.css         # 样式文件
│   └── app.js             # 3D查看器核心逻辑
├── uploads/               # 用户上传文件存储
├── demo_models/           # 演示模型文件
└── README.md              # 项目说明文档

3. 3D渲染原理详解

3.1 WebGL渲染管线

3D模型在网页中的渲染基于WebGL技术,其渲染管线如下:

3D模型数据
顶点处理器
图元装配
光栅化
片段处理器
帧缓冲区
屏幕显示

3.2 Three.js渲染流程

// 1. 创建场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });// 2. 加载3D模型
const loader = new THREE.GLTFLoader();
loader.load('model.glb', (gltf) => {scene.add(gltf.scene);
});// 3. 设置光照
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(ambientLight, directionalLight);// 4. 渲染循环
function animate() {requestAnimationFrame(animate);renderer.render(scene, camera);
}

3.3 坐标系统与变换

Three.js使用右手坐标系,其中:

  • X轴:向右为正
  • Y轴:向上为正
  • Z轴:向屏幕外为正

在这里插入图片描述

4. 后端服务器实现

4.1 Express服务器搭建

const express = require('express');
const multer = require('multer');
const path = require('path');
const cors = require('cors');const app = express();
const PORT = process.env.PORT || 3000;// 中间件配置
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

4.2 文件上传配置

// Multer存储配置
const storage = multer.diskStorage({destination: function (req, file, cb) {cb(null, 'uploads/');},filename: function (req, file, cb) {const timestamp = Date.now();const originalName = file.originalname;cb(null, `${timestamp}_${originalName}`);}
});// 文件类型过滤
const fileFilter = (req, file, cb) => {const allowedExtensions = ['.gltf', '.glb', '.obj', '.fbx', '.stl', '.ply'];const fileExtension = path.extname(file.originalname).toLowerCase();if (allowedExtensions.includes(fileExtension)) {cb(null, true);} else {cb(new Error('不支持的文件格式'), false);}
};const upload = multer({storage: storage,fileFilter: fileFilter,limits: { fileSize: 50 * 1024 * 1024 } // 50MB限制
});

4.3 RESTful API设计

// 文件上传接口
app.post('/api/upload', upload.single('model'), (req, res) => {try {if (!req.file) {return res.status(400).json({ error: '没有选择文件' });}const fileInfo = {originalName: req.file.originalname,filename: req.file.filename,size: req.file.size,path: `/uploads/${req.file.filename}`,uploadTime: new Date().toISOString()};res.json({success: true,message: '文件上传成功',file: fileInfo});} catch (error) {res.status(500).json({ error: '文件上传失败: ' + error.message });}
});// 获取模型列表接口
app.get('/api/models', (req, res) => {try {const files = fs.readdirSync('uploads/');const models = files.map(filename => {const filePath = path.join('uploads/', filename);const stats = fs.statSync(filePath);return {filename: filename,originalName: filename.split('_').slice(1).join('_'),size: stats.size,path: `/uploads/${filename}`,uploadTime: stats.birthtime.toISOString()};});res.json({ success: true, models: models });} catch (error) {res.status(500).json({ error: '获取模型列表失败' });}
});// 删除模型接口
app.delete('/api/models/:filename', (req, res) => {try {const filename = req.params.filename;const filePath = path.join('uploads/', filename);if (fs.existsSync(filePath)) {fs.unlinkSync(filePath);res.json({ success: true, message: '文件删除成功' });} else {res.status(404).json({ error: '文件不存在' });}} catch (error) {res.status(500).json({ error: '文件删除失败' });}
});

5. 前端3D查看器核心

5.1 ModelViewer类设计

class ModelViewer {constructor() {this.scene = null;           // Three.js场景this.camera = null;          // 相机对象this.renderer = null;        // 渲染器this.controls = null;        // 控制器this.currentModel = null;    // 当前加载的模型this.lights = {};           // 光照系统this.isWireframe = false;   // 线框模式标志this.isAutoRotate = false;  // 自动旋转标志this.init();this.setupEventListeners();this.animate();}// 初始化3D场景init() {this.initScene();this.initCamera();this.initRenderer();this.initControls();this.setupLighting();}
}

5.2 场景初始化详解

initScene() {// 创建场景this.scene = new THREE.Scene();this.scene.background = new THREE.Color(0x2c2c2c);// 添加网格辅助线const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x444444);gridHelper.material.opacity = 0.3;gridHelper.material.transparent = true;this.scene.add(gridHelper);
}initCamera() {const container = document.getElementById('canvasContainer');this.camera = new THREE.PerspectiveCamera(75,  // 视野角度container.clientWidth / container.clientHeight,  // 宽高比0.1,  // 近裁剪面1000  // 远裁剪面);this.camera.position.set(5, 5, 5);
}initRenderer() {const canvas = document.getElementById('canvas3d');this.renderer = new THREE.WebGLRenderer({ canvas: canvas,antialias: true,      // 抗锯齿alpha: true           // 透明背景});// 渲染器配置this.renderer.setPixelRatio(window.devicePixelRatio);this.renderer.shadowMap.enabled = true;this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;this.renderer.outputEncoding = THREE.sRGBEncoding;this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
}

5.3 模型加载系统

async loadModel(file) {return new Promise((resolve, reject) => {const fileName = file.name.toLowerCase();const fileUrl = URL.createObjectURL(file);// 清除当前模型if (this.currentModel) {this.scene.remove(this.currentModel);}let loader;// 根据文件扩展名选择合适的加载器if (fileName.endsWith('.gltf') || fileName.endsWith('.glb')) {loader = new THREE.GLTFLoader();loader.load(fileUrl, (gltf) => {this.currentModel = gltf.scene;this.processLoadedModel(gltf.scene, file);resolve();}, this.onProgress, reject);} else if (fileName.endsWith('.obj')) {loader = new THREE.OBJLoader();loader.load(fileUrl, (object) => {this.currentModel = object;this.processLoadedModel(object, file);resolve();}, this.onProgress, reject);} else if (fileName.endsWith('.stl')) {loader = new THREE.STLLoader();loader.load(fileUrl, (geometry) => {const material = new THREE.MeshPhongMaterial({ color: 0x888888,shininess: 100});this.currentModel = new THREE.Mesh(geometry, material);this.processLoadedModel(this.currentModel, file);resolve();}, this.onProgress, reject);}});
}

5.4 模型处理与优化

processLoadedModel(model, file) {// 添加到场景this.scene.add(model);// 计算模型边界盒const box = new THREE.Box3().setFromObject(model);const center = box.getCenter(new THREE.Vector3());const size = box.getSize(new THREE.Vector3());// 居中模型model.position.sub(center);// 设置阴影和材质model.traverse((child) => {if (child.isMesh) {child.castShadow = true;child.receiveShadow = true;// 材质优化if (child.material) {child.material.needsUpdate = true;}}});// 自适应相机位置this.fitCameraToModel(size);// 更新模型信息UIthis.updateModelInfo(model, file, size);
}fitCameraToModel(size) {const maxDim = Math.max(size.x, size.y, size.z);const fov = this.camera.fov * (Math.PI / 180);let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));cameraZ *= 2; // 添加边距this.camera.position.set(cameraZ, cameraZ, cameraZ);this.camera.lookAt(0, 0, 0);this.controls.target.set(0, 0, 0);this.controls.update();
}

6. 文件上传与管理

6.1 拖拽上传实现

setupEventListeners() {const uploadArea = document.getElementById('uploadArea');// 拖拽事件处理uploadArea.addEventListener('dragover', (e) => {e.preventDefault();uploadArea.classList.add('dragover');});uploadArea.addEventListener('dragleave', (e) => {e.preventDefault();uploadArea.classList.remove('dragover');});uploadArea.addEventListener('drop', (e) => {e.preventDefault();uploadArea.classList.remove('dragover');this.handleFileSelect(e);});// 文件选择事件const fileInput = document.getElementById('fileInput');fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
}

6.2 文件处理流程

async handleFileSelect(event) {const files = event.target.files || event.dataTransfer.files;if (!files.length) return;for (const file of files) {try {this.showLoading(true);// 1. 上传到服务器await this.uploadFile(file);// 2. 加载到3D场景await this.loadModel(file);this.showNotification('模型加载成功', 'success');} catch (error) {console.error('文件处理错误:', error);this.showNotification('文件处理失败: ' + error.message, 'error');} finally {this.showLoading(false);}}// 刷新模型列表this.loadModelList();
}async uploadFile(file) {const formData = new FormData();formData.append('model', file);const response = await fetch('/api/upload', {method: 'POST',body: formData});if (!response.ok) {const error = await response.json();throw new Error(error.error || '上传失败');}return await response.json();
}

6.3 文件格式支持

格式扩展名特点适用场景
GLTF/GLB.gltf, .glb现代标准,支持动画材质游戏、AR/VR、产品展示
OBJ.obj通用性强,广泛支持静态模型、简单场景
STL.stl3D打印标准工程制造、医疗建模
PLY.ply科学可视化点云数据、扫描模型

7. 交互控制系统

7.1 OrbitControls配置

initControls() {this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);// 控制器配置this.controls.enableDamping = true;        // 启用阻尼this.controls.dampingFactor = 0.05;        // 阻尼系数this.controls.screenSpacePanning = false;  // 屏幕空间平移this.controls.minDistance = 1;             // 最小距离this.controls.maxDistance = 100;           // 最大距离this.controls.maxPolarAngle = Math.PI;     // 最大极角// 自动旋转配置this.controls.autoRotate = false;this.controls.autoRotateSpeed = 2.0;
}

7.2 交互功能实现

// 重置相机视角
resetCamera() {if (this.currentModel) {const box = new THREE.Box3().setFromObject(this.currentModel);const size = box.getSize(new THREE.Vector3());this.fitCameraToModel(size);}
}// 切换自动旋转
toggleAutoRotate() {this.isAutoRotate = !this.isAutoRotate;if (this.isAutoRotate) {this.controls.autoRotate = true;} else {this.controls.autoRotate = false;}
}// 切换线框模式
toggleWireframe() {this.isWireframe = !this.isWireframe;if (this.currentModel) {this.currentModel.traverse((child) => {if (child.isMesh && child.material) {if (Array.isArray(child.material)) {child.material.forEach(material => {material.wireframe = this.isWireframe;});} else {child.material.wireframe = this.isWireframe;}}});}
}

7.3 触控设备支持

/* 触控优化CSS */
.canvas-container {touch-action: none;user-select: none;-webkit-user-drag: none;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}/* 移动设备适配 */
@media (max-width: 768px) {.control-panel {position: fixed;bottom: 0;left: 0;right: 0;transform: translateY(calc(100% - 60px));transition: transform 0.3s ease;}.control-panel.expanded {transform: translateY(0);}
}

8. 光照与材质系统

8.1 多光源照明设计

setupLighting() {// 1. 环境光 - 提供基础照明this.lights.ambient = new THREE.AmbientLight(0x404040, 0.4);this.scene.add(this.lights.ambient);// 2. 主方向光 - 模拟太阳光this.lights.directional = new THREE.DirectionalLight(0xffffff, 1);this.lights.directional.position.set(10, 10, 5);this.lights.directional.castShadow = true;// 阴影配置this.lights.directional.shadow.mapSize.width = 2048;this.lights.directional.shadow.mapSize.height = 2048;this.lights.directional.shadow.camera.near = 0.5;this.lights.directional.shadow.camera.far = 50;this.lights.directional.shadow.camera.left = -10;this.lights.directional.shadow.camera.right = 10;this.lights.directional.shadow.camera.top = 10;this.lights.directional.shadow.camera.bottom = -10;this.scene.add(this.lights.directional);// 3. 补充光源 - 减少阴影过暗this.lights.fill = new THREE.DirectionalLight(0xffffff, 0.3);this.lights.fill.position.set(-5, 0, -5);this.scene.add(this.lights.fill);// 4. 顶部光源 - 增强立体感this.lights.top = new THREE.DirectionalLight(0xffffff, 0.2);this.lights.top.position.set(0, 10, 0);this.scene.add(this.lights.top);
}

8.2 动态光照控制

// 更新环境光强度
updateAmbientLight(value) {this.lights.ambient.intensity = parseFloat(value);document.getElementById('ambientValue').textContent = parseFloat(value).toFixed(1);
}// 更新方向光强度
updateDirectionalLight(value) {this.lights.directional.intensity = parseFloat(value);document.getElementById('directionalValue').textContent = parseFloat(value).toFixed(1);
}// 改变背景颜色
changeBackground(color) {this.scene.background = new THREE.Color(color);
}

8.3 材质系统优化

// 为不同格式应用合适的材质
applyMaterial(mesh, format) {let material;switch(format) {case 'stl':case 'ply':// 为STL和PLY格式应用Phong材质material = new THREE.MeshPhongMaterial({color: 0x888888,shininess: 100,specular: 0x222222});break;case 'obj':// OBJ格式使用Lambert材质material = new THREE.MeshLambertMaterial({color: 0x888888});break;default:// GLTF等格式保持原有材质return;}if (mesh.material) {mesh.material.dispose(); // 释放旧材质}mesh.material = material;
}

9. 性能优化策略

9.1 渲染性能优化

// 渲染循环优化
animate() {requestAnimationFrame(() => this.animate());// 只在需要时更新控制器if (this.controls.enabled) {this.controls.update();}// 自动旋转优化if (this.isAutoRotate && this.currentModel) {this.currentModel.rotation.y += 0.01;}// 渲染场景this.renderer.render(this.scene, this.camera);// 性能监控this.updateFPS();
}// FPS计数器
updateFPS() {this.frameCount++;const now = performance.now();if (now >= this.lastTime + 1000) {const fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));document.getElementById('fpsCounter').textContent = fps;this.frameCount = 0;this.lastTime = now;}
}

9.2 内存管理

// 清理资源
dispose() {// 清理几何体if (this.currentModel) {this.currentModel.traverse((child) => {if (child.geometry) {child.geometry.dispose();}if (child.material) {if (Array.isArray(child.material)) {child.material.forEach(material => material.dispose());} else {child.material.dispose();}}});}// 清理渲染器this.renderer.dispose();// 清理控制器this.controls.dispose();
}// 窗口大小调整优化
onWindowResize() {const container = document.getElementById('canvasContainer');const width = container.clientWidth;const height = container.clientHeight;// 避免频繁调整if (Math.abs(this.lastWidth - width) < 10 && Math.abs(this.lastHeight - height) < 10) {return;}this.camera.aspect = width / height;this.camera.updateProjectionMatrix();this.renderer.setSize(width, height);this.lastWidth = width;this.lastHeight = height;
}

9.3 文件加载优化

// 分块加载大文件
loadLargeModel(file) {const fileSize = file.size;const chunkSize = 1024 * 1024; // 1MB chunksif (fileSize > chunkSize * 10) { // 大于10MBreturn this.loadModelInChunks(file, chunkSize);} else {return this.loadModel(file);}
}// 预加载常用资源
preloadResources() {// 预加载纹理const textureLoader = new THREE.TextureLoader();const commonTextures = ['grid.png', 'env.hdr'];commonTextures.forEach(texture => {textureLoader.load(`/assets/${texture}`);});
}

10. 部署与扩展

10.1 生产环境部署

// 生产环境配置
const express = require('express');
const compression = require('compression');
const helmet = require('helmet');const app = express();// 安全中间件
app.use(helmet());// Gzip压缩
app.use(compression());// 静态资源缓存
app.use('/static', express.static('public', {maxAge: '1d',etag: false
}));// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`🚀 服务器启动在端口 ${PORT}`);
});

10.2 Docker部署

# Dockerfile
FROM node:16-alpineWORKDIR /app# 复制依赖文件
COPY package*.json ./# 安装依赖
RUN npm ci --only=production# 复制源代码
COPY . .# 创建上传目录
RUN mkdir -p uploads# 暴露端口
EXPOSE 3000# 启动应用
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:3d-viewer:build: .ports:- "3000:3000"volumes:- ./uploads:/app/uploadsenvironment:- NODE_ENV=productionrestart: unless-stopped

10.3 功能扩展方向

A. 添加新的3D格式支持
// 扩展FBX格式支持
if (fileName.endsWith('.fbx')) {// 需要引入FBXLoaderloader = new THREE.FBXLoader();loader.load(fileUrl, (object) => {// FBX特殊处理object.scale.setScalar(0.01); // FBX通常需要缩放this.currentModel = object;this.processLoadedModel(object, file);resolve();}, this.onProgress, reject);
}
B. 添加动画系统
// 动画控制器
class AnimationController {constructor(model) {this.mixer = new THREE.AnimationMixer(model);this.actions = [];this.currentAction = null;}loadAnimations(animations) {animations.forEach((clip, index) => {const action = this.mixer.clipAction(clip);this.actions.push(action);});}playAnimation(index) {if (this.currentAction) {this.currentAction.stop();}this.currentAction = this.actions[index];if (this.currentAction) {this.currentAction.play();}}update(deltaTime) {this.mixer.update(deltaTime);}
}
C. VR/AR支持
// WebXR支持
initVR() {if ('xr' in navigator) {navigator.xr.isSessionSupported('immersive-vr').then((supported) => {if (supported) {this.renderer.xr.enabled = true;const vrButton = document.createElement('button');vrButton.textContent = 'Enter VR';vrButton.onclick = () => {navigator.xr.requestSession('immersive-vr').then((session) => {this.renderer.xr.setSession(session);});};document.body.appendChild(vrButton);}});}
}

🎯 总结

本文详细介绍了基于Node.js和Three.js构建3D模型网页预览器的完整实现过程,涵盖了从后端服务器搭建到前端3D渲染的各个技术环节。

核心技术要点

  1. WebGL渲染: 基于硬件加速的3D图形渲染
  2. 模块化设计: 清晰的代码结构和职责分离
  3. 多格式支持: 灵活的加载器系统
  4. 性能优化: 内存管理和渲染优化策略
  5. 用户体验: 现代化UI和交互设计

应用场景

  • 🏢 产品展示: 电商平台3D产品预览
  • 🎮 游戏开发: 模型资源预览和调试
  • 🏗️ 建筑可视化: BIM模型在线查看
  • 🔬 科学研究: 3D数据可视化分析
  • 📚 教育培训: 3D教学资源展示

这个项目展示了现代Web技术在3D可视化领域的强大能力,为开发者提供了一个完整的技术参考和实现方案。通过合理的架构设计和优化策略,我们可以在浏览器中实现接近桌面应用的3D渲染效果。


文章转载自:

http://tYJlTstu.ckntb.cn
http://25k7x22w.ckntb.cn
http://VX3HO0Xj.ckntb.cn
http://F6rj2Bf1.ckntb.cn
http://MeLYTbyB.ckntb.cn
http://aE9AovJT.ckntb.cn
http://auoa5tVn.ckntb.cn
http://f3V5pwea.ckntb.cn
http://aCINYb1q.ckntb.cn
http://JHWQQodo.ckntb.cn
http://xEaywpFE.ckntb.cn
http://YlIsklpw.ckntb.cn
http://jpLp1oId.ckntb.cn
http://hEok6ElL.ckntb.cn
http://KjtOUAKu.ckntb.cn
http://uLmQJ3rf.ckntb.cn
http://iBb1bfbh.ckntb.cn
http://S0ihScDR.ckntb.cn
http://4jtUmd7P.ckntb.cn
http://L44jRNzG.ckntb.cn
http://ieXyjdtz.ckntb.cn
http://GZIzCwVq.ckntb.cn
http://SaqRCSMr.ckntb.cn
http://piG4884w.ckntb.cn
http://YJ4pHNZy.ckntb.cn
http://mMw5oApT.ckntb.cn
http://ujjRsVCb.ckntb.cn
http://b6T4fZcY.ckntb.cn
http://zuNfx8ZQ.ckntb.cn
http://MX4R6qXU.ckntb.cn
http://www.dtcms.com/a/370003.html

相关文章:

  • Scikit-learn Python机器学习 - 特征降维 压缩数据 - 特征提取 - 主成分分析 (PCA)
  • CSP-J/S IS COMING
  • GraphQL API 性能优化实战:在线编程作业平台指南
  • 【基础-判断】Background状态在UIAbility实例销毁时触发,可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
  • PageHelper的使用及底层原理
  • 探寻卓越:高级RAG技术、架构与实践深度解析
  • 【51单片机】【protues仿真】基于51单片机PM2.5空气质量检测系统
  • AI工具深度测评与选型指南 - 图像生成与编辑类
  • RabbitMQ工作模式(下)
  • Custom SRP - Complex Maps
  • tp报错解决
  • MySQL MHA 高可用集群搭建
  • 《AI大模型应知应会100篇》第68篇:移动应用中的大模型功能开发 —— 用 React Native 打造你的语音笔记摘要 App
  • Mac Intel 芯片 Docker 一键部署 Neo4j 最新版本教程
  • 正态分布 - 正态分布的经验法则(68-95-99.7 法则)
  • 【操作系统-Day 25】死锁 (Deadlock):揭秘多线程编程的“终极杀手”
  • (二).net面试(static)
  • 为什么服务器有主备BMC?
  • Dotnet 项目手动部署到AWS 和Github action CICD 流程总结
  • (2)桌面云、并行计算、分布式、网格计算
  • Java中的死锁
  • SQL 进阶指南:视图的创建与使用(视图语法 / 作用 / 权限控制)
  • SQL 实战指南:电商订单数据分析(订单 / 用户 / 商品表关联 + 统计需求)
  • 附050.Kubernetes Karmada Helm部署联邦及使用
  • 【PCIe EP 设备入门学习专栏 -- 8 PCIe EP 架构详细介绍】
  • STM32HAL 快速入门(十九):UART 编程(二)—— 中断方式实现收发及局限分析
  • 【星闪】Hi2821 | PWM脉宽调制模块 + 呼吸灯例程
  • 具身智能模拟器:解决机器人实机训练场景局限与成本问题的创新方案
  • 【嵌入式】【科普】AUTOSAR学习路径
  • 大麦APP抢票-核心