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

java每日精进 5.18【文件存储】

1.文件存储思路

支持将文件上传到三类存储器:

  1. 兼容 S3 协议的对象存储:支持 MinIO、腾讯云 COS、七牛云 Kodo、华为云 OBS、亚马逊 S3 等等。
  2. 磁盘存储:本地、FTP 服务器、SFTP 服务器。
  3. 数据库存储:MySQL、Oracle、PostgreSQL、SQL Server 等等。

技术选型?

  • 优先,✔ 推荐方案 1。如果无法使用云服务,可以自己搭建一个 MinIO 服务。
  • 其次,推荐方案 3。数据库的主从机制可以实现高可用,备份也方便,少量小文件问题不大。
  • 最后,× 不推荐方案 2。主要是实现高可用比较困难,无法实现故障转移。

2.MinIo服务

1. 创建存储目录

命令

mkdir E:\youkeProject\Minio\data -Force

说明

  • 作用:在 E:\youkeProject\Minio\data 创建用于存储 MinIO 数据的目录,-Force 确保即使目录已存在也不会报错。
  • 注意事项
    • 确保 E:\ 磁盘存在且有足够空间(建议至少预留几 GB,视存储需求而定)。
    • 磁盘需有写入权限,当前用户必须能访问 E:\youkeProject。
  • 错误排查
    • 错误:mkdir : Access is denied(权限不足)
      • 解决:以管理员身份运行 PowerShell(右键 PowerShell,选择“以管理员身份运行”)。
      • 验证:运行 whoami 检查用户是否为管理员(如 NT AUTHORITY\SYSTEM 或 yourdomain\admin)。
    • 错误:mkdir : The device is not ready(磁盘不可用)
      • 解决:检查 E:\ 是否挂载(运行 Get-Disk 或 Get-Volume),确保磁盘可用。
      • 验证:运行 Test-Path E:\,返回 True 表示路径有效。

验证

Test-Path E:\youkeProject\Minio\data

  • 预期输出:True

2. 下载 MinIO 可执行文件

命令

Invoke-WebRequest -Uri "https://mirrors.tuna.tsinghua.edu.cn/minio/releases/windows-amd64/minio.exe" -OutFile "E:\youkeProject\Minio\minio.exe"

说明

  • 作用:从清华大学镜像源下载 MinIO 的 Windows 可执行文件,保存到 E:\youkeProject\Minio\minio.exe。
  • 注意事项
    • 确保网络连接稳定,清华大学镜像源通常比官方源更快。
    • 确认系统为 64 位 Windows(minio.exe 为 windows-amd64 架构)。
    • 下载路径 E:\youkeProject\Minio 必须存在且可写。
  • 错误排查
    • 错误:Invoke-WebRequest : The remote server returned an error: (404) Not Found
      • 解决:镜像源 URL 可能失效,尝试官方源:

        Invoke-WebRequest -Uri "https://dl.min.io/server/minio/release/windows-amd64/minio.exe" -OutFile "E:\youkeProject\Minio\minio.exe"

      • 验证:检查 URL 是否可访问(在浏览器中打开)。
    • 错误:Invoke-WebRequest : Access is denied
      • 解决:以管理员身份运行 PowerShell,或检查 E:\youkeProject\Minio 目录权限:

        icacls "E:\youkeProject\Minio" /grant "Users:(W)"

    • 错误:网络连接失败
      • 解决:检查网络(运行 ping mirrors.tuna.tsinghua.edu.cn),或使用代理(如有):

        $env:HTTP_PROXY="http://proxy:port" $env:HTTPS_PROXY="http://proxy:port"

验证

Test-Path E:\youkeProject\Minio\minio.exe

  • 预期输出:True
  • 检查文件版本:

    (Get-Item E:\youkeProject\Minio\minio.exe).VersionInfo


3. 设置环境变量

命令

setx MINIO_ROOT_USER "admin" /M setx MINIO_ROOT_PASSWORD "password123" /M

说明

  • 作用:设置 MinIO 的管理员用户名和密码为系统环境变量,/M 表示设置系统级变量(需要管理员权限)。
  • 注意事项
    • MINIO_ROOT_PASSWORD 必须至少 8 位,建议包含大小写字母、数字和符号,例如 P@ssw0rd123。
    • 环境变量在当前 PowerShell 会话中不会立即生效,需重启 PowerShell 或系统。
    • 运行命令后,变量会存储在注册表(HKLM\System\CurrentControlSet\Control\Session Manager\Environment)。
  • 错误排查
    • 错误:setx : Access is denied
      • 解决:以管理员身份运行 PowerShell。
      • 验证:运行 whoami 确认用户为管理员。
    • 错误:密码不符合要求
      • 解决:确保密码长度 ≥ 8 位,包含复杂字符:

        setx MINIO_ROOT_PASSWORD "P@ssw0rd123" /M

    • 问题:环境变量未生效
      • 解决:重启 PowerShell 或运行以下命令刷新:

        $env:MINIO_ROOT_USER = [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_USER", "Machine") $env:MINIO_ROOT_PASSWORD = [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_PASSWORD", "Machine")

验证

[System.Environment]::GetEnvironmentVariable("MINIO_ROOT_USER", "Machine") [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_PASSWORD", "Machine")

  • 预期输出:admin 和 P@ssw0rd123(或您设置的密码)

4. 注册为 Windows 服务

命令

# 创建服务
sc.exe create MinIO binPath= "E:\youkeProject\Minio\minio.exe server E:\youkeProject\Minio\data --console-address :9001" start= auto displayname= "MinIO Object Storage"

# 启动服务
sc start MinIO

说明

  • 作用
    • sc.exe create:创建名为 MinIO 的 Windows 服务,指定可执行文件路径和参数。
    • binPath:运行 MinIO 服务器,数据目录为 E:\youkeProject\Minio\data,Web 控制台端口为 9001。
    • start= auto:服务随系统启动自动运行。
    • displayname:服务在服务管理器中的显示名称。
    • sc start:启动 MinIO 服务。
  • 注意事项
    • 必须以管理员身份运行 sc.exe。
    • 确保 E:\youkeProject\Minio\minio.exe 和 E:\youkeProject\Minio\data 路径正确。
    • 端口 9001(控制台)和 9000(API)不能被占用。
  • 错误排查
    • 错误:sc.exe : [SC] CreateService FAILED 5: Access is denied
      • 解决:以管理员身份运行 PowerShell。
    • 错误:sc.exe : [SC] CreateService FAILED 1053: The service did not respond
      • 解决:检查 binPath 是否正确,路径或文件可能有误:

        Test-Path E:\youkeProject\Minio\minio.exe Test-Path E:\youkeProject\Minio\data

      • 验证:手动运行命令检查错误:

        E:\youkeProject\Minio\minio.exe server E:\youkeProject\Minio\data --console-address :9001

    • 错误:sc start : [SC] StartService FAILED 1057: The account name is invalid
      • 解决:确保服务以正确账户运行,默认使用 LocalSystem:

        sc.exe config MinIO obj= LocalSystem

    • 错误:端口被占用
      • 解决:检查端口 9000 和 9001:

        netstat -ano | findstr ":9000" netstat -ano | findstr ":9001"

      • 如果占用,修改端口(例如 --console-address :9002)并重新创建服务:

        sc.exe delete MinIO sc.exe create MinIO binPath= "E:\youkeProject\Minio\minio.exe server E:\youkeProject\Minio\data --console-address :9002" start= auto displayname= "MinIO Object Storage"

验证

Get-Service MinIO | Select-Object Name, Status, StartType

  • 预期输出:

    Name Status StartType ---- ------ --------- MinIO Running Automatic


5. 验证安装

步骤

  1. 访问 Web 管理界面
    • 打开浏览器,访问 http://localhost:9001。
    • 使用用户名 admin 和密码(例如 P@ssw0rd123)登录。
    • 登录成功后,应看到 MinIO 的 Web 控制台,可管理存储桶和文件。
  2. 检查服务状态

    Get-Service MinIO

    • 预期输出:Status 为 Running。

错误排查

  • 问题:无法访问 http://localhost:9001
    • 解决:
      • 确认服务是否运行:

        Get-Service MinIO

      • 检查端口是否监听:

        netstat -ano | findstr ":9001"

      • 如果端口未监听,尝试手动启动:

        E:\youkeProject\Minio\minio.exe server E:\youkeProject\Minio\data --console-address :9001

      • 检查防火墙是否阻止访问(见步骤 6)。
  • 问题:登录失败
    • 解决:
      • 确认环境变量:

        [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_USER", "Machine") [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_PASSWORD", "Machine")

      • 如果密码错误,重新设置:

        setx MINIO_ROOT_PASSWORD "P@ssw0rd123" /M

      • 重启服务:

        sc stop MinIO sc start MinIO

  • 问题:服务未启动
    • 解决:
      • 查看服务日志:

        Get-EventLog -LogName System -Newest 100 | Where-Object { $_.Source -eq "Service Control Manager" -and $_.Message -like "*MinIO*" } | Format-List

      • 检查 MinIO 日志(如果有文件输出):

        Get-Content -Path "E:\youkeProject\Minio\logs\minio.log" -Tail 10


6. 配置防火墙规则(可选)

命令

New-NetFirewallRule -DisplayName "MinIO Console" -Direction Inbound -Protocol TCP -LocalPort 9001 -Action Allow New-NetFirewallRule -DisplayName "MinIO API" -Direction Inbound -Protocol TCP -LocalPort 9000 -Action Allow

说明

  • 作用:开放 MinIO 的控制台端口(9001)和 API 端口(9000),允许外部访问。
  • 注意事项
    • 仅当需要从其他机器访问 MinIO 时执行。
    • 确保防火墙规则不会影响其他服务。
  • 错误排查
    • 错误:New-NetFirewallRule : Access is denied
      • 解决:以管理员身份运行 PowerShell。
    • 问题:规则未生效
      • 解决:检查防火墙状态:

        Get-NetFirewallProfile | Select-Object Name, Enabled

      • 启用防火墙(如果禁用):

        powershell

        Copy

        Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True

      • 验证规则:

        powershell

        Copy

        Get-NetFirewallRule -DisplayName "MinIO*" | Format-Table Name, DisplayName, Enabled, Direction, Action

验证

  • 从另一台机器访问 http://<your-ip>:9001 或 http://<your-ip>:9000。
  • 检查防火墙规则:

    Get-NetFirewallRule -DisplayName "MinIO*" | Format-List


7. 服务管理命令

命令

# 停止服务 sc stop MinIO

# 重启服务 sc restart MinIO

# 删除服务(卸载时使用) sc delete MinIO

说明

  • 作用
    • sc stop:停止 MinIO 服务。
    • sc restart:重启服务(等效于 stop 后 start)。
    • sc delete:移除 MinIO 服务(用于卸载)。
  • 注意事项
    • 所有命令需以管理员身份运行。
    • 删除服务前确保服务已停止:

      sc query MinIO

  • 错误排查
    • 错误:sc stop : [SC] ControlService FAILED 1062: The service has not been started
      • 解决:确认服务状态:

        Get-Service MinIO

      • 如果已停止,无需再次停止。
    • 错误:sc delete : [SC] DeleteService FAILED 1072: The specified service has been marked for deletion
      • 解决:等待几秒或重启系统后重试,或者手动删除注册表项(谨慎操作):

        Remove-Item -Path "HKLM:\System\CurrentControlSet\Services\MinIO" -Force

验证

  • 停止后:

    Get-Service MinIO | Select-Object Status

    • 预期输出:Stopped
  • 删除后:

    Get-Service MinIO -ErrorAction SilentlyContinue

    • 预期输出:无结果(服务不存在)

8. 使用 MinIO 客户端(mc)验证(可选)

步骤

  1. 安装 mc 客户端

    Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "C:\Windows\mc.exe"

  2. 配置别名

    mc alias set myminio http://localhost:9000 admin P@ssw0rd123

  3. 列出存储桶

    mc ls myminio

错误排查

  • 错误:mc: Unable to initialize new alias
    • 解决:确认 MinIO 服务运行,端口 9000 可访问,用户名和密码正确。
    • 验证:

      Test-NetConnection -ComputerName localhost -Port 9000

  • 错误:mc: No such file or directory
    • 解决:确保 mc.exe 在 PATH 中:

      setx PATH "%PATH%;C:\Windows" /M

验证

  • 预期输出示例:

    [2025-05-18 16:00:00 JST] 0B mybucket/


9. 手动启动 MinIO(调试用)

命令

E:\youkeProject\Minio\minio.exe server E:\youkeProject\Minio\data --console-address :9001

说明

  • 作用:在命令行手动启动 MinIO,用于调试或临时运行。
  • 注意事项
    • 需保持命令行窗口打开,关闭窗口会停止 MinIO。
    • 确保环境变量 MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 已设置。
  • 错误排查
    • 错误:ERROR Unable to validate credentials
      • 解决:检查环境变量:

        $env:MINIO_ROOT_USER $env:MINIO_ROOT_PASSWORD

      • 手动指定:

        $env:MINIO_ROOT_USER="admin" $env:MINIO_ROOT_PASSWORD="P@ssw0rd123"

    • 错误:ERROR Unable to use the drive
      • 解决:检查 E:\youkeProject\Minio\data 权限:

        icacls "E:\youkeProject\Minio\data" /grant "Users:(F)"

验证

  • 访问 http://localhost:9001,登录控制台。
  • 检查日志输出,确认启动成功。

10.Java交互

pom:

<?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 http://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.4</version><relativePath/></parent><groupId>com.example</groupId><artifactId>minio-file-manager</artifactId><version>0.0.1-SNAPSHOT</version><name>minio-file-manager</name><description>Spring Boot MinIO File Management System</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.13</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

application.yml:

spring:servlet:multipart:max-file-size: 10MBmax-request-size: 10MB
minio:endpoint: http://127.0.0.1:9000access-key: adminsecret-key: password123bucket-name: bucketone

config:

@Configuration
public class MinioConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.access-key}")private String accessKey;@Value("${minio.secret-key}")private String secretKey;@Value("${minio.bucket-name}")private String bucketName;@Beanpublic MinioClient minioClient() throws Exception {// 创建 MinIO 客户端MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();// 检查存储桶是否存在,不存在则创建boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!bucketExists) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}return minioClient;}
}

