6 changed files with 1273 additions and 1 deletions
@ -0,0 +1,190 @@ |
|||
package com.zdxt.enforcementcode.controller; |
|||
|
|||
import com.zdxt.enforcementcode.domain.vo.QrCodeGenerateHistoryVo; |
|||
import com.zdxt.enforcementcode.util.QrCodeUtil; |
|||
import jakarta.validation.constraints.NotBlank; |
|||
import jakarta.validation.constraints.NotNull; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.dromara.common.core.domain.R; |
|||
import org.dromara.common.idempotent.annotation.RepeatSubmit; |
|||
import org.dromara.common.log.annotation.Log; |
|||
import org.dromara.common.log.enums.BusinessType; |
|||
import org.dromara.common.web.core.BaseController; |
|||
import org.springframework.validation.annotation.Validated; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
|
|||
/** |
|||
* 二维码生成通用接口 |
|||
* <p> |
|||
* 提供二维码生成(标准黑白 / 蓝色带 LOGO)、Base64 编码、二维码解析等通用能力。 |
|||
* 生成后自动上传到 OSS 并保存 zdxt_file 和 qr_code_generate_history 记录。 |
|||
* </p> |
|||
* |
|||
* @author zdxt |
|||
*/ |
|||
@Slf4j |
|||
@Validated |
|||
@RequiredArgsConstructor |
|||
@RestController |
|||
@RequestMapping("/common/qrcode") |
|||
public class QrCodeController extends BaseController { |
|||
|
|||
/** |
|||
* 生成标准黑白二维码(默认尺寸 300px),上传 OSS 并保存记录 |
|||
* |
|||
* @param content 二维码内容(通常为 URL) |
|||
* @param enterpriseNumber 企业纳税识别号 |
|||
* @param businessId 关联的业务ID |
|||
* @return 二维码生成历史记录 |
|||
*/ |
|||
@Log(title = "二维码生成", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/generate") |
|||
public R<QrCodeGenerateHistoryVo> generate( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotBlank(message = "企业纳税识别号不能为空") @RequestParam String enterpriseNumber, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId) { |
|||
QrCodeGenerateHistoryVo vo = QrCodeUtil.generateAndSave(content, enterpriseNumber, businessId); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 生成标准黑白二维码(可指定分类代码和尺寸),上传 OSS 并保存记录 |
|||
* |
|||
* @param content 二维码内容 |
|||
* @param enterpriseNumber 企业纳税识别号 |
|||
* @param businessId 关联的业务ID |
|||
* @param sortCode 分类代码(可为空) |
|||
* @param size 二维码尺寸(像素) |
|||
* @return 二维码生成历史记录 |
|||
*/ |
|||
@Log(title = "二维码生成", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/generateWithSize") |
|||
public R<QrCodeGenerateHistoryVo> generateWithSize( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotBlank(message = "企业纳税识别号不能为空") @RequestParam String enterpriseNumber, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@RequestParam(required = false) String sortCode, |
|||
@NotNull(message = "二维码尺寸不能为空") @RequestParam Integer size) { |
|||
QrCodeGenerateHistoryVo vo = QrCodeUtil.generateAndSave(content, enterpriseNumber, businessId, sortCode, size); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 生成蓝色二维码(含默认 LOGO),上传 OSS 并保存记录 |
|||
* |
|||
* @param content 二维码内容 |
|||
* @param enterpriseNumber 企业纳税识别号 |
|||
* @param businessId 关联的业务ID |
|||
* @param size 二维码尺寸(像素) |
|||
* @return 二维码生成历史记录 |
|||
*/ |
|||
@Log(title = "蓝色二维码生成", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/generateBlueWithLogo") |
|||
public R<QrCodeGenerateHistoryVo> generateBlueWithLogo( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotBlank(message = "企业纳税识别号不能为空") @RequestParam String enterpriseNumber, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@NotNull(message = "二维码尺寸不能为空") @RequestParam Integer size) { |
|||
QrCodeGenerateHistoryVo vo = QrCodeUtil.generateBlueWithLogoAndSave(content, enterpriseNumber, businessId, size); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 生成蓝色二维码(含默认 LOGO、可指定分类代码),上传 OSS 并保存记录 |
|||
* |
|||
* @param content 二维码内容 |
|||
* @param enterpriseNumber 企业纳税识别号 |
|||
* @param businessId 关联的业务ID |
|||
* @param sortCode 分类代码(可为空) |
|||
* @param size 二维码尺寸(像素) |
|||
* @return 二维码生成历史记录 |
|||
*/ |
|||
@Log(title = "蓝色二维码生成", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/generateBlueWithLogoAndSort") |
|||
public R<QrCodeGenerateHistoryVo> generateBlueWithLogoAndSort( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotBlank(message = "企业纳税识别号不能为空") @RequestParam String enterpriseNumber, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@RequestParam(required = false) String sortCode, |
|||
@NotNull(message = "二维码尺寸不能为空") @RequestParam Integer size) { |
|||
QrCodeGenerateHistoryVo vo = QrCodeUtil.generateBlueWithLogoAndSave(content, enterpriseNumber, businessId, sortCode, size); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 生成标准黑白二维码的 Base64 编码字符串(含 data:image 前缀) |
|||
* |
|||
* @param content 二维码内容 |
|||
* @param size 二维码尺寸(像素) |
|||
* @return Base64 编码字符串 |
|||
*/ |
|||
@GetMapping("/base64") |
|||
public R<String> toBase64( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotNull(message = "二维码尺寸不能为空") @RequestParam Integer size) { |
|||
String base64 = QrCodeUtil.toBase64(content, size); |
|||
return R.ok(base64); |
|||
} |
|||
|
|||
/** |
|||
* 生成蓝色二维码(含 LOGO)的 Base64 编码字符串 |
|||
* |
|||
* @param content 二维码内容 |
|||
* @param size 二维码尺寸(像素) |
|||
* @return Base64 编码字符串 |
|||
*/ |
|||
@GetMapping("/blueBase64WithLogo") |
|||
public R<String> toBlueBase64WithLogo( |
|||
@NotBlank(message = "二维码内容不能为空") @RequestParam String content, |
|||
@NotNull(message = "二维码尺寸不能为空") @RequestParam Integer size) { |
|||
String base64 = QrCodeUtil.toBlueBase64WithLogo(content, size); |
|||
return R.ok(base64); |
|||
} |
|||
|
|||
/** |
|||
* 解析上传的二维码图片,返回二维码内容 |
|||
* |
|||
* @param file 二维码图片文件 |
|||
* @return 解析出的文本内容 |
|||
*/ |
|||
@PostMapping("/decode") |
|||
public R<String> decode(@RequestParam("file") MultipartFile file) { |
|||
if (file == null || file.isEmpty()) { |
|||
return R.fail("上传的二维码图片不能为空"); |
|||
} |
|||
Path tempFile = null; |
|||
try { |
|||
// 将上传文件写入临时文件以供解析
|
|||
String originalFilename = file.getOriginalFilename(); |
|||
String suffix = (originalFilename != null && originalFilename.contains(".")) |
|||
? originalFilename.substring(originalFilename.lastIndexOf(".")) |
|||
: ".jpg"; |
|||
tempFile = Files.createTempFile("qr_decode_", suffix); |
|||
file.transferTo(tempFile.toFile()); |
|||
String result = QrCodeUtil.decodeQrCode(tempFile.toFile()); |
|||
return R.ok("解析成功", result); |
|||
} catch (IOException e) { |
|||
log.error("解析二维码失败: {}", e.getMessage(), e); |
|||
return R.fail("解析二维码失败: " + e.getMessage()); |
|||
} finally { |
|||
// 清理临时文件
|
|||
if (tempFile != null) { |
|||
try { |
|||
Files.deleteIfExists(tempFile); |
|||
} catch (IOException ignored) { |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,193 @@ |
|||
package com.zdxt.enforcementcode.controller; |
|||
|
|||
import com.zdxt.enforcementcode.domain.vo.ZdxtFileVo; |
|||
import com.zdxt.enforcementcode.util.ZdxtFileUtil; |
|||
import jakarta.validation.constraints.NotBlank; |
|||
import jakarta.validation.constraints.NotEmpty; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.dromara.common.core.domain.R; |
|||
import org.dromara.common.idempotent.annotation.RepeatSubmit; |
|||
import org.dromara.common.log.annotation.Log; |
|||
import org.dromara.common.log.enums.BusinessType; |
|||
import org.dromara.common.web.core.BaseController; |
|||
import org.springframework.validation.annotation.Validated; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 附件管理通用接口 |
|||
* <p> |
|||
* 封装文件上传(本地存储)、查询、删除等通用能力, |
|||
* 基于 {@link ZdxtFileUtil} 工具类,文件保存到本地磁盘并记录到 zdxt_file 表。 |
|||
* </p> |
|||
* |
|||
* @author zdxt |
|||
*/ |
|||
@Slf4j |
|||
@Validated |
|||
@RequiredArgsConstructor |
|||
@RestController |
|||
@RequestMapping("/common/file") |
|||
public class ZdxtFileUtilController extends BaseController { |
|||
|
|||
// ==================== 上传接口 ====================
|
|||
|
|||
/** |
|||
* 上传单个文件(自动获取当前登录用户信息) |
|||
* |
|||
* @param file 上传的文件 |
|||
* @param businessId 业务ID(关联的业务主键) |
|||
* @return 附件视图对象 |
|||
*/ |
|||
@Log(title = "附件上传", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/upload") |
|||
public R<ZdxtFileVo> upload( |
|||
@RequestParam("file") MultipartFile file, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId) { |
|||
ZdxtFileVo vo = ZdxtFileUtil.uploadFile(file, businessId); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 上传单个文件,带分类代码(自动获取当前登录用户信息) |
|||
* |
|||
* @param file 上传的文件 |
|||
* @param businessId 业务ID |
|||
* @param sortCode 分类代码 |
|||
* @return 附件视图对象 |
|||
*/ |
|||
@Log(title = "附件上传", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/uploadWithSort") |
|||
public R<ZdxtFileVo> uploadWithSort( |
|||
@RequestParam("file") MultipartFile file, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@RequestParam(required = false) String sortCode) { |
|||
ZdxtFileVo vo = ZdxtFileUtil.uploadFile(file, businessId, sortCode); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 批量上传文件 |
|||
* |
|||
* @param files 文件数组 |
|||
* @param businessId 业务ID |
|||
* @return 附件视图对象列表 |
|||
*/ |
|||
@Log(title = "附件批量上传", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/uploadBatch") |
|||
public R<List<ZdxtFileVo>> uploadBatch( |
|||
@RequestParam("files") MultipartFile[] files, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId) { |
|||
List<ZdxtFileVo> list = ZdxtFileUtil.uploadFiles(files, businessId); |
|||
return R.ok(list); |
|||
} |
|||
|
|||
/** |
|||
* 批量上传文件,带分类代码 |
|||
* |
|||
* @param files 文件数组 |
|||
* @param businessId 业务ID |
|||
* @param sortCode 分类代码 |
|||
* @return 附件视图对象列表 |
|||
*/ |
|||
@Log(title = "附件批量上传", businessType = BusinessType.INSERT) |
|||
@RepeatSubmit() |
|||
@PostMapping("/uploadBatchWithSort") |
|||
public R<List<ZdxtFileVo>> uploadBatchWithSort( |
|||
@RequestParam("files") MultipartFile[] files, |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@RequestParam(required = false) String sortCode) { |
|||
List<ZdxtFileVo> list = ZdxtFileUtil.uploadFiles(files, businessId, sortCode); |
|||
return R.ok(list); |
|||
} |
|||
|
|||
// ==================== 查询接口 ====================
|
|||
|
|||
/** |
|||
* 根据附件ID查询附件信息 |
|||
* |
|||
* @param guid 附件主键ID |
|||
* @return 附件视图对象 |
|||
*/ |
|||
@GetMapping("/{guid}") |
|||
public R<ZdxtFileVo> getByGuid( |
|||
@NotBlank(message = "附件ID不能为空") @PathVariable String guid) { |
|||
ZdxtFileVo vo = ZdxtFileUtil.queryByGuid(guid); |
|||
return R.ok(vo); |
|||
} |
|||
|
|||
/** |
|||
* 根据业务ID查询附件列表 |
|||
* |
|||
* @param businessId 业务ID |
|||
* @return 附件列表 |
|||
*/ |
|||
@GetMapping("/listByBusinessId") |
|||
public R<List<ZdxtFileVo>> listByBusinessId( |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId) { |
|||
List<ZdxtFileVo> list = ZdxtFileUtil.queryByBusinessId(businessId); |
|||
return R.ok(list); |
|||
} |
|||
|
|||
/** |
|||
* 根据业务ID和分类代码查询附件列表 |
|||
* |
|||
* @param businessId 业务ID |
|||
* @param sortCode 分类代码 |
|||
* @return 附件列表 |
|||
*/ |
|||
@GetMapping("/listByBusinessIdAndSort") |
|||
public R<List<ZdxtFileVo>> listByBusinessIdAndSort( |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId, |
|||
@NotBlank(message = "分类代码不能为空") @RequestParam String sortCode) { |
|||
List<ZdxtFileVo> list = ZdxtFileUtil.queryByBusinessIdAndSortCode(businessId, sortCode); |
|||
return R.ok(list); |
|||
} |
|||
|
|||
// ==================== 删除接口 ====================
|
|||
|
|||
/** |
|||
* 根据附件ID删除单个附件(同时删除本地文件) |
|||
* |
|||
* @param guid 附件主键ID |
|||
* @return 是否删除成功 |
|||
*/ |
|||
@Log(title = "附件删除", businessType = BusinessType.DELETE) |
|||
@DeleteMapping("/{guid}") |
|||
public R<Void> deleteByGuid( |
|||
@NotBlank(message = "附件ID不能为空") @PathVariable String guid) { |
|||
return toAjax(ZdxtFileUtil.deleteByGuid(guid)); |
|||
} |
|||
|
|||
/** |
|||
* 根据附件ID批量删除附件(同时删除本地文件) |
|||
* |
|||
* @param guids 附件主键ID数组 |
|||
* @return 是否删除成功 |
|||
*/ |
|||
@Log(title = "附件批量删除", businessType = BusinessType.DELETE) |
|||
@DeleteMapping("/batch/{guids}") |
|||
public R<Void> deleteByGuids( |
|||
@NotEmpty(message = "附件ID不能为空") @PathVariable String[] guids) { |
|||
return toAjax(ZdxtFileUtil.deleteByGuids(List.of(guids))); |
|||
} |
|||
|
|||
/** |
|||
* 根据业务ID删除所有关联附件(同时删除本地文件) |
|||
* |
|||
* @param businessId 业务ID |
|||
* @return 是否删除成功 |
|||
*/ |
|||
@Log(title = "按业务ID删除附件", businessType = BusinessType.DELETE) |
|||
@DeleteMapping("/deleteByBusinessId") |
|||
public R<Void> deleteByBusinessId( |
|||
@NotBlank(message = "业务ID不能为空") @RequestParam String businessId) { |
|||
return toAjax(ZdxtFileUtil.deleteByBusinessId(businessId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,404 @@ |
|||
package com.zdxt.rest.test; |
|||
|
|||
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; |
|||
import cn.dev33.satoken.stp.parameter.SaLoginParameter; |
|||
import cn.dev33.satoken.stp.StpUtil; |
|||
import org.dromara.ZdxtAdminApplication; |
|||
import org.dromara.common.core.domain.model.LoginUser; |
|||
import org.dromara.common.satoken.utils.LoginHelper; |
|||
import org.junit.jupiter.api.*; |
|||
import org.springframework.mock.web.MockHttpServletRequest; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.mock.web.MockMultipartFile; |
|||
import org.springframework.test.web.servlet.MockMvc; |
|||
import org.springframework.test.web.servlet.MvcResult; |
|||
|
|||
import javax.imageio.ImageIO; |
|||
import java.awt.image.BufferedImage; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.HashSet; |
|||
|
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; |
|||
|
|||
/** |
|||
* QrCodeController 二维码通用接口测试 |
|||
* <p> |
|||
* 分为三类: |
|||
* 1. 参数校验测试(不依赖 OSS/DB) |
|||
* 2. Base64 / 解析测试(纯计算,不依赖 OSS/DB) |
|||
* 3. 生成+保存集成测试(需要 Spring 上下文、OSS 和数据库) |
|||
* </p> |
|||
* |
|||
* @author zdxt |
|||
*/ |
|||
@SpringBootTest(classes = ZdxtAdminApplication.class) |
|||
@AutoConfigureMockMvc |
|||
@DisplayName("QrCodeController 二维码通用接口测试") |
|||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) |
|||
public class QrCodeControllerTest { |
|||
|
|||
@Autowired |
|||
private MockMvc mockMvc; |
|||
|
|||
private static final String BASE_URL = "/common/qrcode"; |
|||
private static final String TEST_CONTENT = "https://www.example.com/qr?id=ctrl_test"; |
|||
private static final String TEST_ENTERPRISE_NUMBER = "91110000CTRL001"; |
|||
private static final String TEST_BUSINESS_ID = "TEST_CTRL_QR_001"; |
|||
private static final String TEST_CLIENT_ID = "e5cd7e4891bf95d1d19206ce24a7b32e"; |
|||
|
|||
/** 登录后获取的 token */ |
|||
private String token; |
|||
|
|||
@BeforeEach |
|||
public void setUp() { |
|||
// 初始化 Sa-Token 上下文(非 Web 环境下 StpUtil.login 需要)
|
|||
SaTokenContextMockUtil.setMockContext(); |
|||
|
|||
// 设置模拟请求上下文(UserActionListener 需要从 Request 中获取 User-Agent)
|
|||
MockHttpServletRequest mockRequest = new MockHttpServletRequest(); |
|||
mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); |
|||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); |
|||
|
|||
// 构造测试用登录用户
|
|||
LoginUser loginUser = new LoginUser(); |
|||
loginUser.setUserId(1L); |
|||
loginUser.setUserType("sys_user"); |
|||
loginUser.setTenantId("000000"); |
|||
loginUser.setUsername("test_admin"); |
|||
loginUser.setNickname("测试管理员"); |
|||
loginUser.setDeptId("103"); |
|||
loginUser.setDeptName("测试部门"); |
|||
loginUser.setRolePermission(new HashSet<>()); |
|||
loginUser.setMenuPermission(new HashSet<>()); |
|||
|
|||
// 模拟登录并获取 token
|
|||
SaLoginParameter param = new SaLoginParameter(); |
|||
param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID); |
|||
LoginHelper.login(loginUser, param); |
|||
token = StpUtil.getTokenValue(); |
|||
} |
|||
|
|||
@AfterEach |
|||
public void tearDown() { |
|||
try { |
|||
StpUtil.logout(); |
|||
} catch (Exception ignored) { |
|||
} |
|||
RequestContextHolder.resetRequestAttributes(); |
|||
SaTokenContextMockUtil.clearContext(); |
|||
} |
|||
|
|||
// ==================== 参数校验测试 ====================
|
|||
|
|||
@DisplayName("生成二维码 - 缺少 content 参数应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testGenerateMissingContent() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generate") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("生成二维码 - 缺少 enterpriseNumber 参数应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testGenerateMissingEnterpriseNumber() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generate") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("生成二维码 - 缺少 businessId 参数应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testGenerateMissingBusinessId() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generate") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("自定义尺寸生成 - 缺少 size 参数应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testGenerateWithSizeMissingSize() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generateWithSize") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("蓝色二维码生成 - 缺少 size 参数应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testGenerateBlueWithLogoMissingSize() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generateBlueWithLogo") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
// ==================== Base64 接口测试 ====================
|
|||
|
|||
@DisplayName("Base64 接口 - 缺少 content 参数应返回失败") |
|||
@Test |
|||
@Order(2) |
|||
public void testBase64MissingContent() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/base64") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("size", "300")) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("Base64 接口 - 缺少 size 参数应返回失败") |
|||
@Test |
|||
@Order(2) |
|||
public void testBase64MissingSize() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/base64") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("Base64 接口 - 正常生成标准二维码 Base64") |
|||
@Test |
|||
@Order(2) |
|||
public void testBase64Success() throws Exception { |
|||
MvcResult result = mockMvc.perform(get(BASE_URL + "/base64") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("size", "300")) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()) |
|||
.andReturn(); |
|||
|
|||
String responseBody = result.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
Assertions.assertTrue(responseBody.contains("data:image/jpeg;base64,"), |
|||
"返回数据应包含 Base64 前缀"); |
|||
System.out.println("Base64 接口测试成功,响应长度: " + responseBody.length()); |
|||
} |
|||
|
|||
@DisplayName("蓝色 Base64 接口 - 正常生成蓝色二维码 Base64") |
|||
@Test |
|||
@Order(2) |
|||
public void testBlueBase64WithLogoSuccess() throws Exception { |
|||
MvcResult result = mockMvc.perform(get(BASE_URL + "/blueBase64WithLogo") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("size", "300")) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()) |
|||
.andReturn(); |
|||
|
|||
String responseBody = result.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
Assertions.assertTrue(responseBody.contains("data:image/jpeg;base64,"), |
|||
"返回数据应包含 Base64 前缀"); |
|||
System.out.println("蓝色 Base64 接口测试成功"); |
|||
} |
|||
|
|||
// ==================== 二维码解析接口测试 ====================
|
|||
|
|||
@DisplayName("解析接口 - 上传空文件应返回失败") |
|||
@Test |
|||
@Order(3) |
|||
public void testDecodeEmptyFile() throws Exception { |
|||
MockMultipartFile emptyFile = new MockMultipartFile( |
|||
"file", "empty.jpg", "image/jpeg", new byte[0] |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/decode").file(emptyFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(500)); |
|||
} |
|||
|
|||
@DisplayName("解析接口 - 正常解析二维码图片") |
|||
@Test |
|||
@Order(3) |
|||
public void testDecodeSuccess() throws Exception { |
|||
// 通过 ZXing 生成一张真实的二维码图片作为测试输入
|
|||
com.google.zxing.common.BitMatrix bitMatrix = new com.google.zxing.MultiFormatWriter() |
|||
.encode(TEST_CONTENT, com.google.zxing.BarcodeFormat.QR_CODE, 300, 300); |
|||
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_RGB); |
|||
for (int x = 0; x < 300; x++) { |
|||
for (int y = 0; y < 300; y++) { |
|||
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); |
|||
} |
|||
} |
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
ImageIO.write(image, "jpg", baos); |
|||
byte[] imageBytes = baos.toByteArray(); |
|||
|
|||
MockMultipartFile qrFile = new MockMultipartFile( |
|||
"file", "qrcode.jpg", "image/jpeg", imageBytes |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/decode").file(qrFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").value(TEST_CONTENT)); |
|||
|
|||
System.out.println("二维码解析接口测试成功"); |
|||
} |
|||
|
|||
@DisplayName("解析接口 - 上传非二维码图片应返回失败") |
|||
@Test |
|||
@Order(3) |
|||
public void testDecodeNonQrImage() throws Exception { |
|||
// 生成一张纯白色图片(非二维码)
|
|||
BufferedImage whiteImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); |
|||
for (int x = 0; x < 100; x++) { |
|||
for (int y = 0; y < 100; y++) { |
|||
whiteImage.setRGB(x, y, 0xFFFFFFFF); |
|||
} |
|||
} |
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
ImageIO.write(whiteImage, "jpg", baos); |
|||
|
|||
MockMultipartFile notQrFile = new MockMultipartFile( |
|||
"file", "not_qr.jpg", "image/jpeg", baos.toByteArray() |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/decode").file(notQrFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(500)); |
|||
|
|||
System.out.println("非二维码图片解析正确返回失败"); |
|||
} |
|||
|
|||
// ==================== 生成+保存集成测试(需 OSS + DB) ====================
|
|||
|
|||
@DisplayName("生成标准二维码并保存 - 参数完整") |
|||
@Test |
|||
@Order(5) |
|||
public void testGenerateAndSave() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generate") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.id").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.qrCodeUrl").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.enterpriseNumber").value(TEST_ENTERPRISE_NUMBER)); |
|||
|
|||
System.out.println("标准二维码生成并保存接口测试成功"); |
|||
} |
|||
|
|||
@DisplayName("生成自定义尺寸二维码并保存 - 带分类代码") |
|||
@Test |
|||
@Order(5) |
|||
public void testGenerateWithSizeAndSave() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generateWithSize") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("sortCode", "QR_SORT_CTRL") |
|||
.param("size", "400") |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()); |
|||
|
|||
System.out.println("自定义尺寸二维码生成接口测试成功"); |
|||
} |
|||
|
|||
@DisplayName("生成蓝色二维码并保存") |
|||
@Test |
|||
@Order(5) |
|||
public void testGenerateBlueWithLogoAndSave() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generateBlueWithLogo") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("size", "300") |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data.qrCodeUrl").isNotEmpty()); |
|||
|
|||
System.out.println("蓝色二维码生成并保存接口测试成功"); |
|||
} |
|||
|
|||
@DisplayName("生成蓝色二维码(含分类代码)并保存") |
|||
@Test |
|||
@Order(5) |
|||
public void testGenerateBlueWithLogoAndSortAndSave() throws Exception { |
|||
mockMvc.perform(post(BASE_URL + "/generateBlueWithLogoAndSort") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("content", TEST_CONTENT) |
|||
.param("enterpriseNumber", TEST_ENTERPRISE_NUMBER) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("sortCode", "QR_BLUE_CTRL") |
|||
.param("size", "500") |
|||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()); |
|||
|
|||
System.out.println("蓝色二维码(含分类)生成接口测试成功"); |
|||
} |
|||
} |
|||
@ -0,0 +1,482 @@ |
|||
package com.zdxt.rest.test; |
|||
|
|||
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; |
|||
import cn.dev33.satoken.stp.parameter.SaLoginParameter; |
|||
import cn.dev33.satoken.stp.StpUtil; |
|||
import com.zdxt.enforcementcode.domain.vo.ZdxtFileVo; |
|||
import com.zdxt.enforcementcode.util.ZdxtFileUtil; |
|||
import org.dromara.ZdxtAdminApplication; |
|||
import org.dromara.common.core.domain.model.LoginUser; |
|||
import org.dromara.common.satoken.utils.LoginHelper; |
|||
import org.junit.jupiter.api.*; |
|||
import org.springframework.mock.web.MockHttpServletRequest; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.mock.web.MockMultipartFile; |
|||
import org.springframework.test.web.servlet.MockMvc; |
|||
import org.springframework.test.web.servlet.MvcResult; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.HashSet; |
|||
|
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; |
|||
|
|||
/** |
|||
* ZdxtFileUtilController 附件管理通用接口测试 |
|||
* <p> |
|||
* 分为四类: |
|||
* 1. 上传接口测试(单文件 / 批量 / 带分类代码) |
|||
* 2. 查询接口测试(按 guid / 按 businessId / 按 businessId + sortCode) |
|||
* 3. 删除接口测试(单个 / 批量 / 按 businessId) |
|||
* 4. 参数校验测试 |
|||
* </p> |
|||
* |
|||
* @author zdxt |
|||
*/ |
|||
@SpringBootTest(classes = ZdxtAdminApplication.class) |
|||
@AutoConfigureMockMvc |
|||
@DisplayName("ZdxtFileUtilController 附件管理通用接口测试") |
|||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) |
|||
public class ZdxtFileUtilControllerTest { |
|||
|
|||
@Autowired |
|||
private MockMvc mockMvc; |
|||
|
|||
private static final String BASE_URL = "/common/file"; |
|||
private static final String TEST_BUSINESS_ID = "TEST_CTRL_FILE_001"; |
|||
private static final String TEST_SORT_CODE = "CTRL_SORT"; |
|||
private static final String TEST_CLIENT_ID = "e5cd7e4891bf95d1d19206ce24a7b32e"; |
|||
|
|||
/** 登录后获取的 token */ |
|||
private String token; |
|||
|
|||
/** 记录上传后的 guid,供后续查询/删除使用 */ |
|||
private static String uploadedGuid; |
|||
|
|||
@BeforeEach |
|||
public void setUp() { |
|||
// 初始化 Sa-Token 上下文(非 Web 环境下 StpUtil.login 需要)
|
|||
SaTokenContextMockUtil.setMockContext(); |
|||
|
|||
// 设置模拟请求上下文(UserActionListener 需要从 Request 中获取 User-Agent)
|
|||
MockHttpServletRequest mockRequest = new MockHttpServletRequest(); |
|||
mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); |
|||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); |
|||
|
|||
// 构造测试用登录用户
|
|||
LoginUser loginUser = new LoginUser(); |
|||
loginUser.setUserId(1L); |
|||
loginUser.setUserType("sys_user"); |
|||
loginUser.setTenantId("000000"); |
|||
loginUser.setUsername("test_admin"); |
|||
loginUser.setNickname("测试管理员"); |
|||
loginUser.setDeptId("103"); |
|||
loginUser.setDeptName("测试部门"); |
|||
loginUser.setRolePermission(new HashSet<>()); |
|||
loginUser.setMenuPermission(new HashSet<>()); |
|||
|
|||
// 模拟登录并获取 token
|
|||
SaLoginParameter param = new SaLoginParameter(); |
|||
param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID); |
|||
LoginHelper.login(loginUser, param); |
|||
token = StpUtil.getTokenValue(); |
|||
} |
|||
|
|||
@AfterEach |
|||
public void tearDown() { |
|||
try { |
|||
StpUtil.logout(); |
|||
} catch (Exception ignored) { |
|||
} |
|||
RequestContextHolder.resetRequestAttributes(); |
|||
SaTokenContextMockUtil.clearContext(); |
|||
} |
|||
|
|||
// ==================== 上传接口测试 ====================
|
|||
|
|||
@DisplayName("上传单个文件 - 正常上传") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadFile() throws Exception { |
|||
MockMultipartFile mockFile = new MockMultipartFile( |
|||
"file", "controller-test.txt", "text/plain", |
|||
"Controller upload test content.".getBytes(StandardCharsets.UTF_8) |
|||
); |
|||
|
|||
MvcResult result = mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(mockFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.guid").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.businessId").value(TEST_BUSINESS_ID)) |
|||
.andExpect(jsonPath("$.data.fileName").value("controller-test.txt")) |
|||
.andExpect(jsonPath("$.data.fileSuffix").value(".txt")) |
|||
.andExpect(jsonPath("$.data.fileType").value("txt")) |
|||
.andExpect(jsonPath("$.data.uploadPath").isNotEmpty()) |
|||
.andReturn(); |
|||
|
|||
// 从响应中提取 guid 供后续测试使用
|
|||
String responseBody = result.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
// 简单解析 guid(集成测试中直接使用工具查询更可靠)
|
|||
if (responseBody.contains("\"guid\"")) { |
|||
int start = responseBody.indexOf("\"guid\":\"") + 8; |
|||
int end = responseBody.indexOf("\"", start); |
|||
uploadedGuid = responseBody.substring(start, end); |
|||
} |
|||
System.out.println("单文件上传成功: guid=" + uploadedGuid); |
|||
} |
|||
|
|||
@DisplayName("上传单个文件(带分类代码)- 正常上传") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadFileWithSort() throws Exception { |
|||
MockMultipartFile mockFile = new MockMultipartFile( |
|||
"file", "sorted-file.pdf", "application/pdf", |
|||
"PDF content simulation".getBytes(StandardCharsets.UTF_8) |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/uploadWithSort") |
|||
.file(mockFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("sortCode", TEST_SORT_CODE)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data.guid").isNotEmpty()) |
|||
.andExpect(jsonPath("$.data.fileSuffix").value(".pdf")); |
|||
|
|||
System.out.println("带分类代码的文件上传成功"); |
|||
} |
|||
|
|||
@DisplayName("批量上传文件 - 正常上传") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadBatch() throws Exception { |
|||
MockMultipartFile file1 = new MockMultipartFile( |
|||
"files", "batch1.txt", "text/plain", "batch content 1".getBytes() |
|||
); |
|||
MockMultipartFile file2 = new MockMultipartFile( |
|||
"files", "batch2.txt", "text/plain", "batch content 2".getBytes() |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/uploadBatch") |
|||
.file(file1) |
|||
.file(file2) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isArray()) |
|||
.andExpect(jsonPath("$.data.length()").value(2)); |
|||
|
|||
System.out.println("批量上传成功"); |
|||
} |
|||
|
|||
@DisplayName("批量上传文件(带分类代码)- 正常上传") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadBatchWithSort() throws Exception { |
|||
MockMultipartFile file1 = new MockMultipartFile( |
|||
"files", "batch_sort1.txt", "text/plain", "sort batch 1".getBytes() |
|||
); |
|||
MockMultipartFile file2 = new MockMultipartFile( |
|||
"files", "batch_sort2.txt", "text/plain", "sort batch 2".getBytes() |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/uploadBatchWithSort") |
|||
.file(file1) |
|||
.file(file2) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("sortCode", TEST_SORT_CODE)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isArray()); |
|||
|
|||
System.out.println("带分类代码的批量上传成功"); |
|||
} |
|||
|
|||
// ==================== 参数校验测试 ====================
|
|||
|
|||
@DisplayName("上传文件 - 缺少 businessId 应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadMissingBusinessId() throws Exception { |
|||
MockMultipartFile mockFile = new MockMultipartFile( |
|||
"file", "test.txt", "text/plain", "test".getBytes() |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(mockFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("上传文件 - 空文件应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadEmptyFile() throws Exception { |
|||
MockMultipartFile emptyFile = new MockMultipartFile( |
|||
"file", "empty.txt", "text/plain", new byte[0] |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(emptyFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(500)); |
|||
|
|||
System.out.println("空文件上传正确返回失败"); |
|||
} |
|||
|
|||
@DisplayName("上传文件 - 无后缀文件名应返回失败") |
|||
@Test |
|||
@Order(1) |
|||
public void testUploadFileWithoutSuffix() throws Exception { |
|||
MockMultipartFile noSuffixFile = new MockMultipartFile( |
|||
"file", "noextension", "application/octet-stream", "data".getBytes() |
|||
); |
|||
|
|||
mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(noSuffixFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(500)); |
|||
|
|||
System.out.println("无后缀文件上传正确返回失败"); |
|||
} |
|||
|
|||
// ==================== 查询接口测试 ====================
|
|||
|
|||
@DisplayName("根据 guid 查询附件信息") |
|||
@Test |
|||
@Order(2) |
|||
public void testGetByGuid() throws Exception { |
|||
if (uploadedGuid == null) { |
|||
System.out.println("跳过: 依赖上传测试先执行"); |
|||
return; |
|||
} |
|||
|
|||
mockMvc.perform(get(BASE_URL + "/{guid}", uploadedGuid) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data.guid").value(uploadedGuid)); |
|||
|
|||
System.out.println("按 guid 查询成功: " + uploadedGuid); |
|||
} |
|||
|
|||
@DisplayName("根据 businessId 查询附件列表") |
|||
@Test |
|||
@Order(2) |
|||
public void testListByBusinessId() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/listByBusinessId") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isArray()); |
|||
|
|||
System.out.println("按 businessId 查询成功"); |
|||
} |
|||
|
|||
@DisplayName("根据 businessId 和 sortCode 查询附件列表") |
|||
@Test |
|||
@Order(2) |
|||
public void testListByBusinessIdAndSort() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/listByBusinessIdAndSort") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID) |
|||
.param("sortCode", TEST_SORT_CODE)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data").isArray()); |
|||
|
|||
System.out.println("按 businessId + sortCode 查询成功"); |
|||
} |
|||
|
|||
@DisplayName("查询附件列表 - 缺少 businessId 应返回失败") |
|||
@Test |
|||
@Order(2) |
|||
public void testListByBusinessIdMissingParam() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/listByBusinessId") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@DisplayName("查询附件列表 - 缺少 sortCode 应返回失败") |
|||
@Test |
|||
@Order(2) |
|||
public void testListByBusinessIdAndSortMissingSortCode() throws Exception { |
|||
mockMvc.perform(get(BASE_URL + "/listByBusinessIdAndSort") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
// ==================== 删除接口测试 ====================
|
|||
|
|||
@DisplayName("根据 guid 删除附件") |
|||
@Test |
|||
@Order(3) |
|||
public void testDeleteByGuid() throws Exception { |
|||
// 先上传一个文件用于删除
|
|||
MockMultipartFile mockFile = new MockMultipartFile( |
|||
"file", "to-delete.txt", "text/plain", "delete me".getBytes() |
|||
); |
|||
MvcResult uploadResult = mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(mockFile) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", "TEST_DEL_BIZ")) |
|||
.andExpect(status().isOk()) |
|||
.andReturn(); |
|||
|
|||
String body = uploadResult.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
String guidToDelete = null; |
|||
if (body.contains("\"guid\"")) { |
|||
int start = body.indexOf("\"guid\":\"") + 8; |
|||
int end = body.indexOf("\"", start); |
|||
guidToDelete = body.substring(start, end); |
|||
} |
|||
Assertions.assertNotNull(guidToDelete, "上传后应有 guid"); |
|||
|
|||
// 执行删除
|
|||
mockMvc.perform(delete(BASE_URL + "/{guid}", guidToDelete) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)); |
|||
|
|||
System.out.println("按 guid 删除成功: " + guidToDelete); |
|||
} |
|||
|
|||
@DisplayName("批量删除附件") |
|||
@Test |
|||
@Order(3) |
|||
public void testDeleteByGuids() throws Exception { |
|||
// 先上传两个文件
|
|||
MockMultipartFile file1 = new MockMultipartFile( |
|||
"file", "del-batch1.txt", "text/plain", "batch del 1".getBytes() |
|||
); |
|||
MockMultipartFile file2 = new MockMultipartFile( |
|||
"file", "del-batch2.txt", "text/plain", "batch del 2".getBytes() |
|||
); |
|||
|
|||
MvcResult r1 = mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(file1) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", "TEST_BATCH_DEL")) |
|||
.andExpect(status().isOk()).andReturn(); |
|||
MvcResult r2 = mockMvc.perform(multipart(BASE_URL + "/upload") |
|||
.file(file2) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", "TEST_BATCH_DEL")) |
|||
.andExpect(status().isOk()).andReturn(); |
|||
|
|||
String body1 = r1.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
String body2 = r2.getResponse().getContentAsString(StandardCharsets.UTF_8); |
|||
String guid1 = extractGuid(body1); |
|||
String guid2 = extractGuid(body2); |
|||
|
|||
Assertions.assertNotNull(guid1); |
|||
Assertions.assertNotNull(guid2); |
|||
|
|||
// 批量删除
|
|||
mockMvc.perform(delete(BASE_URL + "/batch/{guids}", guid1 + "," + guid2) |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)); |
|||
|
|||
System.out.println("批量删除成功: " + guid1 + ", " + guid2); |
|||
} |
|||
|
|||
@DisplayName("按 businessId 删除所有关联附件") |
|||
@Test |
|||
@Order(4) |
|||
public void testDeleteByBusinessId() throws Exception { |
|||
mockMvc.perform(delete(BASE_URL + "/deleteByBusinessId") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)); |
|||
|
|||
// 验证删除后查询为空
|
|||
mockMvc.perform(get(BASE_URL + "/listByBusinessId") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID) |
|||
.param("businessId", TEST_BUSINESS_ID)) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.data").isArray()) |
|||
.andExpect(jsonPath("$.data.length()").value(0)); |
|||
|
|||
System.out.println("按 businessId 删除所有附件成功"); |
|||
} |
|||
|
|||
@DisplayName("按 businessId 删除 - 缺少参数应返回失败") |
|||
@Test |
|||
@Order(4) |
|||
public void testDeleteByBusinessIdMissingParam() throws Exception { |
|||
mockMvc.perform(delete(BASE_URL + "/deleteByBusinessId") |
|||
.header("Authorization", "Bearer " + token) |
|||
.header("clientid", TEST_CLIENT_ID)) |
|||
.andDo(print()) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
// ==================== 工具方法 ====================
|
|||
|
|||
/** |
|||
* 从 JSON 响应中简单提取 guid 值 |
|||
*/ |
|||
private String extractGuid(String responseBody) { |
|||
if (responseBody != null && responseBody.contains("\"guid\"")) { |
|||
int start = responseBody.indexOf("\"guid\":\"") + 8; |
|||
int end = responseBody.indexOf("\"", start); |
|||
return responseBody.substring(start, end); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue