構建網頁版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.pswp.cn/news/903754.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903754.shtml
英文地址,請注明出處:http://en.pswp.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;并整合…