SpringBoot + Hadoop + HDFS + Vue 实现一个简单的文件管理系统

1. 安装前的准备工作

1.1 更新系统并安装必要的工具

在终端中运行以下命令:

sudo apt-get update
sudo apt-get install -y ssh rsync curl

1.2 安装 Java

如果系统中没有安装 Java,可以通过以下命令安装 OpenJDK:

sudo apt-get install -y openjdk-8-jdk

验证 Java 是否安装成功:

java -version

1.3 配置 SSH 无密码登录

Hadoop 需要通过 SSH 进行节点间通信。首先,生成 SSH 密钥对:

ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa

然后,将公钥添加到授权密钥列表中:

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

测试无密码登录:

ssh localhost

2. 下载和安装 Hadoop

2.1 下载 Hadoop

访问 Apache Hadoop 的官方网站并下载最新的稳定版本。你可以使用 curl 命令下载:

curl -O https://downloads.apache.org/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz

2.2 解压 Hadoop

下载完成后,解压 Hadoop 压缩包:

tar -xzvf hadoop-3.3.6.tar.gz

2.3 配置环境变量

编辑 ~/.bashrc(当前用户目录下,也就是用户的目录)添加 Hadoop 的环境变量:

export HADOOP_HOME=/home/hdfs/hadoop-3.3.6
export HADOOP_HDFS_HOME=$HADOOP_HOME
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop

保存文件并刷新环境变量:

source ~/.bashrc

3. 配置 Hadoop

3.1 配置 core-site.xml

编辑 $HADOOP_HOME/etc/hadoop/core-site.xml 文件,设置默认文件系统为 HDFS:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration><property><name>fs.defaultFS</name><value>hdfs://localhost:9000</value></property>
</configuration>

3.2 配置 hdfs-site.xml