comtroller:

// 标记为 Spring REST 控制器,返回 JSON 响应
@RestController
// 设置控制器基础路径,所有端点以 /files 开头
@RequestMapping("/files")
public class FileController {// 声明 MinIO 文件服务依赖,通过构造函数注入private final MinioFileService minioFileService;// 构造函数,注入 MinioFileService 实例public FileController(MinioFileService minioFileService) {this.minioFileService = minioFileService;}// 处理文件上传请求,POST /files/upload@PostMapping("/upload")// 接收上传的文件,绑定请求中的 file 字段,抛出异常由全局处理器处理public String uploadFile(@RequestParam("file") MultipartFile file) throws Exception {// 调用服务层上传文件,返回生成的文件名return minioFileService.uploadFile(file);}// 处理文件下载请求,GET /files/download/{fileName}@GetMapping("/download/{fileName}")// 接收路径中的文件名,返回文件流响应public ResponseEntity<InputStreamResource> downloadFile(@PathVariable String fileName) throws Exception {// 调用服务层获取文件输入流InputStream inputStream = minioFileService.downloadFile(fileName);// 构建 HTTP 响应,设置下载头和二进制流return ResponseEntity.ok()// 设置 Content-Disposition 头,提示浏览器下载文件.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")// 设置内容类型为通用二进制流.contentType(MediaType.APPLICATION_OCTET_STREAM)// 包装输入流为响应体.body(new InputStreamResource(inputStream));}// 处理文件删除请求,DELETE /files/{fileName}@DeleteMapping("/{fileName}")// 接收路径中的文件名,删除文件并返回提示public String deleteFile(@PathVariable String fileName) throws Exception {// 调用服务层删除文件minioFileService.deleteFile(fileName);// 返回删除成功的提示消息return "File deleted: " + fileName;}// 处理文件列表请求,GET /files/list@GetMapping("/list")// 返回存储桶中的所有文件名列表public List<String> listFiles() throws Exception {// 调用服务层获取文件列表return minioFileService.listFiles();}
}

service:

// 标记为 Spring 服务组件,封装 MinIO 文件操作
@Service
public class MinioFileService {// 声明 MinIO 客户端实例,用于与 MinIO 服务器交互private final MinioClient minioClient;// 从配置文件注入存储桶名称(如 application.yml 中的 minio.bucket-name)@Value("${minio.bucket-name}")private String bucketName;// 构造函数,注入 MinioClient 实例public MinioFileService(MinioClient minioClient) {this.minioClient = minioClient;}// 上传文件到 MinIO 存储桶public String uploadFile(MultipartFile file) throws Exception {// 生成唯一文件名:时间戳 + 原始文件名,防止冲突String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();// 调用 MinIO 客户端上传文件minioClient.putObject(// 配置上传参数PutObjectArgs.builder()// 指定存储桶.bucket(bucketName)// 指定对象名(文件名).object(fileName)// 设置文件流、大小和分片阈值(-1 表示自动分片).stream(file.getInputStream(), file.getSize(), -1)// 设置文件 MIME 类型.contentType(file.getContentType()).build());// 返回上传后的文件名return fileName;}// 从 MinIO 下载文件,返回输入流public InputStream downloadFile(String fileName) throws Exception {// 调用 MinIO 客户端获取文件流return minioClient.getObject(// 配置下载参数GetObjectArgs.builder()// 指定存储桶.bucket(bucketName)// 指定对象名(文件名).object(fileName).build());}// 从 MinIO 删除指定文件public void deleteFile(String fileName) throws Exception {// 调用 MinIO 客户端删除文件minioClient.removeObject(// 配置删除参数RemoveObjectArgs.builder()// 指定存储桶.bucket(bucketName)// 指定对象名(文件名).object(fileName).build());}// 列出存储桶中的所有文件名public List<String> listFiles() throws Exception {// 创建文件名列表List<String> fileNames = new ArrayList<>();// 调用 MinIO 客户端列出存储桶中的对象Iterable<Result<Item>> results = minioClient.listObjects(// 配置列表参数ListObjectsArgs.builder().bucket(bucketName).build());// 迭代对象列表,提取文件名for (Result<Item> result : results) {fileNames.add(result.get().objectName());}// 返回文件名列表return fileNames;}
}

错误排查总结

以下是常见错误及其解决方法的汇总:

错误可能原因解决方法
mkdir : Access is denied权限不足以管理员身份运行 PowerShell。
Invoke-WebRequest : 404 Not Found镜像源失效使用官方源或检查 URL。
setx : Access is denied非管理员运行以管理员身份运行 PowerShell。
sc.exe : CreateService FAILED 1053binPath 错误验证路径和文件存在,尝试手动运行命令。
sc start : StartService FAILED 1057服务账户无效设置服务账户为 LocalSystem。
无法访问 http://localhost:9001服务未启动/端口被占检查服务状态、端口占用,开放防火墙规则。
登录失败用户名/密码错误验证环境变量,重新设置密码并重启服务。
mc: Unable to initialize alias服务不可达确认 MinIO 运行,端口 9000 可访问。

