构建网页版IPFS去中心化网盘

前言:我把它命名为无限网盘 Unlimited network disks(ULND),可以实现简单的去中心化存储,其实实现起来并不难,还是依靠强大的IPFS,跟着我一步一步做就可以了。

第一步:准备开发环境

1.安装Node.js:

访问 Node.js官网

下载并安装LTS版本(如18.x)

安装完成后,打开终端/命令行,输入以下命令检查是否成功:

node -v
npm -v

【可选】如果在终端中运行失败,是Windows PowerShell 的执行策略(PowerShell Execution Policy)限制了脚本的运行,Windows系统默认限制PowerShell脚本执行以防止恶意脚本运行。npm在Windows上实际是通过npm.ps1(PowerShell脚本)运行的,所以受此限制。直接在命令提示符(CMD)中运行,或者以管理员身份运行PowerShell并更改执行策略:

查看当前执行策略:

Get-ExecutionPolicy

可能会显示Restricted(这是默认设置,禁止所有脚本运行)

更改执行策略:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

输入Y确认更改

验证更改:

Get-ExecutionPolicy

现在应该显示RemoteSigned

完成npm操作后,可以改回严格模式:

Set-ExecutionPolicy Restricted -Scope CurrentUser

完成上述任一方法后,再次尝试:

npm -v

现在应该能正常显示npm版本号了。

2.安装代码编辑器:

推荐使用 VS Code(免费)或者Notepad++(免费)

第二步:创建React项目

1.打开终端/命令行,执行:

npx create-react-app ipfs-drive

你在哪里打开终端执行

cd ipfs-drive

