jinshihui преди 2 седмици
родител
ревизия
21938120e0

+ 3 - 3
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java

@@ -313,17 +313,17 @@ public class MaTechnicianController extends BaseController {
     }
 
     /**
-     * 申请技师文件
+     * 申请入驻
      *
      * @param req
      * @return Result<?>
      */
     @PostMapping("/applyFile")
-    @ApiOperation("申请技师文件")
+    @ApiOperation("申请入驻")
     public Result applyFile(@RequestBody MerchantApplyFileRequestDto req) {
         try {
             maTechnicianService.applyFile(req);
-            return Result.ok("上传成功");
+            return Result.ok("申请入驻成功");
         } catch (Exception e) {
             e.printStackTrace();
             throw new RuntimeException(e);

+ 7 - 5
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileDto.java

@@ -2,14 +2,11 @@ package com.ylx.massage.domain.dto;
 
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 public class MerchantApplyFileDto {
 
-    /**
-     * 商户ID。
-     */
-    private Integer merchantId;
-
     /**
      * 文件类型。
      * 1:形象照 2:生活照 3:宣传视频 4-身份证正面 5-身份证反面 6-手持身份证 7-健康证 8-从业资格证,9-无犯罪证明,10-承诺书,11-承诺录音,12-承诺录像,13-其他
@@ -35,4 +32,9 @@ public class MerchantApplyFileDto {
      * 文件 MIME 类型,如 image/jpeg。
      */
     private String contentType;
+
+    /**
+     * 同一文件类型下的多个文件。
+     */
+    private List<MerchantApplyFileDto> files;
 }

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileRequestDto.java

@@ -8,7 +8,7 @@ import java.util.List;
 @Data
 public class MerchantApplyFileRequestDto {
     /**
-     * 商信息
+     * 商信息
      */
     private MaTechnician technician;
 

+ 99 - 37
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -202,32 +202,99 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
      * @param req
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void applyFile(MerchantApplyFileRequestDto req) {
-        if (req == null || req.getReq().isEmpty()) {
-            throw new RuntimeException("请上传申请入驻文件");
+        if (req == null) {
+            throw new ServiceException("上传参数不能为空");
         }
-        for (MerchantApplyFileDto re : req.getReq()) {
-            LambdaQueryWrapper<MerchantApplyFile> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(MerchantApplyFile::getMerchantId, re.getMerchantId());
-            queryWrapper.eq(MerchantApplyFile::getFileType, re.getFileType());
-            MerchantApplyFile merchantApplyFile = merchantApplyFileMapper.selectOne(queryWrapper);
-            if (merchantApplyFile != null) {
-                // 删除原有文件
-                merchantApplyFileMapper.deleteById(merchantApplyFile);
-            } else {
-                //插入文件信息
-                MerchantApplyFile merchantApplyFile1 = new MerchantApplyFile();
-                BeanUtils.copyProperties(re, merchantApplyFile1);
-                merchantApplyFile1.setCreateBy(re.getMerchantId().toString());
-                merchantApplyFile1.setUpdateBy(re.getMerchantId().toString());
-                merchantApplyFileMapper.insert(merchantApplyFile1);
+        Integer merchantId = resolveMerchantId(req.getTechnician());
+        List<MerchantApplyFileDto> files = resolveApplyFiles(merchantId, req.getReq());
+        if (CollectionUtils.isEmpty(files)) {
+            throw new ServiceException("请上传申请入驻文件");
+        }
+        replaceApplyFiles(merchantId, files);
+        updateTechnicianBaseInfo(req.getTechnician());
+    }
+
+    /**
+     * 将新数组格式和旧扁平格式统一转换为单文件记录。
+     *
+     * @param merchantId 商户ID
+     * @param reqFiles   入驻资料文件
+     * @return List<MerchantApplyFileDto> 转换后的文件记录
+     */
+    private List<MerchantApplyFileDto> resolveApplyFiles(Integer merchantId, List<MerchantApplyFileDto> reqFiles) {
+        if (CollectionUtils.isEmpty(reqFiles)) {
+            return Collections.emptyList();
+        }
+        List<MerchantApplyFileDto> files = new ArrayList<>();
+        for (MerchantApplyFileDto item : reqFiles) {
+            if (item == null) {
+                throw new ServiceException("入驻资料文件不能为空");
+            }
+            if (CollectionUtils.isEmpty(item.getFiles())) {
+                checkApplyFileParam(merchantId, item);
+                files.add(item);
+                continue;
+            }
+            checkApplyFileGroupParam(item);
+            for (MerchantApplyFileDto child : item.getFiles()) {
+                files.add(buildApplyFileFromGroup(merchantId, item, child));
+            }
+        }
+        return files;
+    }
+
+    /**
+     * 校验文件组参数是否符合要求。
+     *
+     * @param group
+     */
+    private void checkApplyFileGroupParam(MerchantApplyFileDto group) {
+        if (StringUtils.isBlank(group.getFileType())) {
+            throw new ServiceException("文件类型不能为空");
+        }
+    }
+
+    private MerchantApplyFileDto buildApplyFileFromGroup(Integer merchantId, MerchantApplyFileDto group, MerchantApplyFileDto child) {
+        if (child == null) {
+            throw new ServiceException("入驻资料文件不能为空");
+        }
+        MerchantApplyFileDto file = new MerchantApplyFileDto();
+        BeanUtils.copyProperties(child, file);
+        file.setFiles(null);
+        if (StringUtils.isBlank(file.getFileType())) {
+            file.setFileType(group.getFileType());
+        } else if (!group.getFileType().equals(file.getFileType())) {
+            throw new ServiceException("同一组文件类型必须一致");
+        }
+        checkApplyFileParam(merchantId, file);
+        return file;
+    }
+
+    /**
+     * 按本次提交的文件类型整组替换旧文件。
+     *
+     * @param merchantId 商户ID
+     * @param files      入驻资料文件
+     */
+    private void replaceApplyFiles(Integer merchantId, List<MerchantApplyFileDto> files) {
+        Map<String, List<MerchantApplyFileDto>> filesByType = files.stream()
+                .collect(Collectors.groupingBy(MerchantApplyFileDto::getFileType, LinkedHashMap::new, Collectors.toList()));
+        for (Map.Entry<String, List<MerchantApplyFileDto>> entry : filesByType.entrySet()) {
+            LambdaQueryWrapper<MerchantApplyFile> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(MerchantApplyFile::getMerchantId, merchantId).eq(MerchantApplyFile::getFileType, entry.getKey());
+            merchantApplyFileMapper.delete(deleteWrapper);
+            for (MerchantApplyFileDto file : entry.getValue()) {
+                MerchantApplyFile applyFile = new MerchantApplyFile();
+                BeanUtils.copyProperties(file, applyFile);
+                applyFile.setMerchantId(merchantId);
+                applyFile.setCreateBy(merchantId.toString());
+                applyFile.setUpdateBy(merchantId.toString());
+                applyFile.setIsDelete(NOT_DELETED);
+                merchantApplyFileMapper.insert(applyFile);
             }
         }
-        LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.eq(MaTechnician::getId, req.getTechnician().getId());
-        updateWrapper.set(MaTechnician::getTeNickName, req.getTechnician().getTeNickName());
-        updateWrapper.set(MaTechnician::getTeBrief, req.getTechnician().getTeBrief());
-        maTechnicianService.update(updateWrapper);
     }
 
     /**
@@ -245,7 +312,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             throw new ServiceException("修改参数不能为空");
         }
         MaTechnician technician = req.getTechnician();
-        Integer merchantId = resolveMerchantId(technician, req.getReq());
+        Integer merchantId = resolveMerchantId(technician);
         updateTechnicianBaseInfo(technician);
         upsertApplyFiles(merchantId, req.getReq());
     }
@@ -312,18 +379,16 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         }
     }
 
-    private Integer resolveMerchantId(MaTechnician technician, List<MerchantApplyFileDto> files) {
+    /**
+     * 从商户基础信息中解析商户ID。
+     *
+     * @param technician 商户基础信息
+     * @return Integer 商户ID
+     */
+    private Integer resolveMerchantId(MaTechnician technician) {
         if (technician != null && technician.getId() != null) {
             return technician.getId();
         }
-        if (!CollectionUtils.isEmpty(files)) {
-            return files.stream()
-                    .filter(Objects::nonNull)
-                    .map(MerchantApplyFileDto::getMerchantId)
-                    .filter(Objects::nonNull)
-                    .findFirst()
-                    .orElseThrow(() -> new ServiceException("商户ID不能为空"));
-        }
         throw new ServiceException("商户ID不能为空");
     }
 
@@ -337,9 +402,6 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         if (StringUtils.isBlank(file.getFileUrl())) {
             throw new ServiceException("文件访问地址不能为空");
         }
-        if (file.getMerchantId() != null && !merchantId.equals(file.getMerchantId())) {
-            throw new ServiceException("文件商户ID与当前商户不一致");
-        }
     }
 
     /**
@@ -1266,10 +1328,10 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
                         if (!forceConfirm) {
                             // 返回特定错误码或数据结构,告诉前端弹出“我在想想/确认下线”的模态框
                             return Result.ok("平台对您的在线时间做了约定,每日在线需满足"
-                                    + (requiredMinutes / 60) + "小时,距离您下线时间还剩余" + ((requiredMinutes / 60) - minutesOnline/60) + "小时"
+                                    + (requiredMinutes / 60) + "小时,距离您下线时间还剩余" + ((requiredMinutes / 60) - minutesOnline / 60) + "小时"
                                     + "不满足在线时间将收到平台处罚,是否确认下线?");
                         }
-                    }else{
+                    } else {
                         // 情况: 已满足在线时间,且用户点击点击了“确认下线”,允许通过
                         return Result.ok("状态已切换成功");
                     }

+ 93 - 4
nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/MaTechnicianServiceImplTest.java

@@ -17,16 +17,20 @@ import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -88,8 +92,8 @@ public class MaTechnicianServiceImplTest {
         technician.setTeBrief("新简介");
         request.setTechnician(technician);
 
-        MerchantApplyFileDto updateFile = buildFile(7, "1", "new-image.jpg", "https://file/new-image.jpg");
-        MerchantApplyFileDto insertFile = buildFile(7, "2", "life.jpg", "https://file/life.jpg");
+        MerchantApplyFileDto updateFile = buildFile("1", "new-image.jpg", "https://file/new-image.jpg");
+        MerchantApplyFileDto insertFile = buildFile("2", "life.jpg", "https://file/life.jpg");
         request.setReq(Arrays.asList(updateFile, insertFile));
 
         service.updateTechnician(request);
@@ -117,6 +121,85 @@ public class MaTechnicianServiceImplTest {
         assertEquals(0, insertedFile.getIsDelete());
     }
 
+    @Test
+    public void merchantApplyFileDtoDoesNotExposeMerchantIdInRequestPayload() {
+        for (Field field : MerchantApplyFileDto.class.getDeclaredFields()) {
+            assertNotEquals("merchantId", field.getName());
+        }
+    }
+
+    @Test
+    public void applyFileReplacesRequestedTypesAndInsertsAllFilesInGroups() {
+        MaTechnicianMapper maTechnicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper merchantApplyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(maTechnicianMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1);
+        MaTechnicianServiceImpl service = buildService(maTechnicianMapper, merchantApplyFileMapper);
+
+        MerchantApplyFileRequestDto request = new MerchantApplyFileRequestDto();
+        MaTechnician technician = new MaTechnician();
+        technician.setId(7);
+        technician.setTeNickName("新昵称");
+        technician.setTeBrief("新简介");
+        request.setTechnician(technician);
+        request.setReq(Arrays.asList(
+                buildFileGroup("1",
+                        buildFile(null, "portrait-1.jpg", "https://file/portrait-1.jpg"),
+                        buildFile(null, "portrait-2.jpg", "https://file/portrait-2.jpg")),
+                buildFileGroup("3",
+                        buildFile(null, "video.mp4", "https://file/video.mp4"))
+        ));
+
+        service.applyFile(request);
+
+        verify(merchantApplyFileMapper, times(2)).delete(any(LambdaQueryWrapper.class));
+        ArgumentCaptor<MerchantApplyFile> insertCaptor = ArgumentCaptor.forClass(MerchantApplyFile.class);
+        verify(merchantApplyFileMapper, times(3)).insert(insertCaptor.capture());
+        List<MerchantApplyFile> insertedFiles = insertCaptor.getAllValues();
+        assertEquals("1", insertedFiles.get(0).getFileType());
+        assertEquals("portrait-1.jpg", insertedFiles.get(0).getFileName());
+        assertEquals("1", insertedFiles.get(1).getFileType());
+        assertEquals("portrait-2.jpg", insertedFiles.get(1).getFileName());
+        assertEquals("3", insertedFiles.get(2).getFileType());
+        assertEquals("video.mp4", insertedFiles.get(2).getFileName());
+        insertedFiles.forEach(file -> {
+            assertEquals(7, file.getMerchantId());
+            assertEquals("7", file.getCreateBy());
+            assertEquals("7", file.getUpdateBy());
+            assertEquals(0, file.getIsDelete());
+        });
+    }
+
+    @Test
+    public void applyFileUsesTechnicianIdWhenReqHasNoMerchantId() {
+        MaTechnicianMapper maTechnicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper merchantApplyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(maTechnicianMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1);
+        MaTechnicianServiceImpl service = buildService(maTechnicianMapper, merchantApplyFileMapper);
+
+        MerchantApplyFileRequestDto request = new MerchantApplyFileRequestDto();
+        MaTechnician technician = new MaTechnician();
+        technician.setId(7);
+        technician.setTeNickName("新昵称");
+        technician.setTeBrief("新简介");
+        request.setTechnician(technician);
+        request.setReq(Collections.singletonList(
+                buildFileGroup("2",
+                        buildFile(null, "life.jpg", "https://file/life.jpg"))
+        ));
+
+        service.applyFile(request);
+
+        ArgumentCaptor<MerchantApplyFile> insertCaptor = ArgumentCaptor.forClass(MerchantApplyFile.class);
+        verify(merchantApplyFileMapper).insert(insertCaptor.capture());
+        MerchantApplyFile insertedFile = insertCaptor.getValue();
+        assertEquals(7, insertedFile.getMerchantId());
+        assertEquals("2", insertedFile.getFileType());
+        assertEquals("life.jpg", insertedFile.getFileName());
+        assertEquals("7", insertedFile.getCreateBy());
+        assertEquals("7", insertedFile.getUpdateBy());
+        assertEquals(0, insertedFile.getIsDelete());
+    }
+
     @Test
     public void getTechnicianInfoFindsMerchantByOpenidWithFiles() {
         MaTechnicianMapper maTechnicianMapper = mock(MaTechnicianMapper.class);
@@ -187,9 +270,8 @@ public class MaTechnicianServiceImplTest {
         return file;
     }
 
-    private MerchantApplyFileDto buildFile(Integer merchantId, String fileType, String fileName, String fileUrl) {
+    private MerchantApplyFileDto buildFile(String fileType, String fileName, String fileUrl) {
         MerchantApplyFileDto file = new MerchantApplyFileDto();
-        file.setMerchantId(merchantId);
         file.setFileType(fileType);
         file.setFileName(fileName);
         file.setFileUrl(fileUrl);
@@ -197,4 +279,11 @@ public class MaTechnicianServiceImplTest {
         file.setContentType("image/jpeg");
         return file;
     }
+
+    private MerchantApplyFileDto buildFileGroup(String fileType, MerchantApplyFileDto... files) {
+        MerchantApplyFileDto group = new MerchantApplyFileDto();
+        group.setFileType(fileType);
+        group.setFiles(Arrays.asList(files));
+        return group;
+    }
 }