完整验证流程

  1. 检查目录和文件

    Test-Path E:\youkeProject\Minio\minio.exe Test-Path E:\youkeProject\Minio\data

  2. 检查环境变量

    [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_USER", "Machine") [System.Environment]::GetEnvironmentVariable("MINIO_ROOT_PASSWORD", "Machine")

  3. 检查服务状态

    Get-Service MinIO | Select-Object Name, Status, StartType

  4. 访问 Web 界面
    • 浏览器打开 http://localhost:9001,登录。
  5. 使用 mc 客户端

    mc alias set myminio http://localhost:9000 admin P@ssw0rd123 mc ls myminio


注意事项

  • 管理员权限:所有命令需在管理员权限的 PowerShell 中运行。
  • 密码安全:MINIO_ROOT_PASSWORD 需满足 ≥8 位,包含大小写、数字和符号,避免弱密码(如 password123)。
  • 端口冲突:检查 9000 和 9001 端口是否被占用,若冲突可修改为其他端口(如 9002)。
  • 日志查看
    • MinIO 默认日志可能在 E:\youkeProject\Minio\logs\minio.log:

      Get-Content -Path "E:\youkeProject\Minio\logs\minio.log" -Tail 10

    • 检查 Windows 事件日志(如果配置了事件日志输出):

      Get-WinEvent -LogName System -MaxEvents 100 | Where-Object { $_.ProviderName -like "*minio*" } | Format-List

  • 备份数据:定期备份 E:\youkeProject\Minio\data 中的数据,避免意外丢失。

总结

通过以上步骤,您可以在 Windows 系统上成功安装并启动 MinIO 作为服务,数据存储在 E:\youkeProject\Minio\data,Web 控制台通过 http://localhost:9001 访问。

3.系统实现

3.1文件上传代码实现

3.1.1 方式一:前端上传

前端代码(Vue 组件):

  • 文件:InfraFile.vue 和头像上传组件(未命名,假设为 AvatarUpload.vue)。
  • 功能
    • 文件列表展示:显示文件列表(el-table),支持搜索(路径、创建时间)、分页、删除。
    • 文件上传:通过 <el-upload> 组件实现文件上传,支持拖拽、类型限制(.jpg, .png, .gif)。
    • 头像上传:使用 vue-cropper 裁剪图片后上传。
  • 关键代码
    • 文件上传
      <el-uploadref="upload":limit="1"accept=".jpg, .png, .gif":auto-upload="false":headers="upload.headers":action="upload.url":data="upload.data":on-change="handleFileChange":on-progress="handleFileUploadProgress":on-success="handleFileSuccess"><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或 <em>点击上传</em></div><div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入 jpg、png、gif 格式文件!</div>
      </el-upload>
      • 作用:用户选择文件后,点击“确定”按钮触发上传,发送 POST /admin-api/infra/file/upload 请求。
      • 配置
        • action:上传接口 URL(/admin-api/infra/file/upload)。
        • headers:携带认证 token(Authorization: Bearer xxx)。
        • data:附加参数(如路径)。
        • on-success:处理上传成功的响应,显示 URL 并刷新列表。
    • 头像上传
      <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload"><el-button size="small">选择<i class="el-icon-upload el-icon--right"></i></el-button>
      </el-upload>
      • 作用:选择图片后裁剪,上传到 /admin-api/system/user/avatar。
      • 配置
        • http-request:自定义上传逻辑,调用 uploadAvatar API。
        • before-upload:验证文件类型(必须为图片)。
        • uploadImg:将裁剪后的图片作为 FormData 上传。
  • 后端代码(FileController.java):
    @PostMapping("/upload")
    @Operation(summary = "上传文件")
    public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {MultipartFile file = uploadReqVO.getFile();String path = uploadReqVO.getPath();return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
    }
    • 作用:接收前端上传的 MultipartFile 和路径参数,调用 fileService.createFile 存储文件,返回文件 URL。
    • 流程
      1. 解析 uploadReqVO 获取文件和路径。
      2. 使用 IoUtil.readBytes 转换文件流为字节数组。
      3. 调用 fileService.createFile 上传文件到 MinIO,返回 URL。
      4. 封装响应为 CommonResult<String>。
      5. 方法详解
  • 文件上传:createFile 方法实现文件的上传,接收文件名、路径和内容,上传到存储器并保存元数据到数据库,返回文件访问 URL。
  • 文件客户端管理
    • getMasterFileClient:获取主文件客户端(FileClient),从缓存中加载。
    • clientCache:缓存文件客户端,支持异步刷新,减少数据库查询。
  • 文件客户端工厂
    • FileClientFactoryImpl:管理文件客户端的创建和更新,基于配置动态生成客户端实例(如 S3FileClient、LocalFileClient)。
    • 支持多种存储器,通过 FileStorageEnum 映射存储类型到具体客户端类。

3.1.2调用关系

  1. 外部调用:createFile 方法由上层(如 FileController 或其他服务)调用,用于上传文件。
  2. 内部调用
    • createFile 调用 fileConfigService.getMasterFileClient 获取客户端。
    • getMasterFileClient 通过 clientCache 获取缓存的 FileClient。
    • clientCache 的 load 方法调用 fileConfigService 和 fileClientFactory 创建或更新客户端。
    • FileClientFactoryImpl 的 createOrUpdateFileClient 和 getFileClient 方法管理客户端实例。
  3. 依赖
    • FileConfigService:提供存储配置查询。
    • FileClientFactory:创建和管理文件客户端。
    • FileMapper:操作数据库,保存文件元数据。

3.1.3主要类和接口

  • FileServiceImpl:文件服务实现类,包含 createFile 和 getMasterFileClient。
  • FileClient:文件客户端接口,定义上传、删除、获取内容等方法。
  • FileClientFactoryImpl:文件客户端工厂,动态创建客户端。
  • AbstractFileClient:抽象文件客户端,提供通用逻辑。
  • FileConfigDO:文件配置实体,存储存储器配置。
  • FileDO:文件元数据实体,存储文件名、路径、URL 等。

3.1.4代码逐行分析与调用链

3.1.4.1 FileServiceImpl.createFile

@Override @SneakyThrows public String createFile(String name, String path, byte[] content) {

  • 作用:上传文件到存储器,保存元数据到数据库,返回文件 URL。
  • 入参
    • name:文件名(如 image.jpg)。
    • path:存储路径(如 /avatars/image.jpg)。
    • content:文件内容(字节数组)。
  • 返回值:String,文件访问 URL(如 http://minio.example.com/mybucket/avatars/image.jpg)。
  • 注解
    • @Override:实现 FileService 接口的 createFile 方法。
    • @SneakyThrows:Lombok 注解,简化异常处理,抛出 Exception。
  • 调用者:FileController(前端上传)、其他服务(如后端上传)。
  • 调用链:FileController.uploadFile -> FileServiceImpl.createFile。

String type = FileTypeUtils.getMineType(content, name);

  • 作用:获取文件的 MIME 类型(如 image/jpeg)。
  • 逻辑:使用 FileTypeUtils(可能是 Hutool 或自定义工具)分析文件内容和名称。
  • 调用:FileTypeUtils.getMineType(静态方法)。
  • 示例:name="image.jpg", content 为 JPEG 数据,返回 image/jpeg。

if (StrUtil.isEmpty(path)) { path = FileUtils.generatePath(content, name); }

  • 作用:如果 path 为空,生成默认存储路径。
  • 逻辑
    • StrUtil.isEmpty:Hutool 工具,检查 path 是否为空。
    • FileUtils.generatePath:生成路径,可能基于文件名或日期(如 /2025/05/image.jpg)。
  • 调用:FileUtils.generatePath(静态方法)。
  • 示例:name="image.jpg", 返回 /2025/05/image.jpg。

if (StrUtil.isEmpty(name)) { name = path; }

  • 作用:如果 name 为空,使用 path 作为文件名。
  • 逻辑:确保文件名有效,避免空值。
  • 示例:name="", path="/avatars/image.jpg", 设置 name="/avatars/image.jpg"。

FileClient client = fileConfigService.getMasterFileClient();

  • 作用:获取主文件客户端(FileClient),用于上传文件。
  • 调用:FileConfigService.getMasterFileClient,实际调用 FileServiceImpl.getMasterFileClient。
  • 返回值:FileClient 实例(如 S3FileClient)。
  • 调用链:createFile -> getMasterFileClient -> clientCache.getUnchecked。

Assert.notNull(client, "客户端(master) 不能为空");

  • 作用:校验客户端是否为空,若为空抛出异常。
  • 逻辑:Spring 的 Assert 工具,确保 client 有效。
  • 示例:若 client=null,抛出 IllegalArgumentException: 客户端(master) 不能为空。

String url = client.upload(content, path, type);

  • 作用:调用文件客户端上传文件,返回文件 URL。
  • 调用:FileClient.upload,由具体实现(如 S3FileClient)处理。
  • 入参
    • content:文件内容。
    • path:存储路径。
    • type:MIME 类型。
  • 返回值:文件 URL。
  • 示例:上传 image.jpg,返回 http://minio.example.com/mybucket/avatars/image.jpg。

FileDO file = new FileDO();

  • 作用:创建文件元数据实体,准备保存到数据库。
  • 逻辑:FileDO 是文件表对应的实体类,包含配置 ID、名称、路径等字段。

file.setConfigId(client.getId());

  • 作用:设置文件配置 ID。
  • 调用:FileClient.getId,返回客户端的配置 ID。
  • 示例:client.getId() 返回 1(主配置 ID)。

file.setName(name); file.setPath(path); file.setUrl(url); file.setType(type); file.setSize(content.length);

  • 作用:设置文件元数据,包括文件名、路径、URL、类型和大小。
  • 示例
    • name="image.jpg"
    • path="/avatars/image.jpg"
    • url="http://minio.example.com/mybucket/avatars/image.jpg"
    • type="image/jpeg"
    • size=102400(100KB)

fileMapper.insert(file);

  • 作用:将文件元数据插入数据库。
  • 调用:FileMapper.insert,MyBatis 的 Mapper 方法。
  • 逻辑:保存 FileDO 到 infra_file 表。
  • 示例:插入记录,生成自增 ID。

return url;

  • 作用:返回文件 URL,供调用者使用。
  • 示例:返回 http://minio.example.com/mybucket/avatars/image.jpg。
3.1.4.2 FileServiceImpl.getMasterFileClient

@Override public FileClient getMasterFileClient() {

  • 作用:获取主文件客户端,从缓存中加载。
  • 返回值:FileClient 实例。
  • 调用者:createFile 方法。
  • 调用链:createFile -> getMasterFileClient -> clientCache.getUnchecked。

return clientCache.getUnchecked(CACHE_MASTER_ID);

  • 作用:从缓存获取主客户端,CACHE_MASTER_ID 表示主配置 ID(通常为固定值,如 0)。
  • 调用:LoadingCache.getUnchecked,Guava 缓存方法。
  • 逻辑:若缓存命中,直接返回;若未命中,调用缓存的 load 方法。
  • 示例:返回 S3FileClient 实例。
3.1.4.3 FileServiceImpl.clientCache

@Getter private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),

  • 作用:定义文件客户端缓存,支持异步刷新。
  • 字段
    • clientCache:Guava 的 LoadingCache,键为配置 ID,值为 FileClient。
    • Duration.ofSeconds(10L):缓存刷新间隔为 10 秒。
  • 逻辑:缓存避免频繁查询数据库或创建客户端。
  • 调用:buildAsyncReloadingCache
  • 调用者:getMasterFileClient

new CacheLoader<Long, FileClient>() {

  • 作用:定义缓存加载器,当缓存未命中时加载 FileClient。
  • 逻辑:实现 load 方法,动态创建客户端。

@Override public FileClient load(Long id) {

  • 作用:加载指定 ID 的文件客户端。
  • 入参:id,配置 ID(CACHE_MASTER_ID 表示主配置)。
  • 返回值:FileClient 实例。
  • 调用者:clientCache.getUnchecked。

FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ? fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);

  • 作用:查询存储配置。
  • 逻辑
    • 若 id 是 CACHE_MASTER_ID,调用 fileConfigMapper.selectByMaster 获取主配置。
    • 否则,调用 fileConfigMapper.selectById 获取指定 ID 的配置。
  • 调用
    • fileConfigMapper.selectByMaster:MyBatis 查询主配置。
    • fileConfigMapper.selectById:MyBatis 查询指定配置。
  • 示例:返回 FileConfigDO(包含 endpoint、bucket 等)。

if (config != null) { fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig()); }

  • 作用:若配置存在,创建或更新文件客户端。
  • 调用:FileClientFactory.createOrUpdateFileClient。
  • 入参
    • config.getId():配置 ID。
    • config.getStorage():存储类型(如 S3、本地)。
    • config.getConfig():存储配置(如 MinIO 的 endpoint、accessKey)。
  • 示例:创建 S3FileClient。

return fileClientFactory.getFileClient(null == config ? id : config.getId());

  • 作用:获取文件客户端。
  • 调用:FileClientFactory.getFileClient。
  • 逻辑
    • 若 config 为空,使用原始 id。
    • 否则,使用 config.getId()。
  • 示例:返回 S3FileClient。
3.1.4.4 FileClientFactoryImpl

@Slf4j public class FileClientFactoryImpl implements FileClientFactory {

  • 作用:文件客户端工厂实现类,管理客户端的创建和更新。
  • 注解
    • @Slf4j:Lombok 注解,提供日志记录器(log)。
  • 调用者:clientCache.load。

private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>();

  • 作用:存储文件客户端实例,键为配置 ID,值为客户端。
  • 逻辑:ConcurrentHashMap 确保线程安全。

@Override public FileClient getFileClient(Long configId) {

  • 作用:获取指定配置 ID 的文件客户端。
  • 入参:configId,配置 ID。
  • 返回值:FileClient 实例。
  • 调用者:clientCache.load。

AbstractFileClient<?> client = clients.get(configId);

  • 作用:从 clients 映射获取客户端。
  • 示例:configId=1,返回 S3FileClient。

if (client == null) { log.error("[getFileClient][配置编号({}) 找不到客户端]", configId); }

  • 作用:若客户端不存在,记录错误日志。
  • 示例:configId=999,日志输出 [getFileClient][配置编号(999) 找不到客户端]。

return client;

  • 作用:返回客户端实例(可能为 null)。
  • 示例:返回 S3FileClient。

@Override @SuppressWarnings("unchecked") public <Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config) {

  • 作用:创建或更新文件客户端。
  • 入参
    • configId:配置 ID。
    • storage:存储类型(如 S3、本地)。
    • config:存储配置(泛型,子类如 S3FileClientConfig)。
  • 调用者:clientCache.load。
  • 注解:@SuppressWarnings("unchecked") 抑制类型转换警告。

AbstractFileClient<Config> client = (AbstractFileClient<Config>) clients.get(configId);

  • 作用:尝试从 clients 获取现有客户端。
  • 逻辑:强制类型转换为 AbstractFileClient<Config>。

if (client == null) { client = this.createFileClient(configId, storage, config); client.init(); clients.put(client.getId(), client);

  • 作用:若客户端不存在,创建新客户端。
  • 逻辑
    • 调用 createFileClient 创建客户端。
    • 调用 client.init 初始化客户端(如连接 MinIO)。
    • 将客户端存入 clients 映射。
  • 调用
    • createFileClient
    • AbstractFileClient.init

} else { client.refresh(config); }

  • 作用:若客户端存在,刷新配置。
  • 调用:AbstractFileClient.refresh。
  • 示例:更新 MinIO 的 endpoint 或 accessKey。

@SuppressWarnings("unchecked") private <Config extends FileClientConfig> AbstractFileClient<Config> createFileClient( Long configId, Integer storage, Config config) {

  • 作用:创建文件客户端实例。
  • 入参
    • configId:配置 ID。
    • storage:存储类型。
    • config:存储配置。
  • 返回值:AbstractFileClient<Config>。
  • 调用者:createOrUpdateFileClient。

FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage);

  • 作用:根据存储类型获取枚举值。
  • 调用:FileStorageEnum.getByStorage(静态方法)。
  • 示例:storage=1,返回 FileStorageEnum.S3。

Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum));

  • 作用:校验存储类型是否有效。
  • 示例:若 storageEnum=null,抛出 IllegalArgumentException: 文件配置(null) 为空。

return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config);

  • 作用:创建客户端实例。
  • 调用
    • FileStorageEnum.getClientClass:获取客户端类(如 S3FileClient.class)。
    • ReflectUtil.newInstance:Hutool 工具,通过反射创建实例。
  • 逻辑:调用客户端构造函数,传入 configId 和 config。
  • 示例:storageEnum=S3,创建 S3FileClient。
3.1.4.5 整体调用流程
  1. 外部调用
    • FileController.uploadFile 调用 FileServiceImpl.createFile,传递文件名、路径和内容。
  2. 文件上传(createFile)
    • 计算 MIME 类型(FileTypeUtils.getMineType)。
    • 生成默认路径(FileUtils.generatePath)。
    • 获取主客户端(getMasterFileClient)。
    • 上传文件(FileClient.upload)。
    • 保存元数据(FileMapper.insert)。
    • 返回 URL。
  3. 获取客户端(getMasterFileClient)
    • 从 clientCache 获取客户端(clientCache.getUnchecked)。
  4. 缓存加载(clientCache.load)
    • 查询配置(fileConfigMapper.selectByMaster 或 selectById)。
    • 创建或更新客户端(FileClientFactory.createOrUpdateFileClient)。
    • 返回客户端(FileClientFactory.getFileClient)。
  5. 客户端工厂(FileClientFactoryImpl)
    • getFileClient:从 clients 获取客户端。
    • createOrUpdateFileClient:
      • 若客户端不存在,调用 createFileClient 创建。
      • 若存在,调用 refresh 更新。
    • createFileClient:通过反射创建客户端实例(如 S3FileClient)。

3.1.4.6 每一步作用总结

代码部分作用调用关系
createFile上传文件,保存元数据,返回 URL调用 getMasterFileClient, FileClient.upload, FileMapper.insert
getMasterFileClient获取主文件客户端调用 clientCache.getUnchecked
clientCache缓存文件客户端,支持异步刷新调用 load(查询配置,创建客户端)
clientCache.load加载客户端,查询配置并创建调用 fileConfigMapper, fileClientFactory.createOrUpdateFileClient
FileClientFactoryImpl.getFileClient获取客户端实例从 clients 获取
createOrUpdateFileClient创建或更新客户端调用 createFileClient, AbstractFileClient.init/refresh
createFileClient通过反射创建客户端调用 FileStorageEnum, Refl
  • 前端主导:用户通过浏览器界面选择文件,触发 HTTP 请求,上传过程由前端组件控制(<el-upload>)。
  • 后端辅助:后端仅负责接收文件、存储到 MinIO、返回 URL,不主动发起上传。
  • 特点:上传流程由前端用户交互驱动,适合需要用户选择文件的场景(如上传头像、文档)。

3.1.5方式二:后端上传

后端代码:

  • 文件:FileApiImpl.java 和 FileServiceImpl.java。
  • 功能
    • 提供文件管理 API,包括创建、删除、查询文件内容和生成签名 URL。
    • 支持直接通过字节数组上传文件(无需 MultipartFile)。
  • 关键代码
    • FileApiImpl
      /*** 文件 API 实现类*/
      @Service
      @Validated
      public class FileApiImpl implements FileApi {@Resourceprivate FileService fileService;@Overridepublic String createFile(String name, String path, byte[] content) {return fileService.createFile(name, path, content);}}
      • 作用:实现 FileApi 接口,提供 createFile 方法,接收文件名、路径和字节数组,调用 fileService 上传文件。
    • FileServiceImpl
      @Override@SneakyThrowspublic String createFile(String name, String path, byte[] content) {// 计算默认的 path 名String type = FileTypeUtils.getMineType(content, name);if (StrUtil.isEmpty(path)) {path = FileUtils.generatePath(content, name);}// 如果 name 为空,则使用 path 填充if (StrUtil.isEmpty(name)) {name = path;}// 上传到文件存储器FileClient client = fileConfigService.getMasterFileClient();Assert.notNull(client, "客户端(master) 不能为空");String url = client.upload(content, path, type);// 保存到数据库FileDO file = new FileDO();file.setConfigId(client.getId());file.setName(name);file.setPath(path);file.setUrl(url);file.setType(type);file.setSize(content.length);fileMapper.insert(file);return url;}
      • 作用:处理文件上传逻辑,保存文件到 MinIO,记录元数据到数据库,返回 URL。
      • 流程
        1. 推断文件类型(FileTypeUtils.getMineType)。
        2. 生成默认路径(如果未提供)。
        3. 获取 MinIO 客户端(FileClient),上传文件(client.upload)。
        4. 保存文件元数据(FileDO)到数据库。
        5. 返回文件 URL。

解析

  • 后端主导:上传由后端代码或服务调用触发,文件内容以字节数组形式传入,无需用户通过浏览器选择文件。
  • 特点:适合服务器端批量处理、自动上传或从其他来源(如本地文件、URL 下载)获取文件的场景。
  • 无前端交互:不依赖浏览器,直接由后端 API 或服务调用。

3.1.6. 回答问题

前端上传和后端上传的区别
  1. 触发方式
    • 前端上传:由用户通过浏览器界面(<el-upload>、裁剪组件)选择文件,触发 HTTP 请求。
      • 场景:用户上传头像、文档、图片等。
      • 示例:用户点击“上传文件”按钮,发送 POST /admin-api/infra/file/upload。
    • 后端上传:由后端代码或服务调用触发,文件内容以字节数组形式传入,通常从服务器本地、数据库或其他来源获取。
      • 场景:批量导入文件、服务器端文件处理、从 URL 下载并上传。
      • 示例:后端从本地磁盘读取文件,调用 fileService.createFile。
  2. 文件来源
    • 前端上传:文件来自用户设备(通过浏览器选择)。
      • 示例:用户选择 C:\test.jpg。
    • 后端上传:文件来自服务器环境(如本地文件、远程 URL、数据库)。
      • 示例:后端读取 /tmp/test.jpg 或从 URL 下载文件。
  3. 请求格式
    • 前端上传:使用 multipart/form-data 格式,文件通过 MultipartFile 传输。
      • 示例:form-data: file=(binary), path=/avatars/.
    • 后端上传:文件以字节数组(byte[])传入,通常通过内部方法调用或 API(如 FileApi.createFile)。
      • 示例:createFile("test.jpg", "/avatars/", fileBytes)。
  4. 用户交互
    • 前端上传:需要用户交互(如选择文件、点击上传),前端提供界面和反馈(如进度条、成功提示)。
      • 示例:<el-upload> 显示上传进度,成功后弹出 上传成功。
    • 后端上传:无用户交互,后端自动处理,适合后台任务。
      • 示例:定时任务批量上传文件。
  5. 适用场景
    • 前端上传:用户驱动的场景,如个人中心上传头像、文件管理系统上传文档。
    • 后端上传:系统驱动的场景,如服务器迁移文件、API 集成、自动化脚本。

命名原因

  • 前端上传:被称为“前端上传”,因为上传流程由前端用户交互发起,文件通过前端组件(如 <el-upload>)选择并发送到后端。后端仅处理请求,核心动作(文件选择、触发上传)发生在前端。
  • 后端上传:被称为“后端上传”,因为上传由后端代码或服务主动调用,文件来源和上传逻辑完全由后端控制,无需前端参与。核心动作(文件读取、上传)发生在后端。

3.2 文件下载

// 标记为 Spring REST 控制器,返回 JSON 或文件流响应
@RestController
// 设置日志记录器
@Slf4j
public class FileController {// 处理文件下载请求,GET /admin-api/infra/file/{configId}/get/**@GetMapping("/{configId}/get/**")// 允许未认证用户访问@PermitAll// Swagger 文档:接口描述@Operation(summary = "下载文件")// Swagger 文档:参数描述@Parameter(name = "configId", description = "配置编号", required = true)// 接收请求、响应和配置 ID,处理文件下载public void getFileContent(HttpServletRequest request,HttpServletResponse response,@PathVariable("configId") Long configId) throws Exception {// 从请求 URI 中提取文件路径(/get/ 之后的部分)String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);// 校验路径是否为空,若为空抛出异常if (StrUtil.isEmpty(path)) {throw new IllegalArgumentException("结尾的 path 路径必须传递");}// 解码路径,解决中文路径的编码问题path = URLUtil.decode(path);// 调用文件服务获取文件内容byte[] content = fileService.getFileContent(configId, path);// 若文件不存在,记录警告日志并返回 404 状态if (content == null) {log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);response.setStatus(HttpStatus.NOT_FOUND.value());return;}// 返回文件内容作为附件FileTypeUtils.writeAttachment(response, path, content);}
}// Hutool 的 StrUtil 工具类,提供字符串操作
class StrUtil {// 从字符串中提取指定分隔符后的子串public static String subAfter(CharSequence string, CharSequence separator, boolean isLastSeparator) {// 若输入字符串为空,返回空或 nullif (isEmpty(string)) {return null == string ? null : "";} else if (separator == null) {// 若分隔符为空,返回空字符串return "";} else {// 转换为字符串String str = string.toString();String sep = separator.toString();// 根据 isLastSeparator 决定使用最后一个或第一个分隔符int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);// 若找到分隔符且不是字符串末尾,返回分隔符后的子串return -1 != pos && string.length() - 1 != pos ? str.substring(pos + separator.length()) : "";}}// 检查字符串是否为空(Hutool 工具方法)public static boolean isEmpty(CharSequence str) {return str == null || str.length() == 0;}
}// 文件服务接口实现类
class FileServiceImpl {// 获取文件内容@Overridepublic byte[] getFileContent(Long configId, String path) throws Exception {// 获取指定配置 ID 的文件客户端FileClient client = fileConfigService.getFileClient(configId);// 校验客户端是否存在Assert.notNull(client, "客户端({}) 不能为空", configId);// 调用客户端获取文件内容return client.getContent(path);}
}// Servlet 工具类,提供响应处理方法
class ServletUtils {// 将文件内容作为附件写入响应public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {// 设置 Content-Disposition 头,指定文件名(UTF-8 编码)response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtil.encodeUtf8(filename));// 获取文件 MIME 类型String contentType = FileTypeUtils.getMineType(content, filename);// 设置响应内容类型response.setContentType(contentType);// 针对视频文件的特殊处理,解决移动端播放兼容性if (StrUtil.containsIgnoreCase(contentType, "video")) {response.setHeader("Content-Length", String.valueOf(content.length - 1));response.setHeader("Content-Range", String.valueOf(content.length - 1));response.setHeader("Accept-Ranges", "bytes");}// 将文件内容写入响应输出流IoUtil.write(response.getOutputStream(), false, content);}
}

3.3 文件客户端

// 定义文件客户端接口,抽象文件操作方法,支持多种存储器(如 S3、本地磁盘、数据库等)
public interface FileClient {/*** 获取客户端编号** @return 客户端编号,用于标识存储配置(如 MinIO、S3 的配置 ID)*/Long getId();/*** 上传文件到存储器** @param content 文件内容,字节数组形式* @param path 相对路径,指定文件在存储器中的位置(如 "/avatars/image.jpg")* @return 完整路径,即文件的 HTTP 访问地址(如 "http://minio.example.com/mybucket/avatars/image.jpg")*/String upload(byte[] content, String path);/*** 从存储器删除指定文件** @param path 相对路径,指定要删除的文件位置(如 "/avatars/image.jpg")*/void delete(String path);/*** 获取存储器中指定文件的内容** @param path 相对路径,指定要读取的文件位置(如 "/avatars/image.jpg")* @return 文件内容,字节数组形式,若文件不存在可能返回 null 或抛出异常*/byte[] getContent(String path);}

4.前端直传S3存储

1. S3 存储和前端直传 S3 的解释

1.1 什么是 S3 存储?

  • 定义:S3(Simple Storage Service)是 Amazon 提供的一种对象存储服务,广泛用于存储文件(如图片、视频、文档)。七牛云、阿里云 OSS、腾讯云 COS 等提供了 S3 兼容的存储服务,允许使用类似 AWS S3 的 API 操作文件。
  • 核心概念
    • Bucket:存储文件的容器,类似文件夹,名称全局唯一。
    • Object:存储的文件,每个对象有唯一的 Key(路径,如 avatars/image.jpg)。
    • URL 访问:文件通过 HTTP URL 访问(如 http://bucket.qiniucs.com/avatars/image.jpg)。
  • 特点
    • 高可用性:数据多副本存储,耐久性达 99.999999999%。
    • 可扩展性:支持无限存储容量,适合大文件和海量数据。
    • 安全性:通过访问密钥(Access Key/Secret Key)和权限策略控制访问。
  • 适用场景:用户头像上传、视频存储、静态网站托管、数据备份等。

1.2 什么是前端直传 S3?

  • 定义:前端直传 S3 是指前端(浏览器或客户端)直接将文件上传到 S3 存储(如七牛云),而不通过后端服务器中转。相比传统方式(前端 → 后端 → S3),它减少了后端带宽压力。
  • 传统上传(前端 → 后端 → S3)
    • 流程:前端将文件发送到后端,后端再上传到 S3。
    • 问题:文件流量经过后端,若后端带宽有限(如 1MB/s),上传大文件(如 10MB)会很慢(需 10 秒),多用户上传可能导致带宽瓶颈。
  • 前端直传(前端 → S3)
    • 流程:
      1. 前端向后端请求预签名 URL(Presigned URL),包含临时访问权限。
      2. 前端使用预签名 URL 直接上传文件到 S3。
      3. 上传成功后,通知后端记录文件信息(如 URL、路径)。
    • 优势:
      • 速度快:文件直接上传到 S3,利用用户带宽(如 100MB/s,10MB 文件只需 0.1 秒)。
      • 减轻后端压力:后端仅处理轻量请求(如生成预签名 URL),无需传输大文件。
      • 高并发:S3 天然支持高并发上传,适合多用户场景。
  • 适用场景:大文件上传(如视频、图片)、高并发上传(如社交平台用户上传头像)。

1.3 七牛云 S3 存储的特点

  • 七牛云提供 S3 兼容的对象存储服务,支持 AWS S3 的 API 和 SDK。
  • 需要配置:
    • Endpoint:存储服务地址(如 s3-cn-south-1.qiniucs.com)。
    • Bucket:存储桶名称。
    • Access Key/Secret Key:用于认证的密钥。
    • Domain:自定义访问域名(如 http://bucket.qiniucs.com)。
  • 七牛云要求配置自定义域名(domain),否则无法生成可访问的 URL。

2. 代码分析

2.1 前端代码(yudao-ui-admin-vue3)

前端代码基于 Vue3 和 Element Plus,使用 ElUpload 组件实现文件上传,支持两种模式:前端直传(client)和后端上传(server)。

关键代码:useUpload 方法
export const useUpload = () => {const uploadUrl = getUploadUrl() // 获取上传 URLconst isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE // 判断是否前端直传// 重写 ElUpload 的上传方法const httpRequest = async (options: UploadRequestOptions) => {if (isClientUpload) {// 模式一:前端直传// 1. 生成唯一文件名(基于 SHA256)const fileName = await generateFileName(options.file)// 2. 请求后端获取预签名 URLconst presignedInfo = await FileApi.getFilePresignedUrl(fileName)// 3. 直接上传文件到 S3return axios.put(presignedInfo.uploadUrl, options.file, {headers: { 'Content-Type': options.file.type }}).then(() => {// 4. 异步记录文件信息到后端createFile(presignedInfo, fileName, options.file)// 返回与后端上传一致的格式return { data: presignedInfo.url }})} else {// 模式二:后端上传return new Promise((resolve, reject) => {FileApi.updateFile({ file: options.file }).then((res) => {if (res.code === 0) resolve(res)else reject(res)}).catch((res) => reject(res))})}}return { uploadUrl, httpRequest }
}
  • 逻辑
    1. 检查模式:通过 VITE_UPLOAD_TYPE 判断是否为 client 模式。
    2. 生成文件名:使用 generateFileName 计算文件的 SHA256 哈希值,拼接后缀(如 .jpg),生成唯一文件名。
    3. 获取预签名 URL:调用后端 /presigned-url 接口,获取上传用的临时 URL。
    4. 上传文件:使用 axios.put 直接将文件上传到预签名 URL,设置 Content-Type 为文件类型。
    5. 记录文件:上传成功后,调用 createFile 通知后端保存文件信息(如 URL、路径)。
  • 注意
    • 不使用 FormData 上传,因为 MinIO(或七牛云)不支持 multipart/form-data 格式。
    • 返回格式与后端上传一致,确保 ElUpload 组件兼容。
生成文件名:generateFileName
async function generateFileName(file: UploadRawFile) {const data = await file.arrayBuffer() // 读取文件内容const wordArray = CryptoJS.lib.WordArray.create(data) // 转换为 CryptoJS 格式const sha256 = CryptoJS.SHA256(wordArray).toString() // 计算 SHA256const ext = file.name.substring(file.name.lastIndexOf('.')) // 获取文件后缀return `${sha256}${ext}` // 返回唯一文件名
}
  • 作用:基于文件内容的 SHA256 哈希生成唯一文件名,避免文件名冲突。
  • 优点:即使文件名相同,内容不同也会生成不同文件名,确保文件不被覆盖。

记录文件信息:createFile

function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {const fileVo = {configId: vo.configId, // 存储配置 IDurl: vo.url, // 文件访问 URLpath: name, // 文件路径name: file.name, // 原始文件名type: file.type, // 文件类型size: file.size // 文件大小}FileApi.createFile(fileVo) // 调用后端保存文件信息return fileVo
}
  • 作用:将文件信息(如 URL、路径、大小)发送到后端,保存到数据库,便于后续管理。

2.2 后端代码

后端基于 Spring Boot,提供预签名 URL 和文件信息保存接口,支持七牛云等 S3 兼容存储。

获取预签名 URL:FileController.getFilePresignedUrl
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {return success(fileService.getFilePresignedUrl(path));
}
  • 作用:接收前端请求的文件路径,返回预签名 URL 和相关信息。
  • 输入:path(如 avatars/abc123.jpg)。
  • 输出:FilePresignedUrlRespVO(包含 uploadUrl、url、configId)。

文件服务:FileServiceImpl.getFilePresignedUrl

  • 逻辑
    1. 获取默认 FileClient(如七牛云的 S3 客户端)。
    2. 调用 fileClient.getPresignedObjectUrl 生成预签名 URL。
    3. 返回包含 uploadUrl(上传地址)、url(访问地址)和 configId 的对象。
S3 配置类:S3FileClientConfig
@Data
public class S3FileClientConfig implements FileClientConfig {public static final String ENDPOINT_QINIU = "qiniucs.com";@NotNull(message = "endpoint 不能为空") private String endpoint; // 节点地址@URL(message = "domain 必须是 URL 格式") private String domain; // 自定义域名@NotNull(message = "bucket 不能为空") private String bucket; // 存储桶@NotNull(message = "accessKey 不能为空") private String accessKey; // 访问密钥@NotNull(message = "accessSecret 不能为空") private String accessSecret; // 秘密密钥@AssertTrue(message = "domain 不能为空")@JsonIgnorepublic boolean isDomainValid() {if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {return false; // 七牛云必须配置 domain}return true;}
}
  • 作用:定义 S3 存储的配置参数,支持七牛云、阿里云等。
  • 字段
    • endpoint:存储服务地址(如 s3-cn-south-1.qiniucs.com)。
    • domain:访问域名(如 http://bucket.qiniucs.com)。
    • bucket:存储桶名称。
    • accessKey/accessSecret:认证密钥。
  • 校验:七牛云要求 domain 必填。

2.3 七牛云 S3 客户端(假设实现)

虽然您未提供 S3FileClient 的具体实现,但可以参考之前的 S3FileClient(基于 MinIO SDK)。七牛云的实现类似,使用 AWS SDK 或七牛云 SDK 生成预签名 URL。

public class S3FileClient extends AbstractFileClient<S3FileClientConfig> implements FileClient {private final AmazonS3 s3Client;public S3FileClient(Long configId, S3FileClientConfig config) {super(configId, config);s3Client = AmazonS3ClientBuilder.standard().withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(), "auto")).withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(config.getAccessKey(), config.getSecretKey()))).build();}@Overridepublic FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {// 生成预签名 URL(有效期 1 小时)GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(config.getBucket(), path).withMethod(HttpMethod.PUT).withExpiration(Date.from(Instant.now().plusSeconds(3600)));URL uploadUrl = s3Client.generatePresignedUrl(request);// 返回预签名 URL 和访问 URLreturn new FilePresignedUrlRespDTO().setUploadUrl(uploadUrl.toString()).setUrl(config.getDomain() + "/" + path);}
}

逻辑:使用 AWS SDK 生成预签名 URL,设置 HTTP 方法为 PUT,有效期 1 小时。

总结:

前端代码作用

前端基于 Vue3 和 Element Plus,使用 ElUpload 组件实现文件上传,核心代码在 useUpload 方法中。支持两种模式:前端直传(client)和后端上传(server)。以下是前端直传的逻辑和作用:

  • 配置文件(.env.local):
    • 设置 VITE_UPLOAD_TYPE=client,启用前端直传。
    • 配置后端 API 地址(VITE_BASE_URL 和 VITE_API_URL)。
  • 生成文件名(generateFileName):
    • 使用 SHA256 算法基于文件内容生成唯一文件名,防止冲突。
    • 作用:确保文件名全局唯一,避免覆盖。
  • 获取预签名 URL(FileApi.getFilePresignedUrl):
    • 调用后端 /presigned-url 接口,获取上传用的预签名 URL 和访问 URL。
    • 作用:获得 S3 存储的临时上传权限。
  • 上传文件(axios.put):
    • 使用预签名 URL 直接将文件上传到 S3,设置 Content-Type 为文件类型。
    • 作用:将文件存储到 S3,无需后端中转。
  • 记录文件信息(createFile):
    • 将文件信息(路径、URL、大小等)发送到后端,保存到数据库。
    • 作用:确保后端能跟踪和管理文件。

后端代码作用

后端基于 Spring Boot,提供预签名 URL 和文件信息保存接口,支持 S3 兼容存储(如七牛云)。核心代码在 FileController 和 FileServiceImpl 中。

  • 获取预签名 URL(FileController.getFilePresignedUrl):
    • 接收前端的路径参数,返回预签名 URL 和访问 URL。
    • 作用:为前端提供上传 S3 的临时权限。
  • 生成预签名 URL(FileServiceImpl.getFilePresignedUrl):
    • 使用默认 FileClient(如七牛云的 S3 客户端)生成预签名 URL。
    • 作用:调用 S3 客户端生成安全的上传地址。
  • S3 客户端(S3FileClient.getPresignedObjectUrl):
    • 使用 AWS SDK 或七牛云 SDK 生成预签名 URL,设置上传方法(PUT)和有效期(如 1 小时)。
    • 作用:与 S3 存储交互,生成临时访问令牌。
  • 保存文件信息(FileApi.createFile):
    • 接收前端发送的文件元数据,保存到数据库。
    • 作用:记录文件信息,便于后续查询和管理。
  • S3 配置(S3FileClientConfig):
    • 定义 S3 存储的配置参数(endpoint、bucket、accessKey 等)。
    • 作用:初始化 S3 客户端,确保连接正确。

整体流程

S3 直传通过前端直接上传文件到七牛云 S3 存储,极大提升上传效率。流程如下:

  1. 用户选择图片 avatar.jpg(100KB)。
  2. 前端生成唯一文件名(abc1234567890.jpg),请求后端预签名 URL。
  3. 后端使用七牛云 S3 客户端生成预签名 URL,返回上传地址和访问地址。
  4. 前端通过 axios.put 上传文件到七牛云。
  5. 上传成功后,前端通知后端保存文件信息到数据库。
  6. 用户获得文件 URL,可直接访问图片。

相关文章:

java每日精进 5.18【文件存储】

1.文件存储思路 支持将文件上传到三类存储器&#xff1a; 兼容 S3 协议的对象存储&#xff1a;支持 MinIO、腾讯云 COS、七牛云 Kodo、华为云 OBS、亚马逊 S3 等等。磁盘存储&#xff1a;本地、FTP 服务器、SFTP 服务器。数据库存储&#xff1a;MySQL、Oracle、PostgreSQL、S…...

LeetCode 394. 字符串解码详解:Java栈实现与逐行解析

文章目录 1. 问题描述2. 解决思路核心问题栈的应用遍历逻辑 3. 完整代码实现4. 关键代码解析处理右括号 ]处理嵌套的示例 5. 复杂度分析6. 总结 1. 问题描述 给定一个经过编码的字符串&#xff0c;要求将其解码为原始字符串。编码规则为 k[encoded_string]&#xff0c;表示方括…...

基于STC89C52的红外遥控的电子密码锁设计与实现

一、引言 电子密码锁作为一种安全便捷的门禁系统,广泛应用于家庭、办公室等场景。结合红外遥控功能,可实现远程控制开锁,提升使用灵活性。本文基于 STC89C52 单片机,设计一种兼具密码输入和红外遥控的电子密码锁系统,详细阐述硬件选型、电路连接及软件实现方案。 二、硬…...

Android 性能优化入门(一)—— 数据结构优化

1、概述 一款 app 除了要有令人惊叹的功能和令人发指交互之外&#xff0c;在性能上也应该追求丝滑的要求&#xff0c;这样才能更好地提高用户体验&#xff1a; 优化目的性能指标优化的方向更快流畅性启动速度页面显示速度(显示和切换)响应速度更稳定稳定性避免出现 应用崩溃&…...

深入理解Docker和K8S

深入理解Docker和K8S Docker 是大型架构的必备技能&#xff0c;也是云原生核心。Docker 容器化作为一种轻量级的虚拟化技术&#xff0c;其核心思想&#xff1a;将应用程序及其所有依赖项打包在一起&#xff0c;形成一个可移植的单元。 容器的本质是进程&#xff1a; 容器是在…...

5.18本日总结

一、英语 复习list3list28 二、数学 学习14讲部分内容&#xff0c;1000题13讲部分 三、408 学习计网5.3剩余内容 四、总结 计网TCP内容比较重要&#xff0c;连接过程等要时常复习&#xff1b;高数学到二重积分对定积分的计算相关方法有所遗忘&#xff0c;需要加强巩固。…...

muduo库TcpServer模块详解

Muduo库核心模块——TcpServer Muduo库的TcpServer模块是一个基于Reactor模式的高性能TCP服务端实现&#xff0c;负责管理监听端口、接受新连接、分发IO事件及处理连接生命周期。 一、核心组件与职责 Acceptor 监听指定端口&#xff0c;接受新连接&#xff0c;通过epoll监听l…...

深入理解 OpenCV 的 DNN 模块:从基础到实践

在计算机视觉领域蓬勃发展的当下&#xff0c;深度学习模型的广泛应用推动着技术的不断革新。OpenCV 作为一款强大且开源的计算机视觉库&#xff0c;其 DNN&#xff08;Deep Neural Network&#xff09;模块为深度学习模型的落地应用提供了高效便捷的解决方案。本文将以理论为核…...

MyBatis 延迟加载与缓存

一、延迟加载策略&#xff1a;按需加载&#xff0c;优化性能 1. 延迟加载 vs 立即加载&#xff1a;核心区别 立即加载&#xff1a;主查询&#xff08;如查询用户&#xff09;执行时&#xff0c;主动关联加载关联数据&#xff08;如用户的所有账号&#xff09;。 场景&#xf…...

6.2.2邻接表法-图的存储

知识总览&#xff1a; 为什么要用邻接表 因为邻接矩阵的空间复杂度高(O(n))&#xff0c;且不适合边少的稀疏图&#xff0c;所以有了邻接表 用代码表示顶点、图 声明顶点图信息 声明顶点用一维数组存储各个顶点的信息&#xff0c;一维数组字段包括2个&#xff0c;每个顶点的…...

【甲方安全建设】拉取镜像执行漏洞扫描教程

文章目录 前置知识镜像(Docker Image)是什么?镜像的 tag(标签)查看本地已有镜像的 tag查看远程仓库的所有 tag构建镜像与拉取镜像的区别正文安装docker拉取待扫描镜像安装 veinmind-runner 镜像下载 veinmind-runner 平行容器启动脚本快速扫描本地镜像/容器6. 生成 报告前…...

第四天的尝试

目录 一、每日一言 二、练习题 三、效果展示 四、下次题目 五、总结 一、每日一言 很抱歉的说一下&#xff0c;我昨天看白色巨塔电视剧&#xff0c;看的入迷了&#xff0c;同时也看出一些道理&#xff0c;学到东西&#xff1b; 但是把昨天的写事情给忘记了&#xff0c;今天…...

大数据场景下数据导出的架构演进与EasyExcel实战方案

一、引言&#xff1a;数据导出的演进驱动力 在数字化时代&#xff0c;数据导出功能已成为企业数据服务的基础能力。随着数据规模从GB级向TB级甚至PB级发展&#xff0c;传统导出方案面临三大核心挑战&#xff1a; ‌数据规模爆炸‌&#xff1a;单次导出数据量从万级到亿级的增长…...

svn: E170013 和 svn: E120171 的问题

在 Deepin23 上尝试用 svn 连接我的 Visual SVN 服务器&#xff0c;得到如下错误信息&#xff0c; > svn: E170013: Unable to connect to a repository at URL https://my.com/svn/mysource/branch_4.2.x > svn: E120171: 执行上下文错误: An error occurred during SSL…...

Limesurvay系统“48核心92GB服务器”优化方案

1、Redis maxmemory 16GB # 限制Redis内存&#xff08;预留足够空间给其他服务&#xff09; maxmemory-policy volatile-lru # 自动淘汰旧会话&#xff08;仅对带TTL的键&#xff09; save 300 100 # 仅保留一个条件减少阻塞 stop-writes-on-bgsave-error no #…...

DockerFile实战

背景 在上一篇文章中&#xff0c;我们对DockerFile有了一个较为深刻的认识&#xff0c;那么这篇文章&#xff0c;我将会向你展示如何自定义一个镜像并且在docker上运行。 一、基础指令 指令技术说明生产环境最佳实践典型错误示例​FROM​- 必须作为Dockerfile第一条指令 - 推…...

【Linux】简易版Shell实现(附源码)

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 前言 之前我们学习了Linux的进程概念以及进程控制相关接口&#xff1a; 【Linux】进程控制-CSDN博客 本篇文章&#xff0c;我们将一起踏上一段有趣的旅程&a…...

MATLAB安装常见问题解决方案

目前新版本的matlab安装往往需要十几G的本地安装容量&#xff0c;例如matlab2022b、matlab2023b, 首先就是要保证本地硬盘空间足够大&#xff0c;如果没有足够的本地内存空间&#xff0c;那么可以尝试释放本地硬盘空间&#xff0c;或者安装所需内存空间较小的旧版本的matlab&am…...

在 Vue 中插入 B 站视频

前言 在 Vue 项目中&#xff0c;有时我们需要嵌入 B 站视频来丰富页面内容&#xff0c;为用户提供更直观的信息展示。本文将详细介绍在 Vue 中插入 B 站视频的多种方法。 使用<iframe>标签直接嵌入,<iframe>标签是一种简单直接的方式&#xff0c;可将 B 站视频嵌…...

【深度学习】#12 计算机视觉

主要参考学习资料&#xff1a; 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李沐学AI 目录 目标检测锚框交并比&#xff08;IoU&#xff09;锚框标注真实边界框分配偏移量计算损失函数 非极大值抑制预测 多尺度目标检测单发多框检测&#xff08;S…...

QT学习3

QT项目视图 1、List View清单视图 private:QListView *listview1; private slots:void slotClickedFunc(const QModelIndex &index); #include "widget.h" #include "ui_widget.h"#include <QStringListModel>//字符串列表模型 #include <QS…...

Vue 3 动态 ref 的使用方式(表格)

一、问题描述 先给大家简单介绍一下问题背景。我正在开发的项目中&#xff0c;有一个表格组件&#xff0c;其中一列是分镜描述&#xff0c;需要支持视频上传功能。用户可以为每一行的分镜描述上传对应的视频示例。然而&#xff0c;在实现过程中&#xff0c;出现了一个严重的问…...

FAST-DDS源码分析PDP(一)

准备开一个FAST-DDS源码分析系列&#xff0c;源码版本FAST-DDS 1.1.0版本。 FAST-DDS这种网络中间件是非常复杂的&#xff0c;所以前期先去分析每个类的作用是什么&#xff0c;然后在结合RTPS DOC&#xff0c;FAST-DDS DEMO,以及FAST-DDS的doc去串起来逻辑。 Builtin Discovery…...

Flutter与Kotlin Multiplatform(KMP)深度对比及鸿蒙生态适配解析

Flutter 与 Kotlin Multiplatform&#xff08;KMP&#xff09;深度对比及鸿蒙生态适配解析 在跨平台开发领域&#xff0c;Flutter 与 Kotlin Multiplatform&#xff08;KMP&#xff09;代表了两种不同的技术路线&#xff1a;前者以 “统一 UI 体验” 为核心&#xff0c;后者以…...

深入了解linux系统—— 基础IO(上)

文件 在之前学习C语言文件操作时&#xff0c;我们了解过什么是文件&#xff0c;这里简单回顾一下&#xff1a; 文件存在磁盘中&#xff0c;文件有分为程序文件、数据文件&#xff1b;二进制文件和文本文件等。 详细描述见文章&#xff1a;文件操作——C语言 文件在磁盘里&a…...

C++ map multimap 容器:赋值、排序、大小与删除操作

概述 map和multimap是C STL中的关联容器&#xff0c;它们存储的是键值对(key-value pairs)&#xff0c;并且会根据键(key)自动排序。两者的主要区别在于&#xff1a; map不允许重复的键multimap允许重复的键 本文将详细解析示例代码中涉及的map操作&#xff0c;包括赋值、排…...

EmuEdit

EmuEdit详解&#xff1a;统一多任务图像编辑的扩展性范式 引言&#xff1a;图像编辑的困境 近年来&#xff0c;扩散模型&#xff08;Diffusion Models&#xff09;在图像合成和编辑方面取得了巨大进展&#xff0c;如 Prompt-to-Prompt (P2P)、InstructPix2Pix、DiffEdit 等方法…...

Linux编译rpm包与deb包

注意&#xff1a; 本文内容于 2025-05-14 23:55:53 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;编译rpm包与deb包。感谢您的关注与支持&#xff01; 近期在通过源码编译安装一些软件包时&#…...

GitHub 趋势日报 (2025年05月17日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1TapXWorld/ChinaTextbookPDF教材。⭐ 2471⭐ 22302Roff2public-apis/public-a…...

[创业之路-362]:企业战略管理案例分析-3-战略制定-华为使命、愿景、价值观的演变过程

一、华为使命、愿景、价值观的演变过程 1、创业初期&#xff08;1987 - 1994 年&#xff09;&#xff1a;生存导向&#xff0c;文化萌芽 使命愿景雏形&#xff1a;1994年华为提出“10年之后&#xff0c;世界通信行业三分天下&#xff0c;华为将占一份”的宏伟梦想&#xff0c…...

Android 性能优化入门(二)—— 内存优化

1、概述 1.1 Java 对象的生命周期 各状态含义&#xff1a; 创建&#xff1a;分配内存空间并调用构造方法应用&#xff1a;使用中&#xff0c;处于被强引用持有&#xff08;至少一个&#xff09;的状态不可见&#xff1a;不被强引用持有&#xff0c;应用程序已经不再使用该对象…...

(5)python爬虫--BeautifulSoup(bs4)

文章目录 [TOC](文章目录) 前言一、安装bs4二、bs4的基础使用2.1 创建soup对象2.2 根据标签名查找节点2.3 根据函数来查找节点1. find函数2. find_all函数3. select函数 三、使用bs4获取节点信息3.1 获取节点内容3.2 获取节点的属性3.3 获取节点的属性值 四、测试练习 总结 前言…...

如何利用DeepSeek提升工作效率

1. 代码开发辅助 1.1 代码生成 根据需求描述生成代码框架 自动补全代码片段 生成单元测试用例 创建项目文档 1.2 代码优化 代码重构建议 性能优化方案 最佳实践推荐 设计模式应用 2. 问题诊断与解决 2.1 错误分析 编译错误解析 运行时错误诊断 内存泄漏检测 性…...

游戏引擎学习第292天:实现蛇

每次VLC 读取OSD 会有bug 修复一下 回顾并计划实现一种漂浮的移动方式&#xff0c;并制作一个贪吃蛇 虽然不完全记得之前具体计划&#xff0c;但感觉是想实现一个小蛇形生物&#xff0c;之前一直没来得及做。我们还打算让熟悉的伙伴能漂浮移动&#xff0c;所以今天会继续进行一…...

菱形继承原理

在C中&#xff0c;菱形继承的内存模型会因是否使用虚继承产生本质差异。我们通过具体示例说明两种场景的区别&#xff1a; 一、普通菱形继承的内存模型 class A { int a; }; class B : public A { int b; }; class C : public A { int c; }; class D : public B, public C { i…...

C++编程起步项目

员工信息管理系统 需求 Employee.h #pragma once#include<iostream> #include<string>using namespace std;class Employee { public:int id; // 编号string name; // 姓名string position; // 岗位int deptId; // 部门编号Employee();Employee(int id, string n…...

c++编写中遇见的错误

目录 一.获取动态数组的长度二.编译错误三、内存泄露 一.获取动态数组的长度 首先想到获取数组的长度的代码是&#xff1a; sizeof(arr) / sizeof(arr[0]);但是当将其使用到动态数组上时就会产生错误&#xff1b; int* help new int[3];for (int i 0; i < 3; i) {help[…...

股票数据源对接技术指南:印度尼西亚、印度、韩国

一、多国数据对接全景图 1. 核心数据领域对比 国家金融市场数据源宏观经济指标特色数据资源印度NSE/BSE实时行情RBI经济统计库UPI支付数据/GST税务记录印尼IDX交易所数据流BPS官方统计棕榈油产业数据/群岛物流信息韩国KRX综合指数KOSTAT国家统计K-POP消费趋势/半导体出口数据…...

常见面试题:Webpack的构建流程简单说一下。

文章目录 前言一、Webpack 的核心使命&#xff1a;模块化打包二、Webpack 构建流程详解三、构建流程的可视化演示项目结构构建流程图 四、构建流程中的关键技术点1. 依赖图的构建与优化2. 哈希与缓存策略3. 开发环境优化 五、简易版概括构建流程 总结 前言 在前端工程化中&…...

Elasticsearch基础篇-java程序通过RestClient操作es

目录 1.引入 2 初始化RestClient 1&#xff09;引入es的RestHighLevelClient依赖&#xff1a; 2&#xff09;因为SpringBoot默认的ES版本是7.17.10&#xff0c;所以我们需要覆盖默认的ES版本&#xff1a; 3&#xff09;初始化RestHighLevelClient&#xff1a; 4&#xff09…...

SuperYOLO:多模态遥感图像中的超分辨率辅助目标检测之论文阅读

摘要 在遥感影像&#xff08;RSI&#xff09;中&#xff0c;准确且及时地检测包含数十像素的多尺度小目标仍具有挑战性。现有大多数方法主要通过设计复杂的深度神经网络来学习目标与背景的区分特征&#xff0c;常导致计算量过大。本文提出一种兼顾检测精度与计算代价的快速准确…...

k6学习k6学习k6学习k6学习k6学习k6学习

1.安装go 2.安装 xk6 (k6 扩展构建工具): go install go.k6.io/xk6/cmd/xk6latest3.构建自定义 k6 二进制文件&#xff08;集成 faker 扩展&#xff09;: xk6 build --with github.com/gkarthiks/xk6-fakerlatest构建报错处理&#xff08;代码拉取失败&#xff09;&#xff1…...

ubuntu 安装mq

一、安装依赖 编译 Erlang 需要以下依赖库和工具&#xff1a; sudo apt update sudo apt install -y build-essential autoconf libncurses5-dev libssl-dev m4 unixodbc-dev libwxgtk3.0-gtk3-dev libgl1-mesa-dev libglu1-mesa-dev 二、解压源码包 tar -xzvf otp_src_21.…...

优化 Spring Boot 应用启动性能的实践指南

1. 引言 Spring Boot 以其“开箱即用”的特性深受开发者喜爱,但随着项目复杂度的增加,应用的启动时间也可能会变得较长。对于云原生、Serverless 等场景而言,快速启动是一个非常关键的指标。 2. 分析启动过程 2.1 启动阶段概述 Spring Boot 的启动流程主要包括以下几个阶…...

ubuntu18.04编译qt5.14.2源码

ubuntu18.04编译qt5.14.2源码 文章目录 ubuntu18.04编译qt5.14.2源码[toc]1 前言2 参考文档3 下载源码3.1 方法13.2 方法23.3 方法3 4 ubuntu编译qt源码4.1 环境准备4.2 设置交换分区大小4.3 编译源码4.4 添加环境变量4.5 验证编译结果4.6 编译帮助文档&#xff08;qch&#xf…...

leetcodehot100刷题——排序算法总结

排序算法总结 冒泡排序介绍步骤&#xff08;以升序排序为例&#xff09;算法实现复杂度分析时间复杂度空间复杂度 是否为稳定排序&#xff1a;是稳定排序的定义 选择排序介绍步骤&#xff08;以升序排序为例&#xff09;算法实现复杂度分析时间复杂度空间复杂度 是否为稳定排序…...

多用途商务,电子产品发布,科技架构,智能手表交互等发布PPT模版20套一组分享

产品发布类PPT模版20套一组&#xff1a;产品发布PPT模版https://pan.quark.cn/s/25c8517b0be3 第一套PPT模版是一个总结用的PPT封面&#xff0c;背景浅灰色&#xff0c;有绿色叶片和花朵装饰&#xff0c;深绿色标题&#xff0c;多个适用场景和占位符。突出其清新自然的设计和商…...

2025年- H29-Lc137- 19.删除链表的倒数第N个节点(快慢指针)---java版

1.题目描述 2.思路 快慢指针都在虚拟头节点&#xff0c;然后让快指针先走n1步&#xff0c;接下来&#xff0c;快慢指针以前移动&#xff0c;直到快指针指向null&#xff0c;慢指针指向被删节点的前一个节点。 3.代码实现 方法一&#xff1a;不带测试用例 /*** Definition …...

新电脑软件配置二:安装python,git, pycharm

安装python 地址 https://www.python.org/downloads/ 不是很懂为什么这么多版本 安装windows64位的 这里我是凭自己感觉装的了 然后cmd输入命令没有生效&#xff0c;先重启下&#xff1f; 重启之后再次验证 环境是成功的 之前是输入的python -version 命令输入错误 安装pyc…...

医学影像开发的开源生态与技术实践:从DCMTK到DICOMweb的全面探索

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…...