就会装在哪里,或者使用完整路径(如装在D盘:

npx create-react-app D:\ipfs-drive

2.安装所需依赖:

npm install ipfs-http-client @mui/material @mui/icons-material @emotion/react @emotion/styled

各包的作用

  1. ipfs-http-client: 连接IPFS网络的客户端库

  2. @mui/material: Material-UI核心组件

  3. @mui/icons-material: Material-UI官方图标

  4. @emotion/react 和 @emotion/styled: MUI v5的样式依赖

【可选】如果安装不了可以尝试使用淘宝镜像(中国大陆用户):

npm config set registry https://registry.npmmirror.comnpm install ipfs-http-client @mui/material @mui/icons-material @emotion/react @emotion/styled

第三步:创建IPFS连接文件

1.在src文件夹中新建ipfs.js文件

2.自建 IPFS 节点(持久化存储)

安装 IPFS 桌面应用发布 ·ipfs/ipfs-桌面
启动后修改 src/ipfs.js:

// 确保从 'ipfs-http-client' 导入 create 方法
import { create } from 'ipfs-http-client';// 自建节点配置(确保你的本地IPFS守护进程正在运行)
const ipfs = create({host: 'localhost',port: 5001,protocol: 'http'
});// 必须导出 ipfs 实例
export default ipfs;

特点:

文件保存在本地

需要保持节点在线才能访问

通过修改 config 文件可连接其他节点

第四步:修改主应用文件

1.打开src/App.js,清空原有内容

2.复制以下完整代码:

import React, { useState } from 'react';import {Button,Container,LinearProgress,List,ListItem,ListItemText,Typography,Box} from '@mui/material';import { CloudUpload, Download, ContentCopy } from '@mui/icons-material';import ipfs from './ipfs';function App() {const [files, setFiles] = useState([]);const [progress, setProgress] = useState(0);const handleFileUpload = async (event) => {const file = event.target.files[0];if (!file) return;try {const added = await ipfs.add(file, {progress: (prog) => setProgress((prog / file.size) * 100)});setFiles([...files, {cid: added.cid.toString(),name: file.name,size: (file.size / 1024).toFixed(2) + ' KB'}]);setProgress(0);alert('文件上传成功!');} catch (error) {console.error('上传出错:', error);alert('上传失败: ' + error.message);}};const downloadFile = async (cid, name) => {try {const chunks = [];for await (const chunk of ipfs.cat(cid)) {chunks.push(chunk);}const content = new Blob(chunks);const url = URL.createObjectURL(content);const link = document.createElement('a');link.href = url;link.download = name;link.click();} catch (error) {console.error('下载出错:', error);alert('下载失败: ' + error.message);}};return (<Container maxWidth="md" sx={{ mt: 4 }}><Typography variant="h3" gutterBottom>IPFS网盘</Typography><Box sx={{ mb: 3 }}><inputaccept="*"style={{ display: 'none' }}id="file-upload"type="file"onChange={handleFileUpload}/><label htmlFor="file-upload"><Buttonvariant="contained"color="primary"component="span"startIcon={<CloudUpload />}>上传文件</Button></label></Box>{progress > 0 && (<Box sx={{ width: '100%', mb: 2 }}><LinearProgress variant="determinate" value={progress} /><Typography variant="body2" align="center">上传中: {progress.toFixed(1)}%</Typography></Box>)}<List>{files.map((file, index) => (<ListItem key={index} divider><ListItemTextprimary={file.name}secondary={`CID: ${file.cid} | 大小: ${file.size}`}/><Buttonvariant="outlined"startIcon={<Download />}onClick={() => downloadFile(file.cid, file.name)}sx={{ mr: 1 }}>下载</Button><Buttonvariant="outlined"startIcon={<ContentCopy />}onClick={() => {navigator.clipboard.writeText(file.cid);alert('CID已复制!');}}>复制CID</Button></ListItem>))}</List>{files.length === 0 && (<Typography variant="body1" color="text.secondary" align="center">暂无文件,请上传您的第一个文件</Typography>)}</Container>);}export default App;

第五步:运行开发服务器

1.在终端执行:

npm start

2.浏览器会自动打开 http://localhost:3000

3.你应该能看到一个简洁的文件上传界面

第六步:测试功能

1.上传文件:

点击"上传文件"按钮

选择任意文件

观察上传进度条

上传成功后文件会显示在列表中

2.下载文件:

在文件列表中点击"下载"按钮

检查下载的文件是否完整

3.复制CID:

点击"复制CID"按钮

粘贴到文本编辑器验证是否复制成功

【常见错误】由于 CORS (跨域资源共享) 限制导致的,你的 React 应用运行在 http://localhost:3000,而 IPFS API 运行在 http://127.0.0.1:5001,浏览器出于安全考虑阻止了跨域请求。以下是完整的解决方案:

方法一:配置 IPFS 允许跨域

步骤:

操作步骤:
  1. 关闭 IPFS 桌面应用(如果正在运行)

  2. 修改 IPFS 配置

  3. 打开终端(Windows 用 CMD/PowerShell,Mac/Linux 用 Terminal)

  4. 运行以下命令

  5. # 允许所有来源(开发环境用)
    ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]'# 允许所有方法
    ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST", "GET"]'# 允许自定义头
    ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers '["Authorization"]'
  6. 重新启动 IPFS 桌面应用
    (或通过命令行 ipfs daemon 启动)

  7. 方法 2:直接编辑配置文件

    配置文件路径:
  8. Windows:
    C:\Users\<你的用户名>\.ipfs\config

  9. Mac/Linux:
    ~/.ipfs/config

  10. 用文本编辑器(如 Notepad++、VS Code)打开配置文件

  11. 找到或添加以下字段:

    "API": {"HTTPHeaders": {"Access-Control-Allow-Origin": ["*"],"Access-Control-Allow-Methods": ["PUT", "POST", "GET"],"Access-Control-Allow-Headers": ["Authorization"]}
    }
  12. 保存文件后重启 IPFS 守护进程

第七步:部署到网络

运行以下命令:

npm run buildipfs add -r build

记下最后输出的目录CID(如Qm...)

通过任意IPFS网关访问,如:

https://ipfs.io/ipfs/YOUR_CID_HERE

