Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.github.xezzon.zeroweb.attachment.enumeration.AttachmentStatusEnum;
import io.github.xezzon.zeroweb.common.config.FileProviderEnum;
import io.github.xezzon.zeroweb.common.constant.DatabaseConstant;
import io.github.xezzon.zeroweb.common.jpa.IEntity;
import io.github.xezzon.zeroweb.common.jpa.IdGenerator;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -42,7 +43,7 @@
@Entity
@Table(name = "zeroweb_attachment")
@EntityListeners({AuditingEntityListener.class})
public class Attachment {
public class Attachment implements IEntity<String> {

/**
* 附件的唯一标识符。
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: Copyright (C) 2026 xezzon
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of ZeroWeb.
*
* ZeroWeb is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* ZeroWeb is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with ZeroWeb. If not, see <https://www.gnu.org/licenses/>.
*/

package io.github.xezzon.zeroweb.attachment.internal;

import io.github.xezzon.zeroweb.attachment.Attachment;
import io.github.xezzon.zeroweb.attachment.repository.AttachmentRepository;
import io.github.xezzon.zeroweb.common.jpa.BaseDAO;
import org.springframework.stereotype.Repository;

/// 附件数据访问对象
/// @author xezzon
@Repository
public class AttachmentDAO extends BaseDAO<Attachment, String, AttachmentRepository> {

/// 依赖注入
/// @param repository 附件 JPA 接口
protected AttachmentDAO(final AttachmentRepository repository) {
super(repository, Attachment.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (C) 2025 xezzon
* SPDX-FileCopyrightText: Copyright (C) 2025-2026 xezzon
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of ZeroWeb.
Expand All @@ -15,9 +15,11 @@

import static io.github.xezzon.zeroweb.common.constant.FileConstant.MAX_MULTIPART_NUMBER;

import cn.dev33.satoken.annotation.SaCheckPermission;
import io.github.xezzon.zeroweb.attachment.Attachment;
import io.github.xezzon.zeroweb.attachment.entity.AddAttachmentReq;
import io.github.xezzon.zeroweb.attachment.entity.UploadInfo;
import io.github.xezzon.zeroweb.core.odata.ODataRequestParam;
import io.github.xezzon.zeroweb.storage.DownloadEndpoint;
import io.github.xezzon.zeroweb.storage.StorageContext;
import io.github.xezzon.zeroweb.storage.UploadEndpoint;
Expand All @@ -27,6 +29,7 @@
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -126,6 +129,14 @@ public List<Attachment> queryByBiz(
return attachmentService.queryByBiz(bizType, bizId);
}

/// 查询指定附件
/// @param id 附件ID
/// @return 附件信息
@GetMapping("/{id}")
public Attachment queryById(@PathVariable final String id) {
return attachmentService.queryById(id);
}

/// 获取附件下载地址
/// @param id 附件ID
/// @return 附件下载地址
Expand All @@ -140,4 +151,13 @@ public DownloadEndpoint getDownloadEndpoint(@PathVariable @NotBlank final String
public void deleteAttachment(@PathVariable @NotBlank final String id) {
attachmentService.deleteAttachment(id);
}

/// 分页查询附件列表
/// @param odata 查询参数
/// @return 附件列表
@SaCheckPermission("/")
@GetMapping("/page")
public Page<Attachment> queryPage(final ODataRequestParam odata) {
return attachmentService.queryPage(odata.into());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (C) 2025 xezzon
* SPDX-FileCopyrightText: Copyright (C) 2025-2026 xezzon
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of ZeroWeb.
Expand All @@ -25,6 +25,7 @@
import io.github.xezzon.zeroweb.auth.JwtClaim;
import io.github.xezzon.zeroweb.common.config.ZerowebFileConfig;
import io.github.xezzon.zeroweb.common.exception.IncorrectFileException;
import io.github.xezzon.zeroweb.core.odata.ODataQueryOption;
import io.github.xezzon.zeroweb.storage.DownloadEndpoint;
import io.github.xezzon.zeroweb.storage.IStorageService;
import io.github.xezzon.zeroweb.storage.UploadEndpoint;
Expand All @@ -33,6 +34,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

/// 附件管理服务
Expand All @@ -41,21 +43,25 @@
public class AttachmentService implements IAttachmentService {

private final AttachmentRepository attachmentRepository;
private final AttachmentDAO attachmentDAO;
private final ZerowebFileConfig zerowebFileConfig;
private final IStorageService.Factory storageServiceFactory;
@Resource
private ApplicationEventPublisher eventPublisher;

/// 依赖注入
/// @param attachmentRepository 附件 JPA 接口
/// @param attachmentDAO 附件相关的数据库操作
/// @param zerowebFileConfig 文件管理相关设置
/// @param storageServiceFactory 用于获取存储操作服务实现类的工厂
public AttachmentService(
final AttachmentRepository attachmentRepository,
final AttachmentDAO attachmentDAO,
final ZerowebFileConfig zerowebFileConfig,
IStorageService.Factory storageServiceFactory
) {
this.attachmentRepository = attachmentRepository;
this.attachmentDAO = attachmentDAO;
this.zerowebFileConfig = zerowebFileConfig;
this.storageServiceFactory = storageServiceFactory;
}
Expand Down Expand Up @@ -147,11 +153,13 @@ List<Attachment> queryByBiz(String bizType, String bizId) {

/// 获取文件下载访问点
/// @param id 附件ID
/// @return 下载访问点信息,包含下载URL和相关参数
/// @return 下载访问点信息,包含下载URL、文件名等参数
DownloadEndpoint getDownloadEndpoint(String id) {
Attachment attachment = attachmentRepository.findById(id).orElseThrow();
IStorageService storageService = storageServiceFactory.get(attachment.getProvider());
return storageService.getDownloadEndpoint(attachment);
DownloadEndpoint endpoint = storageService.getDownloadEndpoint(attachment);
endpoint.setFilename(attachment.getName());
return endpoint;
}

/// 下载文件内容
Expand All @@ -172,4 +180,11 @@ void deleteAttachment(String id) {
eventPublisher.publishEvent(new AttachmentDeletedEvent(attachment));
});
}

/// 分页查询附件列表
/// @param odata 查询参数
/// @return 附件列表(分页)
Page<Attachment> queryPage(final ODataQueryOption odata) {
return attachmentDAO.findAll(odata);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (C) 2025 xezzon
* SPDX-FileCopyrightText: Copyright (C) 2025-2026 xezzon
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of ZeroWeb.
Expand All @@ -14,6 +14,7 @@
package io.github.xezzon.zeroweb.storage;

import lombok.Getter;
import lombok.Setter;

/// 附件下载地址
/// @author xezzon
Expand All @@ -22,6 +23,9 @@ public class DownloadEndpoint {

/// 下载地址
private String endpoint;
/// 文件名
@Setter
private String filename;

/**
* 默认构造函数
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.github.xezzon.zeroweb.auth.AuthHttpConstant.AUTHORIZATION;
import static io.github.xezzon.zeroweb.auth.JwtFilter.PUBLIC_KEY_HEADER;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
Expand All @@ -14,6 +15,7 @@
import io.github.xezzon.zeroweb.auth.TestJwtGenerator;
import io.github.xezzon.zeroweb.common.config.ZerowebFileConfig;
import io.github.xezzon.zeroweb.common.constant.BannerConstant;
import io.github.xezzon.zeroweb.common.domain.PagedModel;
import io.github.xezzon.zeroweb.common.exception.ErrorCodeConstant;
import io.github.xezzon.zeroweb.common.exception.ReadFileException;
import io.github.xezzon.zeroweb.core.util.ResourceUtil;
Expand All @@ -33,11 +35,13 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.zip.CRC32;
import org.jspecify.annotations.NonNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -70,8 +74,10 @@ abstract class AttachmentHttpTest {
private static final String GET_UPLOAD_ADDRESS = "/attachment/{id}/endpoint/upload";
private static final String FINISH_UPLOAD = "/attachment/{id}/status/done";
private static final String QUERY_BY_BIZ = "/attachment/list";
private static final String QUERY_BY_ID = "/attachment/{id}";
private static final String GET_DOWNLOAD_ADDRESS = "/attachment/{id}/endpoint/download";
private static final String DELETE_ATTACHMENT = "/attachment/{id}";
private static final String QUERY_PAGE = "/attachment/page";
private static final String FILE_NAME = "test.txt";
private static final String LARGE_FILE = "large_file.jpg";

Expand Down Expand Up @@ -158,11 +164,17 @@ void setUp_download() throws IOException {
attachmentForDownloading.setBizType(RandomUtil.randomString(8));
attachmentForDownloading.setBizId(UUID.randomUUID().toString());
attachmentForDownloading.setProvider(zerowebFileConfig.getProvider());
attachmentForDownloading.setStatus(AttachmentStatusEnum.UPLOADING);
attachmentForDownloading.setStatus(AttachmentStatusEnum.DONE);
repository.save(attachmentForDownloading);
this.saveFile(attachmentForDownloading);
}

@AfterEach
void tearDown() {
repository.deleteAll();
largeFileParts.clear();
}

@Test
void addAttachment() {
final String userId = UUID.randomUUID().toString();
Expand Down Expand Up @@ -798,6 +810,17 @@ void queryByBiz_empty() {
Assertions.assertTrue(responseBody2.isEmpty());
}

@Test
void queryById() {
Attachment responseBody = testClient.get()
.uri(QUERY_BY_ID, attachment.getId())
.exchange()
.expectStatus().isOk()
.expectBody(Attachment.class).returnResult().getResponseBody();
Assertions.assertNotNull(responseBody);
Assertions.assertEquals(attachment.getId(), responseBody.getId());
}

@Test
void download() throws IOException {
DownloadEndpoint downloadEndpoint = testClient.get()
Expand Down Expand Up @@ -836,6 +859,37 @@ void deleteAttachment() {
Assertions.assertArrayEquals(new byte[0], this.readFile(attachment));
}

@Test
void queryPage() {
final int top = 5;
final int skip = 0;

PagedModel<Attachment> responseBody = testClient.get()
.uri(builder -> builder
.path(QUERY_PAGE)
.queryParam("top", top)
.queryParam("skip", skip)
.build()
)
.header(PUBLIC_KEY_HEADER, TestJwtGenerator.getPublicKey())
.header(AUTHORIZATION, TestJwtGenerator.userBuilder().bearer())
.exchange()
.expectStatus().isOk()
.expectBody(new ParameterizedTypeReference<@NonNull PagedModel<Attachment>>() {
})
.returnResult().getResponseBody();

assertNotNull(responseBody);
Assertions.assertEquals(2, responseBody.getPage().getTotalElements());
List<Attachment> actual = responseBody.getContent();
Assertions.assertTrue(actual.stream().anyMatch(o ->
Objects.equals(o.getId(), attachment.getId())
));
Assertions.assertTrue(actual.stream().anyMatch(o ->
Objects.equals(o.getId(), attachmentForDownloading.getId())
));
}

private URI localhost(String uri) {
return URI.create("http://localhost:" + port).resolve(URI.create(uri));
}
Expand Down