编辑 $HADOOP_HOME/etc/hadoop/hdfs-site.xml`,设置远程访问地址,以及namenode。

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration><!-- 设置数据副本数 --><property><name>dfs.replication</name><value>1</value></property><!-- NameNode的存储目录 --><property><name>dfs.namenode.name.dir</name><value>file:///home/hdfs/hadoop-3.3.6/namenode</value></property><!-- NameNode的RPC地址 --><property><name>dfs.namenode.rpc-address</name><value>0.0.0.0:9000</value></property><!-- DataNode的存储目录 --><property><name>dfs.datanode.data.dir</name><value>file:///home/hdfs/hadoop-3.3.6/datanode</value></property>
</configuration>

3.1 确定 Java 安装目录

sudo update-alternatives --config java

3.4 配置 JAVA_HOME

编辑 $HADOOP_HOME/etc/hadoop/hadoop-env.sh文件,配置 JAVA_HOME 设置:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

4. 启动 HDFS

4.1 格式化 NameNode

在首次启动 HDFS 之前,需要格式化 NameNode:

hdfs namenode -format

4.2 启动 HDFS

启动 NameNode 和 DataNode 服务:

start-dfs.sh 

你可以使用以下命令检查进程是否启动成功:

jps

正常情况下,你应该会看到 NameNodeDataNode 进程在运行。

结束进程:

kill -9 pid #可以先通过jps查看进程,再杀掉

4.3 验证 HDFS

你可以通过浏览器访问 NameNode 的 Web UI,地址是:

http://192.168.186.77:9870

在这里插入图片描述

4.4 查看数据节点状态

hdfs dfsadmin -report 

5. 项目结构

在这里插入图片描述

5.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>hdfs_hadoop</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>3.3.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>

5.2 HdfsHadoopApplication.java

package org.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class HdfsHadoopApplication {public static void main(String[] args) {SpringApplication.run(HdfsHadoopApplication.class, args);}
}

5.3 HDFSService.java

package org.example.service;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.example.model.SimpleFileStatusDTO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;@Service
public class HDFSService {private static final String HDFS_URI = "hdfs://192.168.186.77:9000";private static final String BASE_DIR = "/home"; // HDFS 上的基本目录private final FileSystem fileSystem;// 构造函数,初始化 FileSystempublic HDFSService() throws IOException, InterruptedException {Configuration configuration = new Configuration();configuration.set("fs.defaultFS", HDFS_URI);// 设置环境变量,指定 HDFS 用户System.setProperty("HADOOP_USER_NAME", "liber");// 初始化 FileSystemthis.fileSystem = FileSystem.get(URI.create(HDFS_URI), configuration);}// 1. 上传文件到 HDFSpublic void uploadFile(MultipartFile file, String subDirectory) throws IOException {// 生成新的文件名,避免重名冲突String originalFilename = file.getOriginalFilename();String newFilename = UUID.randomUUID() + "_" + originalFilename;// 目标目录路径String targetDirectory = BASE_DIR + (subDirectory.startsWith("/") ? subDirectory : "/" + subDirectory);Path directoryPath = new Path(targetDirectory);// 如果目录不存在,创建目录if (!fileSystem.exists(directoryPath)) {fileSystem.mkdirs(directoryPath);}// 目标文件路径Path destinationPath = new Path(targetDirectory + "/" + newFilename);// 上传文件try (FSDataOutputStream outputStream = fileSystem.create(destinationPath)) {outputStream.write(file.getBytes());}}// 2. 删除文件或目录public void deleteFile(String hdfsPath) throws IOException {fileSystem.delete(new Path(BASE_DIR+"/"+hdfsPath), true);}// 3. 列出目录内容public Map<String,Object> listFiles(String subDirectory) throws IOException {String directoryPath = BASE_DIR + (subDirectory.startsWith("/") ? subDirectory : "/" + subDirectory);FileStatus[] fileStatuses = fileSystem.listStatus(new Path(directoryPath));List<SimpleFileStatusDTO> fileStatusDTOList = new ArrayList<>();for (FileStatus fileStatus : fileStatuses) {fileStatusDTOList.add(new SimpleFileStatusDTO(fileStatus));}Map<String,Object> map=new HashMap<>();map.put("basePath", subDirectory);map.put("files", fileStatusDTOList);return map;}// 4. 创建目录public void createDirectory(String subDirectory) throws IOException {String targetDirectory = BASE_DIR + (subDirectory.startsWith("/") ? subDirectory : "/" + subDirectory);Path path = new Path(targetDirectory);if (!fileSystem.exists(path)) {fileSystem.mkdirs(path);} else {throw new IOException("Directory already exists: " + targetDirectory);}}// 5. 下载文件public InputStream readFileAsStream(String hdfsFilePath) throws IOException {Path path = new Path(BASE_DIR+hdfsFilePath);return fileSystem.open(path);}// 6. 重命名文件或目录public void rename(String sourceSubDirectory, String destSubDirectory) throws IOException {String sourcePath = BASE_DIR + (sourceSubDirectory.startsWith("/") ? sourceSubDirectory : "/" + sourceSubDirectory);String destPath = BASE_DIR + (destSubDirectory.startsWith("/") ? destSubDirectory : "/" + destSubDirectory);Path src = new Path(sourcePath);Path dst = new Path(destPath);if (!fileSystem.rename(src, dst)) {throw new IOException("Failed to rename: " + sourcePath + " to " + destPath);}}
}

5.4 SimpleFileStatusDTO.java

package org.example.model;import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hadoop.fs.FileStatus;@Data
@NoArgsConstructor
public class SimpleFileStatusDTO {private String pathSuffix;private long length;private boolean isDirectory;public SimpleFileStatusDTO(FileStatus fileStatus) {String pathSuffix = fileStatus.getPath().toString();this.pathSuffix = pathSuffix.substring(pathSuffix.lastIndexOf("/")+1);this.length = fileStatus.getLen();this.isDirectory = fileStatus.isDirectory();}
}

5.5 HDFSController.java

package org.example.controller;import org.example.service.HDFSService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.Map;@RestController
@RequestMapping("/hdfs")
public class HDFSController {private final HDFSService hdfsService;@Autowiredpublic HDFSController(HDFSService hdfsService) {this.hdfsService = hdfsService;}// 1. 上传文件@PostMapping("/upload")public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file,@RequestParam("hdfsDirectory") String hdfsDirectory) {try {hdfsService.uploadFile(file, hdfsDirectory);return ResponseEntity.ok("上传成功");} catch (IOException e) {return ResponseEntity.status(500).body(null);}}// 2. 下载文件@GetMapping("/download")public ResponseEntity<InputStreamResource> downloadFile(@RequestParam("hdfsFilePath") String hdfsFilePath) {try {String filename = hdfsFilePath.substring(hdfsFilePath.lastIndexOf("/") + 1);InputStreamResource resource = new InputStreamResource(hdfsService.readFileAsStream(hdfsFilePath));return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"").body(resource);} catch (IOException e) {return ResponseEntity.status(500).body(null);}}// 3. 删除文件或目录@DeleteMapping("/delete")public ResponseEntity<String> deleteFile(@RequestParam("hdfsPath") String hdfsPath) {try {hdfsService.deleteFile(hdfsPath);return ResponseEntity.ok("File deleted successfully");} catch (IOException e) {return ResponseEntity.status(500).body("Failed to delete file: " + e.getMessage());}}// 4. 列出目录内容@GetMapping("/list")public ResponseEntity<Map<String, Object>> listFiles(@RequestParam("directoryPath") String directoryPath) {try {Map<String, Object> files = hdfsService.listFiles(directoryPath);return ResponseEntity.ok(files);} catch (IOException e) {return ResponseEntity.status(500).body(null);}}// 5. 创建目录@PostMapping("/mkdir")public ResponseEntity<String> createDirectory(@RequestParam("directoryPath") String directoryPath) {try {hdfsService.createDirectory(directoryPath);return ResponseEntity.ok("Directory created successfully");} catch (IOException e) {return ResponseEntity.status(500).body("Failed to create directory: " + e.getMessage());}}// 6. 重命名文件或目录@PostMapping("/rename")public ResponseEntity<String> rename(@RequestParam("sourcePath") String sourcePath,@RequestParam("destPath") String destPath) {try {hdfsService.rename(sourcePath, destPath);return ResponseEntity.ok("File renamed successfully");} catch (IOException e) {return ResponseEntity.status(500).body("Failed to rename file: " + e.getMessage());}}
}

5.6 application.yml

spring:application:name: hdfs_hadoopservlet:multipart:max-file-size: 1024MBmax-request-size: 1024MB

5.7 index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>HDFS 文件管理</title><!-- Vue.js CDN --><script src="https://cdn.jsdelivr.net/npm/vue@2"></script><!-- Axios CDN 用于 HTTP 请求 --><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><!-- Bootstrap CDN 用于样式 --><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"><style>.current-path {font-weight: bold;font-size: 1.2em;margin-bottom: 15px;}.go-up-item {background-color: #f8f9fa;cursor: pointer;}.go-up-item:hover {background-color: #e2e6ea;}.file-item {cursor: pointer;}.file-item:hover {background-color: #f1f3f5;}.btn-icon {background-color: transparent;border: none;color: #007bff;cursor: pointer;padding: 0.2rem;}.btn-icon:hover {color: #0056b3;}.form-inline {display: flex;align-items: center;gap: 10px;margin-bottom: 15px;}.form-inline input {flex: 1;}</style>
</head>
<body>
<div id="app" class="container mt-5"><h1 class="mb-4">HDFS 文件管理</h1><!-- 目录列表、创建目录和上传文件 --><div class="mb-3"><h4>管理目录</h4><div class="current-path"><span>📁 {{ currentPath }}</span></div><!-- 创建目录的内联表单 --><div class="form-inline"><input type="text" v-model="newDirectoryPath" placeholder="新目录名称" class="form-control"><button @click="createDirectory" class="btn btn-info">创建目录</button><button @click="showUploadDialog" class="btn btn-primary ms-2">上传文件</button></div><ul class="list-group"><li v-if="currentPath !== '/'" @click="goUpOneLevel" class="list-group-item go-up-item"><strong>🔙 返回上一级</strong></li><li v-for="file in files" :key="file.pathSuffix" class="list-group-item d-flex justify-content-between align-items-center file-item"><div @click="file.directory ? onDirectoryClick(file) : null"><span v-if="file.directory">📁</span><span v-else>📄</span>{{ file.pathSuffix }}<!-- 当是文件时显示文件大小 --><span v-if="!file.directory" class="text-muted">({{ formatFileSize(file.length) }})</span></div><div><button @click="showRenameDialog(file)" class="btn-icon"><span>✏️</span></button><button v-if="file.directory" @click="deleteFile(currentPath + '/' + file.pathSuffix)" class="btn-icon"><span>🗑️</span></button><button v-if="!file.directory" @click="downloadFile(currentPath + '/' + file.pathSuffix)" class="btn-icon"><span>⬇️</span></button><button v-if="!file.directory" @click="deleteFile(currentPath + '/' + file.pathSuffix)" class="btn-icon"><span>🗑️</span></button></div></li></ul></div><!-- 上传文件的模态框 --><div class="modal" tabindex="-1" role="dialog" id="uploadModal"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">上传文件</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><input type="file" @change="onFileChange" class="form-control"></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button><button type="button" class="btn btn-primary" @click="handleUpload">上传</button></div></div></div></div><!-- 重命名的模态框 --><div class="modal" tabindex="-1" role="dialog" id="renameModal"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">重命名</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><input type="text" v-model="renameNewName" class="form-control"></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button><button type="button" class="btn btn-primary" @click="handleRename">重命名</button></div></div></div></div>
</div><script>new Vue({el: '#app',data: {uploadFile: null,currentPath: '/',  // 当前目录路径newDirectoryPath: '',files: [],renameFile: null,  // 需要重命名的文件或目录renameNewName: '', // 新名称},methods: {// 处理文件选择onFileChange(event) {this.uploadFile = event.target.files[0];},// 显示上传模态框showUploadDialog() {const modal = new bootstrap.Modal(document.getElementById('uploadModal'));modal.show();},// 显示重命名模态框showRenameDialog(file) {this.renameFile = file;this.renameNewName = file.pathSuffix;const modal = new bootstrap.Modal(document.getElementById('renameModal'));modal.show();},// 上传文件async handleUpload() {try {const formData = new FormData();formData.append('file', this.uploadFile);formData.append('hdfsDirectory', this.currentPath);await axios.post('/hdfs/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}});this.listFiles();  // 上传后刷新文件列表const modal = bootstrap.Modal.getInstance(document.getElementById('uploadModal'));modal.hide(); // 上传后隐藏模态框} catch (error) {console.error('上传文件时出错:', error);}},// 重命名文件或目录async handleRename() {try {const sourcePath = this.currentPath + '/' + this.renameFile.pathSuffix;const destPath = this.currentPath + '/' + this.renameNewName;await axios.post('/hdfs/rename', null, {params: { sourcePath, destPath }});this.listFiles();  // 重命名后刷新文件列表const modal = bootstrap.Modal.getInstance(document.getElementById('renameModal'));modal.hide(); // 重命名后隐藏模态框} catch (error) {console.error('重命名文件或目录时出错:', error);}},// 列出目录中的文件async listFiles() {try {const response = await axios.get('/hdfs/list', {params: { directoryPath: this.currentPath }});this.files = response.data.files; // 取出 files 数组this.currentPath = response.data.basePath; // 更新当前路径} catch (error) {console.error('列出文件时出错:', error);}},// 下载文件async downloadFile(filePath) {try {const response = await axios.get('/hdfs/download', {params: { hdfsFilePath: filePath },responseType: 'blob'});const url = window.URL.createObjectURL(new Blob([response.data]));const link = document.createElement('a');link.href = url;link.setAttribute('download', filePath.split('/').pop());document.body.appendChild(link);link.click();} catch (error) {console.error('下载文件时出错:', error);}},// 删除文件或目录async deleteFile(filePath) {try {await axios.delete('/hdfs/delete', {params: { hdfsPath: filePath }});this.listFiles(); // 刷新文件列表} catch (error) {console.error('删除文件或目录时出错:', error);}},// 创建新目录async createDirectory() {try {await axios.post('/hdfs/mkdir', null, {params: { directoryPath: this.currentPath + '/' + this.newDirectoryPath }});this.newDirectoryPath = ''; // 清空输入框this.listFiles(); // 创建目录后刷新文件列表} catch (error) {console.error('创建目录时出错:', error);}},// 返回上一级目录goUpOneLevel() {const pathParts = this.currentPath.split('/').filter(part => part);if (pathParts.length > 1) {pathParts.pop();this.currentPath = '/' + pathParts.join('/');} else {this.currentPath = '/';}this.listFiles(); // 刷新文件列表},// 进入一个目录onDirectoryClick(file) {if (!this.currentPath.endsWith('/')) {this.currentPath += '/';}if (!this.currentPath.endsWith(file.pathSuffix)) {this.currentPath += file.pathSuffix;}this.listFiles(); // 刷新文件列表以显示点击的目录的内容},// 格式化文件大小formatFileSize(size) {if (size < 1024) return size + ' B';else if (size < 1048576) return (size / 1024).toFixed(2) + ' KB';else if (size < 1073741824) return (size / 1048576).toFixed(2) + ' MB';else return (size / 1073741824).toFixed(2) + ' GB';}},mounted() {this.listFiles(); // 页面加载时加载初始目录中的文件}});
</script>
<!-- Bootstrap JS 用于模态框 -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script>
</body>
</html>

6. 测试验证

6.1 创建目录

在这里插入图片描述

6.2 创建结果

在这里插入图片描述

6.3 上传文件

在这里插入图片描述

6.4 上传结果

在这里插入图片描述

6.5 重命名测试

在这里插入图片描述

6.6 重命名结果

在这里插入图片描述

6.7 其他

删除,下载等不再赘余。

6.8 查看HDFS默认的文件管理

在这里插入图片描述

7、遇到错误

第1个错误

java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.

下载对应版本的原生文件:https://github.com/cdarlint/winutils

然后修改下面内容到系统环境即可。

HADOOP_HOME=D:\hadoop-3.3.6\bin
%HADOOP_HOME%\bin

在这里插入图片描述

重要的话要说三遍:记得重启,记得重启,记得重启IDEA编译器。

第2个错误

Engine2 : Call: addBlock took 169ms
2024-08-11T19:14:22.716+08:00 DEBUG 13116 — [hdfs_hadoop] [ Thread-5] org.apache.hadoop.hdfs.DataStreamer : pipeline = [DatanodeInfoWithStorage[127.0.0.1:9866,DS-d52f1df8-88e2-4807-bc48-842e7b9f07a2,DISK]], blk_1073741826_1002
2024-08-11T19:14:22.716+08:00 DEBUG 13116 — [hdfs_hadoop] [ Thread-5] org.apache.hadoop.hdfs.DataStreamer : Connecting to datanode 127.0.0.1:9866
2024-08-11T19:14:22.718+08:00 WARN 13116 — [hdfs_hadoop] [ Thread-5] org.apache.hadoop.hdfs.DataStreamer : Exception in createBlockOutputStream blk_1073741826_1002

java.net.ConnectException: Connection refused: getsockopt
at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682) ~[na:na]

org.apache.hadoop.ipc.RemoteException: File /home/7dff6c94-88d2-4b62-83b9-92f93253b473_01.jpg could only be written to 0 of the 1 minReplication nodes. There are 1 datanode(s) running and 1 node(s) are excluded in this operation.
at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.chooseTarget4NewBlock(BlockManager.java:2350)

说明:一开始,我以为一个时间节点不行,后来想试一下集群还是不行,最后通过以下方式进行修改:

在这里插入图片描述

编辑 /etc/hosts 文件

sudo nano /etc/hosts
  • 我是把liber-vmware-virtual-platform 改为 192.168.186.77 连接成功,不然默认是127.0.0.1。

重要的话要说三遍:记得重启,记得重启,记得重启Ubuntu。

8. 总结

​ 基于Ubuntu24.04 TLS 安装Hadoop以及配置HDFS,通过Spring Boot 和 Vue 实现一个简单的文件管理系统。

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

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

相关文章

基于ESP32的智能门锁系统测试

项目介绍 基于ESP32的智能门锁系统。可以运用在商务办公、家用住宅、酒店以及公租房短租公寓等领域。基于esp32的智能门锁系统是生物识别技术和嵌入式系统技术的完美结合&#xff0c;基于ESP32系统进行开发&#xff0c;同时在云端服务器搭建了MQTT服务器并连接开源的家庭自动化…

工商业和户用光伏区别及怎样运维

工商业光伏系统和户用光伏系统在设计、安装和运维方面存在一些显著的区别。首先&#xff0c;工商业光伏系统通常安装在工厂、办公楼、商场等大型建筑物的屋顶或空地上&#xff0c;而户用光伏系统则主要安装在居民住宅的屋顶上。工商业光伏系统的规模一般较大&#xff0c;发电量…

Unity | AmplifyShaderEditor插件基础(第二集:模版说明)

目录 一、前言 二、核心模版和URP模版 1.区别介绍 2.自己的模版 三、输出节点 1.界面 2.打开OutPut 3.ShderType 4.ShaderName 5.Shader大块内容 6.修改内容 四、预告 一、前言 内容全部基于以下链接基础以上讲的。 Unity | Shader基础知识&#xff08;什么是shader…

Android 实现动态换行显示的 TextView 列表

在开发 Android 应用程序时&#xff0c;我们经常需要在标题栏中显示多个 TextView&#xff0c;而这些 TextView 的内容长度可能不一致。如果一行内容过长&#xff0c;我们希望它们能自动换行&#xff1b;如果一行占不满屏幕宽度&#xff0c;则保持在一行内。本文将带我们一步步…

仅12%程序员担心被AI取代 62%开发者在使用AI工具

**根据Stack Overflow近日发布的2024年开发者调查报告&#xff0c;只有12%的开发者认为AI威胁到了他们当前的工作&#xff0c;而高达70%的受访者已经将AI工具整合到了自己的工作流程中。**该调查共有超过6.5万名开发者参与&#xff0c;结果显示&#xff0c;使用AI工具的开发者比…

Java知识点一——列表、表格与媒体元素

显示表格边框&#xff1a;<table border"1"></table> 因为初始的表格是没有边框的 collapse相邻的单元格共用同一条边框&#xff08;采用 collapsed-border 表格渲染模型&#xff09;。 separate默认值。每个单元格拥有独立的边框&#xff08;采用 sep…

什么是实时数据仓库? 优势与最佳实践

在当今数据驱动的世界中&#xff0c;许多企业使用实时数据仓库来满足其分析和商业智能 (BI) 需求。这使他们能够做出更好的决策、推动增长并为客户提供价值。 数据仓库是一种数据存储和管理系统&#xff0c;其设计目标只有一个&#xff1a;管理和分析数据&#xff0c;以实现商…

掌握Jenkins自动化部署:从代码提交到自动上线的全流程揭秘

Jenkins自动化部署是现代软件开发中不可或缺的一部分&#xff0c;它不仅简化了代码的发布过程&#xff0c;还为整个团队带来了无与伦比的效率和协作力。想象一下&#xff0c;开发者们可以专注于编写高质量的代码&#xff0c;而不是为繁琐的手动部署所烦恼&#xff1b;测试人员能…

Python进阶之3D图形

Python进阶之3D图形 在数据可视化中&#xff0c;2D图形通常可以满足大多数需求。然而&#xff0c;对于一些复杂的数据或分析&#xff0c;3D图形可以提供更多的视角和洞察。在Python中&#xff0c;使用 Matplotlib 和 Plotly 等库可以轻松创建各种3D图形。本文将介绍如何使用这…

C++_2_ inline内联函数 宏函数(2/3)

C推出了inline关键字&#xff0c;其目的是为了替代C语言中的宏函数。 我们先来回顾宏函数&#xff1a; 宏函数 现有个需求&#xff1a;要求你写一个Add(x,y)的宏函数。 正确的写法有一种&#xff0c;错误的写法倒是五花八门&#xff0c;我们先来“见不贤而自省也。” // …

SpringCloud的能源管理系统-能源管理平台源码

介绍 基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码 软件架构

提升体验:UI设计的可用性原则

在中国&#xff0c;每年都有数十万设计专业毕业生涌入市场&#xff0c;但只有少数能够进入顶尖企业。尽管如此&#xff0c;所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时&#xff0c;我们可能会依赖直觉来决定设计方案&#xff0c;或者在寻找改善产品体验的切入…

【STM32F4】——DMA初始化结构体详解

一.DMA_InitTypeDef 初始化结构体 typedef struct {uint32_t DMA_Channel; //通道选择 uint32_t DMA_PeripheralBaseAddr;//外设地址uint32_t DMA_Memory0BaseAddr; //存储器 0 地址uint32_t DMA_DIR; //传输方向 uint32_t DMA_BufferSize; /…

opencascade Adaptor3d_CurveOnSurface源码学习

opencascade Adaptor3d_CurveOnSurface 前言 用于连接由Geom包中表面上的曲线提供的服务&#xff0c;以及使用这条曲线的算法所要求的服务。该曲线被定义为一个二维曲线&#xff0c;来自Geom2d包&#xff0c;位于表面的参数空间中 方法 1 默认构造函数 Standard_EXPORT Ada…

Windows设置定时任务进行oracle数据库备份

先找到“定时任务计划” 方法1.开始->所有程序->附件->系统工具->定时任务计划 方法2:控制面板->输入计划 进行查询操作 名称随便定&#xff0c;点击下一步 下一步 设置每天的定时执行时间&#xff0c;点下一步 点下一步选择启动程序&#xff0c;点下一步 点…

Lesson 64 Don‘t ... You mustn‘t ...

Lesson 64 Don’t … You mustn’t … 词汇 play n. 戏剧&#xff08;真人演的&#xff0c;话剧&#xff09;v. 玩耍 搭配&#xff1a;play with 物体 / 人    玩…… / 和……一起玩 例句&#xff1a;我正在和Leo玩。    I am playing with Leo.演奏&#xff08;乐器…

ddos造成服务器瘫痪后怎么办

在服务器遭受DDoS攻击后&#xff0c;应立即采取相应措施&#xff0c;包括加强服务器安全、使用CDN和DDoS防御服务来减轻攻击的影响。rak小编为您整理发布ddos造成服务器瘫痪后怎么办。 当DDoS攻击发生时&#xff0c;首先要做的是清理恶意流量。可以通过云服务提供商提供的防护措…

初步融合snowboy+pyttsx3+espeak+sherpa-ncnn的python代码

在前文《将Snowboy语音唤醒的“叮”一声改成自定义语言》中&#xff0c;我已经实现唤醒snowboy后&#xff0c;树莓派会说一句自定义文本。今天&#xff0c;会在此基础上增加ASR的应用&#xff08;基于sherpa-ncnn&#xff09;。 首先&#xff0c;编写一个asr.py的程序&#xf…

@DateTimeFormat 和 @JsonFormat 注解详解

目录 一、快速入门1.1 准备工作1.2、入参格式化&#xff08;前端传参到后端&#xff09;1.3、出参格式化&#xff08;后端返回给前端&#xff09;1.4、如果是请求体RequestBody传参 二、详细解释这两个注解1、JsonFormat2、DateTimeFormat注意&#xff1a;1、这两者的注解一般联…

大型、复杂、逼真的安全服和安全帽检测:SFCHD数据集和SCALE方法

智能守护工地安全&#xff1a;SFCHD数据集与SCALE模块介绍 在人工智能&#xff08;AI&#xff09;技术飞速发展的今天&#xff0c;其在建筑工地安全领域的应用正逐渐展现出巨大潜力。尤其是高风险行业如化工厂的施工现场&#xff0c;对工人的保护措施要求极为严格。个人防护装…