一个简单的去中心化网盘就做好啦,接下来就是完善了,主要修改src/APP.js 文件
import React, { useState, useEffect } from 'react';
import {Button, Container, LinearProgress, List, ListItem, ListItemText,Typography, Box, Chip, Dialog, DialogContent, DialogActions, Snackbar, Alert
} from '@mui/material';
import {CloudUpload, Download, ContentCopy, CreateNewFolder,Lock, LockOpen, Image as ImageIcon, Folder, Refresh
} from '@mui/icons-material';
import ipfs from './ipfs';// 持久化存储键名
const STORAGE_KEY = 'ipfs_drive_data_v2';function App() {const [files, setFiles] = useState([]);const [folders, setFolders] = useState([]);const [progress, setProgress] = useState(0);const [currentPath, setCurrentPath] = useState('');const [previewImage, setPreviewImage] = useState(null);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);const [initialized, setInitialized] = useState(false);// 初始化加载数据useEffect(() => {const loadPersistedData = async () => {try {// 1. 从本地存储加载基础信息const savedData = localStorage.getItem(STORAGE_KEY);if (savedData) {const { files: savedFiles, folders: savedFolders, path } = JSON.parse(savedData);setFiles(savedFiles || []);setFolders(savedFolders || []);setCurrentPath(path || '');}// 2. 从IPFS加载实际数据await refreshData();setInitialized(true);} catch (err) {setError('初始化失败: ' + err.message);}};loadPersistedData();}, []);// 数据持久化useEffect(() => {if (initialized) {localStorage.setItem(STORAGE_KEY, JSON.stringify({files: files.filter(f => !f.isDirectory),folders,path: currentPath}));}}, [files, folders, currentPath, initialized]);// 加密函数const encryptData = async (data, password) => {const encoder = new TextEncoder();const keyMaterial = await window.crypto.subtle.importKey('raw',encoder.encode(password),{ name: 'PBKDF2' },false,['deriveBits']);const salt = window.crypto.getRandomValues(new Uint8Array(16));const keyBits = await window.crypto.subtle.deriveBits({name: 'PBKDF2',salt,iterations: 100000,hash: 'SHA-256'},keyMaterial,256);const iv = window.crypto.getRandomValues(new Uint8Array(12));const cryptoKey = await window.crypto.subtle.importKey('raw',keyBits,{ name: 'AES-GCM' },false,['encrypt']);const encrypted = await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv },cryptoKey,data);return { encrypted, iv, salt };};// 解密函数const decryptData = async (encryptedData, password, iv, salt) => {try {const encoder = new TextEncoder();const keyMaterial = await window.crypto.subtle.importKey('raw',encoder.encode(password),{ name: 'PBKDF2' },false,['deriveBits']);const keyBits = await window.crypto.subtle.deriveBits({name: 'PBKDF2',salt,iterations: 100000,hash: 'SHA-256'},keyMaterial,256);const cryptoKey = await window.crypto.subtle.importKey('raw',keyBits,{ name: 'AES-GCM' },false,['decrypt']);return await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv },cryptoKey,encryptedData);} catch (err) {throw new Error('解密失败: 密码错误或数据损坏');}};// 文件上传函数const handleFileUpload = async (event) => {const file = event.target.files[0];if (!file) return;setLoading(true);try {const shouldEncrypt = window.confirm('是否需要加密此文件?');let fileData = await file.arrayBuffer();let encryptionInfo = null;if (shouldEncrypt) {const password = prompt('请输入加密密码');if (!password) return;encryptionInfo = await encryptData(fileData, password);fileData = encryptionInfo.encrypted;}// 上传文件内容const added = await ipfs.add({ content: fileData },{ progress: (prog) => setProgress((prog / fileData.byteLength) * 100),pin: true});// 如果是文件夹内上传,更新目录结构const uploadPath = currentPath ? `${currentPath}/${file.name}` : file.name;if (currentPath) {await ipfs.files.cp(`/ipfs/${added.cid}`, `/${uploadPath}`);}// 存储元数据const metadata = {originalName: file.name,mimeType: file.type,size: file.size,encrypted: shouldEncrypt,timestamp: new Date().toISOString()};const metadataCid = (await ipfs.add(JSON.stringify(metadata))).cid.toString();await ipfs.pin.add(metadataCid);const newFile = {cid: added.cid.toString(),name: file.name,size: (file.size / 1024).toFixed(2) + ' KB',encrypted: !!encryptionInfo,path: uploadPath,isDirectory: false,isImage: file.type.startsWith('image/'),encryptionInfo,metadataCid};setFiles(prev => [...prev, newFile]);setError(null);alert(`文件${encryptionInfo ? '(加密)' : ''}上传成功!`);} catch (err) {console.error('上传出错:', err);setError('上传失败: ' + err.message);} finally {setLoading(false);setProgress(0);}};// 处理文件下载const handleDownload = async (file) => {try {setLoading(true);let blob;if (file.encrypted) {// 加密文件处理const password = prompt('请输入解密密码');if (!password) return;const chunks = [];for await (const chunk of ipfs.cat(file.cid)) {chunks.push(chunk);}const encryptedData = new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...new Uint8Array(chunk)], []));const decrypted = await decryptData(encryptedData,password,file.encryptionInfo.iv,file.encryptionInfo.salt);blob = new Blob([decrypted], { type: 'application/octet-stream' });} else {// 普通文件处理const chunks = [];for await (const chunk of ipfs.cat(file.cid)) {chunks.push(chunk);}blob = new Blob(chunks, { type: 'application/octet-stream' });}// 创建下载链接const url = URL.createObjectURL(blob);const link = document.createElement('a');link.href = url;link.download = file.name;document.body.appendChild(link);link.click();setTimeout(() => {document.body.removeChild(link);URL.revokeObjectURL(url);}, 100);} catch (err) {console.error('下载出错:', err);setError(err.message.includes('解密失败') ? err.message : '下载失败: ' + err.message);} finally {setLoading(false);}};// 处理图片预览const handlePreview = async (file) => {try {setLoading(true);const chunks = [];for await (const chunk of ipfs.cat(file.cid)) {chunks.push(chunk);}let fileData = new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...new Uint8Array(chunk)], []));if (file.encrypted) {const password = prompt('请输入解密密码');if (!password) return;fileData = new Uint8Array(await decryptData(fileData,password,file.encryptionInfo.iv,file.encryptionInfo.salt));}const blob = new Blob([fileData], { type: 'image/*' });const reader = new FileReader();reader.onload = () => {setPreviewImage({url: reader.result,name: file.name,blob});};reader.readAsDataURL(blob);} catch (err) {console.error('预览出错:', err);setError(err.message.includes('解密失败') ? err.message : '预览失败: ' + err.message);} finally {setLoading(false);}};// 创建文件夹const createFolder = async () => {const folderName = prompt('请输入文件夹名称');if (!folderName) return;try {const path = currentPath ? `${currentPath}/${folderName}` : folderName;await ipfs.files.mkdir(`/${path}`);const newFolder = {cid: (await ipfs.files.stat(`/${path}`)).cid.toString(),name: folderName,path,isDirectory: true};setFolders(prev => [...prev, newFolder]);setError(null);} catch (err) {console.error('创建文件夹失败:', err);setError('创建文件夹失败: ' + err.message);}};// 加载目录内容const loadDirectory = async (folder) => {try {setLoading(true);const contents = [];const path = folder.path || folder.cid;for await (const entry of ipfs.files.ls(`/${path}`)) {// 尝试加载元数据let originalName = entry.name;let isImage = false;try {const metadata = await loadMetadata(entry.cid.toString());if (metadata) {originalName = metadata.originalName || originalName;isImage = metadata.mimeType?.startsWith('image/') || false;}} catch {}contents.push({cid: entry.cid.toString(),name: originalName,size: (entry.size / 1024).toFixed(2) + ' KB',isDirectory: entry.type === 'directory',path: `${path}/${entry.name}`,isImage});}setCurrentPath(path);setFiles(contents);setError(null);} catch (err) {console.error('目录加载失败:', err);setError('加载目录失败: ' + err.message);} finally {setLoading(false);}};// 加载元数据const loadMetadata = async (cid) => {try {const chunks = [];for await (const chunk of ipfs.cat(cid)) {chunks.push(chunk);}return JSON.parse(new TextDecoder().decode(new Uint8Array(chunks)));} catch {return null;}};// 刷新数据const refreshData = async () => {try {setLoading(true);const updatedFiles = [];const updatedFolders = [];// 1. 加载所有固定文件for await (const { cid } of ipfs.pin.ls()) {try {// 2. 获取文件状态const stats = await ipfs.files.stat(`/ipfs/${cid}`);// 3. 尝试加载元数据const metadata = await loadMetadata(cid.toString());if (stats.type === 'file') {updatedFiles.push({cid: cid.toString(),name: metadata?.originalName || cid.toString(),size: (stats.size / 1024).toFixed(2) + ' KB',isDirectory: false,isImage: metadata?.mimeType?.startsWith('image/') || false,encrypted: metadata?.encrypted || false});} else if (stats.type === 'directory') {updatedFolders.push({cid: cid.toString(),name: metadata?.originalName || cid.toString(),isDirectory: true});}} catch (err) {console.warn(`无法处理 ${cid}:`, err);}}setFiles(updatedFiles);setFolders(updatedFolders);setError(null);} catch (err) {console.error('刷新数据失败:', err);setError('刷新数据失败: ' + err.message);} finally {setLoading(false);}};return (<Container maxWidth="md" sx={{ mt: 4 }}><Typography variant="h3" gutterBottom>IPFS网盘 {currentPath && `- ${currentPath.split('/').pop()}`}</Typography>{/* 操作栏 */}<Box sx={{ mb: 3, display: 'flex', gap: 2, flexWrap: 'wrap' }}><inputaccept="*"style={{ display: 'none' }}id="file-upload"type="file"onChange={handleFileUpload}disabled={loading}/><label htmlFor="file-upload"><Button variant="contained" startIcon={<CloudUpload />} component="span" disabled={loading}>上传文件</Button></label><Button onClick={createFolder} startIcon={<CreateNewFolder />} disabled={loading}>新建文件夹</Button><Button onClick={refreshData} startIcon={<Refresh />} disabled={loading}>刷新数据</Button></Box>{/* 显示当前路径 */}{currentPath && (<Box sx={{ mb: 2 }}><Button onClick={() => setCurrentPath('')} size="small" startIcon={<Folder />}>返回根目录</Button><Typography variant="body2" sx={{ mt: 1 }}>当前路径: <code>{currentPath}</code></Typography></Box>)}{/* 文件夹列表 */}{folders.length > 0 && !currentPath && (<Box sx={{ mb: 3 }}><Typography variant="h6">文件夹</Typography><Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>{folders.map((folder, i) => (<Chipkey={i}icon={<Folder />}label={folder.name}onClick={() => loadDirectory(folder)}sx={{ cursor: 'pointer' }}color="primary"/>))}</Box></Box>)}{/* 文件列表 */}<List>{files.map((file, i) => (<ListItem key={i} divider><ListItemTextprimary={<Box sx={{ display: 'flex', alignItems: 'center' }}>{file.isDirectory ? <Folder sx={{ mr: 1 }} /> : null}{file.name}{file.encrypted && <Lock color="warning" sx={{ ml: 1, fontSize: '1rem' }} />}</Box>}secondary={<><span>CID: {file.cid}</span><br /><span>大小: {file.size}</span></>}/><Box sx={{ display: 'flex', gap: 1 }}>{file.isDirectory ? (<Buttonsize="small"variant="outlined"startIcon={<Folder />}onClick={() => loadDirectory(file)}>打开</Button>) : (<><Buttonsize="small"variant="outlined"startIcon={file.encrypted ? <LockOpen /> : <Download />}onClick={() => handleDownload(file)}disabled={loading}>{file.encrypted ? '解密下载' : '下载'}</Button>{file.isImage && (<Buttonsize="small"variant="outlined"startIcon={<ImageIcon />}onClick={() => handlePreview(file)}disabled={loading}>预览</Button>)}</>)}</Box></ListItem>))}</List>{/* 空状态提示 */}{files.length === 0 && (<Typography color="text.secondary" align="center" sx={{ py: 4 }}>{currentPath ? '此文件夹为空' : '暂无文件,请上传文件或创建文件夹'}</Typography>)}{/* 图片预览对话框 */}<Dialog open={!!previewImage} onClose={() => setPreviewImage(null)} maxWidth="md" fullWidth><DialogContent><imgsrc={previewImage?.url}alt="预览"style={{ maxWidth: '100%', maxHeight: '70vh',display: 'block',margin: '0 auto'}}/></DialogContent><DialogActions><Button onClick={() => setPreviewImage(null)}>关闭</Button><Button onClick={() => {if (previewImage?.blob) {const url = URL.createObjectURL(previewImage.blob);const link = document.createElement('a');link.href = url;link.download = previewImage.name;document.body.appendChild(link);link.click();setTimeout(() => {document.body.removeChild(link);URL.revokeObjectURL(url);}, 100);}}}color="primary"startIcon={<Download />}>下载图片</Button></DialogActions></Dialog>{/* 全局加载状态 */}{loading && (<Box sx={{position: 'fixed',top: 0, left: 0, right: 0, bottom: 0,bgcolor: 'rgba(0,0,0,0.5)',display: 'flex',justifyContent: 'center',alignItems: 'center',zIndex: 9999}}><Box sx={{bgcolor: 'background.paper',p: 4,borderRadius: 2,textAlign: 'center'}}><Typography variant="h6" gutterBottom>处理中,请稍候...</Typography><LinearProgress /></Box></Box>)}{/* 错误提示 */}<Snackbaropen={!!error}autoHideDuration={6000}onClose={() => setError(null)}anchorOrigin={{ vertical: 'top', horizontal: 'center' }}><Alert severity="error" onClose={() => setError(null)}>{error}</Alert></Snackbar></Container>);
}export default App;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国标GB28181视频平台EasyGBS在物业视频安防管理服务中的应用方案​

一、方案背景​ 在现代物业服务中&#xff0c;高效的安全管理与便捷的服务运营至关重要。随着科技的不断发展&#xff0c;物业行业对智能化、集成化管理系统的需求日益增长。EasyGBS作为一款基于国标GB28181协议的视频监控平台&#xff0c;具备强大的视频管理与集成能力&#…

[Unity]设置自动打包脚本

背景 我们经常会使用自动打包功能 文件名称: AutoBuild.csusing System.IO; using System.Linq; using UnityEditor; using UnityEngine;public class AutoBuilder {[MenuItem("Build/GetCurrentBuildTarget")]public static void GetCurrentBuildTarget(){Debug.L…

正点原子STM32H743单片机实现ADC多通道检测

目标 使用STM32CubeMX工具&#xff0c;配置ADC相关参数&#xff0c;实现在STM32H743单片机上获取ADC多通道电压值。共14个ADC引脚&#xff0c;ADC2有5个&#xff0c;ADC3有9个&#xff0c;全部设置单通道 ADC引脚 PF3PF4PF5PF10PC0PC2PC3PH2PH3PA3PB0PB1PA4PA5PA6 STM32cube…

深度学习基础(四)——计算量(FLOPs)、参数量(Params)、计算速度(FLOPS/TOPS))

一、计算量FLOPs FLOPs&#xff0c;全称为Floating Point Operations, (s为复数缩写&#xff09;&#xff0c;浮点运算数&#xff0c;指模型完成一次前向传播所需的浮点运算次数&#xff0c;可以理解为计算量&#xff08;模型的时间复杂度&#xff09;&#xff0c;用来衡量算法…

电子秤检测管理系统开发实战:从数据采集到可视化大屏

简介 电子秤作为现代工业生产和商业流通中的核心计量设备,其准确性直接关系到产品质量和交易公平。针对仙贝生产企业的电子秤管理需求,我们开发了一套集电子秤检测信息录入、产品信息管理、实时称重数据采集和后台可视化大屏于一体的综合管理系统。该系统基于Django框架构建…

Cesium添加WMS,WMTS,地形图图,3D Tiles数据

在 Cesium 中&#xff0c;你可以添加 WMS、WMTS、地形图 和 3D Tiles 数据源。以下是详细的实现方法&#xff1a; 1. 添加 WMS 服务 WMS&#xff08;Web Map Service&#xff09;是一种动态地图服务&#xff0c;适用于加载栅格地图图层。 代码示例 const viewer new Cesium…

数据库基本概念:数据库的定义、特点、分类、组成、作用

一&#xff1a;数据库相关概念 1.1 定义 &#xff08;1&#xff09;数据库&#xff1a;存储数据的仓库 &#xff08;2&#xff09;数据库管理系统&#xff1a;模拟和管理数据库的大型软件 &#xff08;3&#xff09;SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义…

【项目篇之消息序列化】仿照RabbitMQ模拟实现消息队列

实现消息序列化 为什么不使用JSON来序列化直接使用二进制序列化实现序列化方法toBytes()1&#xff1a; 创建内存缓冲区​​2 &#xff1a;创建对象序列化通道​3&#xff1a;执行序列化操作​4&#xff1a;提取二进制数据&#xff0c;转换成byte[]序列化图示流程&#xff1a;序…

单片机-89C51部分:13、看门狗

飞书文档https://x509p6c8to.feishu.cn/wiki/LefkwDPU7iUUWBkfKE9cGLvonSh 一、作用 程序发生死循环的时候&#xff08;跑飞&#xff09;&#xff0c;能够自动复位。 启动看门狗计数器->计数器计数->指定时间内不对计数器赋值&#xff08;主程序跑飞&#xff0c;无法喂…

C++23/26 静态反射机制深度解析:编译时元编程的新纪元

目录 引言 一、C静态反射的核心特性 1. 编译时元数据获取 2. 元信息操作的语法革新 3. 与现有特性的深度融合 二、应用场景&#xff1a;从理论到实践 1. 序列化与反序列化 2. 领域特定语言&#xff08;DSL&#xff09;与代码生成 3. 动态插件系统 4. 调试与元编程增强…

RISCV学习(5)GD32VF103 MCU架构了解

RISCV学习&#xff08;5&#xff09;GD32VF103 MCU架构了解 1、芯片内核功能简介 GD32VF103 MCU架构&#xff0c;采用Bumblebee内核&#xff0c;芯来科技&#xff08;Nuclei System Technology&#xff09;与台湾晶心科技&#xff08;Andes Technology&#xff09;联合开发&am…

【Java学习笔记】递归

递归&#xff08;recursion&#xff09; 思想&#xff1a;把一个复杂的问题拆分成一个简单问题和子问题&#xff0c;子问题又是更小规模的复杂问题&#xff0c;循环往复 本质&#xff1a;栈的使用 递归的注意事项 &#xff08;1&#xff09;需要有递归出口&#xff0c;否者就…

渗透测试中的那些“水洞”:分析与防御

1. Nginx 版本泄露 风险分析&#xff1a; Nginx 默认会在响应头中返回 Server: nginx/x.x.x&#xff0c;攻击者可利用该信息匹配已知漏洞进行攻击。 防御措施&#xff1a; 修改 nginx.conf 配置文件&#xff0c;隐藏版本信息&#xff1a;server_tokens off;使用 WAF 进行信息…

基于C#开发的适合Windows开源文件管理器

使用DDD从零构建一个完整的系统 推荐一个功能强大且直观的开源文件管理器&#xff0c;适用于Windows平台。 01 项目简介 该项目是一个基于C#开发、开源的文件管理器&#xff0c;适用于Windows&#xff0c;界面UI美观、方便轻松浏览文件。此外&#xff0c;支持创建和提取压缩…

实习入职的总结

我是4月14号入职的&#xff0c;到现在差不多已经三个礼拜了&#xff0c;今天想总结一下这段时间的工作情况&#xff0c;并给学弟学妹们提供一些指引。 目前&#xff0c;我所在的公司是一家初创企业&#xff0c;专注于IPC安防领域。作为一名大专生&#xff0c;我深知自己的学历在…

Ubuntu 系统上部署 Kubernetes 的完整指南

Ubuntu 系统上部署 Kubernetes 的完整指南 一、环境准备&#xff08;Ubuntu 22.04/24.04&#xff09;1. 系统初始化2. 安装容器运行时&#xff08;containerd&#xff09;3. 安装 Kubernetes 组件&#xff08;kubeadm, kubelet, kubectl&#xff09; 二、部署 Kubernetes 集群1…

partition_pdf 和chunk_by_title 的区别

from unstructured.partition.pdf import partition_pdf from unstructured.chunking.title import chunk_by_titlepartition_pdf 和 chunk_by_title 初看有点像&#xff0c;都在"分块"&#xff0c;但是它们的本质完全不一样。 先看它们核心区别 partition_pdfchun…

基于深度学习的医疗诊断辅助系统设计

标题:基于深度学习的医疗诊断辅助系统设计 内容:1.摘要 随着医疗数据的爆炸式增长和深度学习技术的飞速发展&#xff0c;开发基于深度学习的医疗诊断辅助系统具有重要的现实意义。本研究的目的在于设计一个高效、准确的医疗诊断辅助系统&#xff0c;以辅助医生进行更精准的诊断…

Matlab/Simulink - BLDC直流无刷电机仿真基础教程(四) - PWM调制模拟

Matlab/Simulink - BLDC直流无刷电机仿真基础教程&#xff08;四&#xff09; - PWM调制模拟 前言一、PWM调制技术基本原理二、仿真模型中加入PWM调制三、逆变电路MOS管添加体二极管四、模拟添加机械负载五、仿真模型与控制框图文章相关模型文件下载链接参考链接 前言 本系列文…

Curl 全面使用指南

Curl&#xff08;Client URL&#xff09;是一个跨平台命令行工具&#xff0c;支持多种协议&#xff08;HTTP/HTTPS/FTP/SFTP等&#xff09;&#xff0c;用于数据传输、API调试、文件上传/下载等场景。以下从 核心功能、用户疑问解答、高级技巧 三方面系统总结&#xff0c;并整合…