Pārlūkot izejas kodu

开发了删除动态接口

jinshihui 6 dienas atpakaļ
vecāks
revīzija
0d13fe9a62
22 mainītis faili ar 1167 papildinājumiem un 14 dzēšanām
  1. 45 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
  2. 27 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TechnicianMomentController.java
  3. 19 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTechnician.java
  4. 3 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantApplyFile.java
  5. 17 4
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MomentMedia.java
  6. 24 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantProfileAuditDTO.java
  7. 21 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantProfileSubmitDTO.java
  8. 25 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileFileGroupVO.java
  9. 25 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileFileVO.java
  10. 19 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileTextItemVO.java
  11. 29 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileVO.java
  12. 6 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentListVO.java
  13. 6 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java
  14. 9 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/ITechnicianMomentService.java
  15. 523 6
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java
  16. 62 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TechnicianMomentServiceImpl.java
  17. 1 0
      nightFragrance-massage/src/main/resources/mapper/massage/MomentMediaMapper.xml
  18. 1 1
      nightFragrance-massage/src/main/resources/mapper/massage/TechnicianMomentMapper.xml
  19. 31 0
      nightFragrance-massage/src/test/java/com/ylx/massage/mapper/MomentMediaMapperXmlTest.java
  20. 8 0
      nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TechnicianMomentMapperXmlTest.java
  21. 157 1
      nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/MaTechnicianServiceImplTest.java
  22. 109 0
      nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/TechnicianMomentServiceImplTest.java

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

@@ -426,6 +426,51 @@ public class MaTechnicianController extends BaseController {
     /**
      * 查询技师列表
      */
+    @GetMapping("/profile")
+    @ApiOperation("查询商户端我的资料")
+    public R<MerchantProfileVO> getMerchantProfile(@RequestParam("merchantId") Integer merchantId) {
+        try {
+            return R.ok(maTechnicianService.getMerchantProfile(merchantId));
+        } catch (Exception e) {
+            log.error("查询商户端我的资料失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @PostMapping("/profile/submit")
+    @ApiOperation("提交商户端我的资料修改审核")
+    public R<?> submitMerchantProfile(@Valid @RequestBody MerchantProfileSubmitDTO req) {
+        try {
+            maTechnicianService.submitMerchantProfile(req);
+            return R.ok("提交成功,请等待审核");
+        } catch (Exception e) {
+            log.error("提交商户端我的资料修改审核失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+
+    /**
+     * 后台审核商户端我的资料修改
+     *
+     * @param merchantId
+     * @param req
+     * @return
+     */
+    @PutMapping("/profile/audit/{merchantId}")
+    @ApiOperation("后台审核商户端我的资料修改")
+    @PreAuthorize("@ss.hasPermi('technician:technician:edit')")
+    @Log(title = "商户我的资料审核", businessType = BusinessType.UPDATE)
+    public R<Void> auditMerchantProfile(@PathVariable("merchantId") Integer merchantId, @Valid @RequestBody MerchantProfileAuditDTO req) {
+        try {
+            LoginUser loginUser = getLoginUser();
+            return toR(maTechnicianService.auditMerchantProfile(merchantId, req, loginUser));
+        } catch (Exception e) {
+            log.error("后台审核商户端我的资料修改失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
     @PreAuthorize("@ss.hasPermi('technician:technician:list')")
     @GetMapping("/list")
     public R<TableDataInfo> list(MaTechnician maTechnician) {

+ 27 - 0
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TechnicianMomentController.java

@@ -6,6 +6,7 @@ import com.ylx.common.core.controller.BaseController;
 import com.ylx.common.core.domain.R;
 import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.enums.BusinessType;
+import com.ylx.common.exception.ServiceException;
 import com.ylx.massage.domain.dto.MomentAuditDTO;
 import com.ylx.massage.domain.dto.MomentManageQueryDTO;
 import com.ylx.massage.domain.dto.MomentRecommendDTO;
@@ -272,6 +273,32 @@ public class TechnicianMomentController extends BaseController {
         }
     }
 
+    /**
+     * 删除我的动态
+     *
+     * @param momentId 动态ID
+     * @return R<Boolean> 是否删除成功
+     */
+    @DeleteMapping("/{momentId}")
+    @ApiOperation("删除我的动态")
+    public R<Boolean> deleteMyMoment(@ApiParam("动态ID") @PathVariable Long momentId) {
+        try {
+            WxLoginUser wxLoginUser = getWxLoginUser();
+            String openId = wxLoginUser.getCOpenid();
+            log.info("删除我的动态,openId:{},动态ID:{}", openId, momentId);
+
+            Boolean result = momentService.deleteMyMoment(momentId, openId);
+            return R.ok(result, "动态删除成功");
+        } catch (ServiceException e) {
+            e.printStackTrace();
+            log.warn("删除我的动态业务校验失败,动态ID:{},原因:{}", momentId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.fail("删除动态失败");
+        }
+    }
+
 
     /**
      * 根据技师ID查询技师的动态列表(已发布的动态:审核中+审核通过)

+ 19 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTechnician.java

@@ -200,6 +200,24 @@ public class MaTechnician extends BaseEntity {
     @TableField("te_brief")
     private String teBrief;
 
+    @TableField("pending_te_nick_name")
+    private String pendingTeNickName;
+
+    @TableField("te_nick_name_audit_status")
+    private Integer teNickNameAuditStatus;
+
+    @TableField("te_nick_name_audit_remark")
+    private String teNickNameAuditRemark;
+
+    @TableField("pending_te_brief")
+    private String pendingTeBrief;
+
+    @TableField("te_brief_audit_status")
+    private Integer teBriefAuditStatus;
+
+    @TableField("te_brief_audit_remark")
+    private String teBriefAuditRemark;
+
     /**
      * 健康证过期日期
      */
@@ -230,7 +248,7 @@ public class MaTechnician extends BaseEntity {
      * 商户管理状态: 0-正常, 1-限制接单, 2-冻结, 3-注销
      */
     @Excel(name = "商户管理状态: 0-正常, 1-限制接单, 2-冻结, 3-注销")
-    private String merchantStatus;
+    private Integer merchantStatus;
 
     /**
      * 商户接单状态(0:休息中 1:在线接单)

+ 3 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantApplyFile.java

@@ -69,6 +69,9 @@ public class MerchantApplyFile implements Serializable {
     @TableField("content_type")
     private String contentType;
 
+    @TableField("apply_batch_no")
+    private String applyBatchNo;
+
     /**
      * 审核状态:1-审核通过,2-审核未通过
      */
@@ -114,4 +117,3 @@ public class MerchantApplyFile implements Serializable {
     private Integer isDelete;
 
 }
-

+ 17 - 4
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MomentMedia.java

@@ -2,6 +2,7 @@ package com.ylx.massage.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import java.time.LocalDateTime;
@@ -39,10 +40,6 @@ public class MomentMedia {
      */
     private Integer sortOrder;
 
-    /**
-     * 创建时间
-     */
-    private LocalDateTime createTime;
 
     /**
      * 文件大小(字节)
@@ -53,4 +50,20 @@ public class MomentMedia {
      * 文件格式:jpg/png/mp4等
      */
     private String fileFormat;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 是否删除(0否1是)
+     */
+    @TableLogic
+    private Integer isDelete;
 }

+ 24 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantProfileAuditDTO.java

@@ -0,0 +1,24 @@
+package com.ylx.massage.domain.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDate;
+
+@Data
+public class MerchantProfileAuditDTO {
+
+    /**
+     * 2-审核通过 3-审核驳回
+     */
+    @NotNull(message = "审核状态不能为空")
+    private Integer auditStatus;
+
+    private LocalDate idCardExpirationDate;
+
+    private LocalDate healthCertificateExpirationDate;
+
+    private LocalDate qualificationCertificateExpirationDate;
+
+    private String auditRemark;
+}

+ 21 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantProfileSubmitDTO.java

@@ -0,0 +1,21 @@
+package com.ylx.massage.domain.dto;
+
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class MerchantProfileSubmitDTO {
+
+    @NotNull(message = "商户ID不能为空")
+    private Integer merchantId;
+
+    private String nickName;
+
+    private String brief;
+
+    @Valid
+    private List<MerchantApplyFileDto> files;
+}

+ 25 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileFileGroupVO.java

@@ -0,0 +1,25 @@
+package com.ylx.massage.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class MerchantProfileFileGroupVO {
+
+    private String fileType;
+
+    private String fileTypeName;
+
+    private List<MerchantProfileFileVO> officialFiles;
+
+    private List<MerchantProfileFileVO> pendingFiles;
+
+    private Integer auditStatus;
+
+    private String auditStatusText;
+
+    private String auditRemark;
+
+    private Boolean editable;
+}

+ 25 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileFileVO.java

@@ -0,0 +1,25 @@
+package com.ylx.massage.domain.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class MerchantProfileFileVO {
+
+    private Long id;
+
+    private String fileName;
+
+    private String fileUrl;
+
+    private BigDecimal fileSize;
+
+    private String contentType;
+
+    private String applyBatchNo;
+
+    private Integer auditStatus;
+
+    private String auditRemark;
+}

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileTextItemVO.java

@@ -0,0 +1,19 @@
+package com.ylx.massage.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class MerchantProfileTextItemVO {
+
+    private String value;
+
+    private String pendingValue;
+
+    private Integer auditStatus;
+
+    private String auditStatusText;
+
+    private String auditRemark;
+
+    private Boolean editable;
+}

+ 29 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MerchantProfileVO.java

@@ -0,0 +1,29 @@
+package com.ylx.massage.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class MerchantProfileVO {
+
+    private Integer merchantId;
+
+    private String name;
+
+    private Integer sex;
+
+    private String phone;
+
+    private String address;
+
+    private String areaCode;
+
+    private String avatar;
+
+    private MerchantProfileTextItemVO nickName;
+
+    private MerchantProfileTextItemVO brief;
+
+    private List<MerchantProfileFileGroupVO> fileGroups;
+}

+ 6 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentListVO.java

@@ -51,6 +51,12 @@ public class MomentListVO {
     @ApiModelProperty("封面图URL")
     private String coverUrl;
 
+    /**
+     * 地址
+     */
+    @ApiModelProperty("地址")
+    private String location;
+
     /**
      * 浏览量
      */

+ 6 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java

@@ -211,6 +211,12 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
      */
     void updateTechnician(MerchantApplyFileRequestDto req);
 
+    MerchantProfileVO getMerchantProfile(Integer merchantId);
+
+    void submitMerchantProfile(MerchantProfileSubmitDTO dto);
+
+    int auditMerchantProfile(Integer merchantId, MerchantProfileAuditDTO dto, LoginUser loginUser);
+
     /**
      * 技师状态切换
      *

+ 9 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/ITechnicianMomentService.java

@@ -97,6 +97,15 @@ public interface ITechnicianMomentService extends IService<TechnicianMoment> {
      */
     Page<com.ylx.massage.domain.vo.MyMomentVO> getMyMoments(String openId, Integer pageNum, Integer pageSize);
 
+    /**
+     * 删除我的动态。
+     *
+     * @param momentId 动态ID
+     * @param openId   当前登录商户OpenID
+     * @return 是否成功
+     */
+    Boolean deleteMyMoment(Long momentId, String openId);
+
     /**
      * 根据技师ID查询动态列表(已发布的动态:审核中+审核通过)
      *

+ 523 - 6
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -71,8 +71,25 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     private static final int NS_STATUS_NOT_ON_DUTY = -1;
     private static final int DEFAULT_STAT_VALUE = 0;
     private static final Integer NOT_DELETED = 0;
-    private static final String MERCHANT_STATUS_NORMAL = "0";
+    private static final Integer MERCHANT_STATUS_NORMAL = 0;
     private static final String PASSWORD = "123456";
+    private static final int PROFILE_AUDIT_PENDING = 0;
+    private static final int PROFILE_AUDIT_APPROVED = 1;
+    private static final int PROFILE_AUDIT_REJECTED = 2;
+    private static final Set<String> PROFILE_FILE_TYPES = new LinkedHashSet<>(Arrays.asList(
+            PORTRAIT.getCode(),
+            LIFE_PHOTO.getCode(),
+            PROMOTION_VIDEO.getCode(),
+            ID_CARD_FRONT.getCode(),
+            ID_CARD_BACK.getCode(),
+            ID_CARD_HANDHELD.getCode(),
+            HEALTH_CERT.getCode(),
+            QUALIFICATION_CERT.getCode(),
+            NO_CRIME_RECORD.getCode(),
+            COMMITMENT_LETTER.getCode(),
+            COMMITMENT_AUDIO.getCode(),
+            COMMITMENT_VIDEO.getCode()
+    ));
 
     @Resource
     private MaTechnicianMapper maTechnicianMapper;
@@ -284,11 +301,510 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             throw new ServiceException("修改参数不能为空");
         }
         MaTechnician technician = req.getTechnician();
-        Integer merchantId = resolveMerchantId(technician);
-        updateTechnicianBaseInfo(technician);
+        MerchantProfileSubmitDTO submitDTO = new MerchantProfileSubmitDTO();
+        submitDTO.setMerchantId(resolveMerchantId(technician));
+        if (technician != null) {
+            submitDTO.setNickName(technician.getTeNickName());
+            submitDTO.setBrief(technician.getTeBrief());
+        }
         // 处理入驻资料文件
-        List<MerchantApplyFileDto> files = resolveApplyFiles(req.getReq());
-        replaceApplyFilesBySubmittedTypes(merchantId, files);
+        submitDTO.setFiles(req.getReq());
+        submitMerchantProfile(submitDTO);
+    }
+
+    @Override
+    public MerchantProfileVO getMerchantProfile(Integer merchantId) {
+        MaTechnician merchant = getExistingMerchant(merchantId);
+        List<MerchantApplyFile> files = listMerchantApplyFiles(merchantId);
+        MerchantProfileVO profile = new MerchantProfileVO();
+        profile.setMerchantId(merchant.getId());
+        profile.setName(merchant.getTeName());
+        profile.setSex(merchant.getTeSex());
+        profile.setPhone(merchant.getTePhone());
+        profile.setAddress(merchant.getTeAddress());
+        profile.setAreaCode(merchant.getTeAreaCode());
+        profile.setAvatar(merchant.getTeAvatar());
+        profile.setNickName(buildTextItem(merchant.getTeNickName(), merchant.getPendingTeNickName(),
+                merchant.getTeNickNameAuditStatus(), merchant.getTeNickNameAuditRemark()));
+        profile.setBrief(buildTextItem(merchant.getTeBrief(), merchant.getPendingTeBrief(),
+                merchant.getTeBriefAuditStatus(), merchant.getTeBriefAuditRemark()));
+        profile.setFileGroups(buildFileGroups(files));
+        return profile;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitMerchantProfile(MerchantProfileSubmitDTO dto) {
+        if (dto == null) {
+            throw new ServiceException("修改参数不能为空");
+        }
+        checkProfileSubmitParam(dto);
+        MaTechnician merchant = getExistingMerchant(dto.getMerchantId());
+        boolean changed = submitProfileTextFields(merchant, dto);
+        List<MerchantApplyFileDto> files = resolveApplyFiles(dto.getFiles());
+        if (!CollectionUtils.isEmpty(files)) {
+            submitProfileFiles(merchant.getId(), files);
+            changed = true;
+        }
+        if (!changed) {
+            throw new ServiceException("请至少提交一项资料修改");
+        }
+    }
+
+    private void checkProfileSubmitParam(MerchantProfileSubmitDTO dto) {
+        if (dto.getNickName() != null) {
+            checkProfileTextValue(dto.getNickName(), "昵称");
+        }
+        if (dto.getBrief() != null) {
+            checkProfileTextValue(dto.getBrief(), "简介");
+        }
+        resolveApplyFiles(dto.getFiles());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int auditMerchantProfile(Integer merchantId, MerchantProfileAuditDTO dto, LoginUser loginUser) {
+        MaTechnician merchant = getExistingMerchant(merchantId);
+        if (dto != null && (AUDIT_APPROVED == dto.getAuditStatus() || AUDIT_REJECTED == dto.getAuditStatus())) {
+            String profileAuditRemark = dto.getAuditRemark() == null ? "" : dto.getAuditRemark().trim();
+            if (AUDIT_REJECTED == dto.getAuditStatus() && StringUtils.isBlank(profileAuditRemark)) {
+                throw new ServiceException("审核驳回时审核备注不能为空");
+            }
+            if (AUDIT_APPROVED == dto.getAuditStatus()) {
+                checkProfileAuditExpirationDates(dto);
+                return approveAllPendingProfile(merchant, dto, profileAuditRemark, loginUser);
+            }
+            return rejectAllPendingProfile(merchant, profileAuditRemark, loginUser);
+        }
+        if (dto == null) {
+            throw new ServiceException("审核参数不能为空");
+        }
+        checkEnumValue(dto.getAuditStatus(), "审核状态", PROFILE_AUDIT_APPROVED, PROFILE_AUDIT_REJECTED);
+        String auditRemark = dto.getAuditRemark() == null ? "" : dto.getAuditRemark().trim();
+        if (PROFILE_AUDIT_REJECTED == dto.getAuditStatus() && StringUtils.isBlank(auditRemark)) {
+            throw new ServiceException("审核驳回时审核备注不能为空");
+        }
+        if (PROFILE_AUDIT_APPROVED == dto.getAuditStatus() || PROFILE_AUDIT_REJECTED == dto.getAuditStatus()) {
+            throw new ServiceException("审核状态请使用2-审核通过或3-审核驳回");
+        }
+        throw new ServiceException("审核资料类型不正确");
+    }
+
+    private void checkProfileAuditExpirationDates(MerchantProfileAuditDTO dto) {
+        if (dto.getIdCardExpirationDate() == null
+                || dto.getHealthCertificateExpirationDate() == null
+                || dto.getQualificationCertificateExpirationDate() == null) {
+            throw new ServiceException("审核通过时,身份证到期日期、健康证到期日期、从业资格证到期日期不能为空");
+        }
+    }
+
+    private int approveAllPendingProfile(MaTechnician merchant, MerchantProfileAuditDTO dto, String auditRemark, LoginUser loginUser) {
+        List<MerchantApplyFile> pendingFiles = listPendingProfileFiles(merchant.getId());
+        boolean hasPendingText = hasPendingProfileText(merchant);
+        if (!hasPendingText && CollectionUtils.isEmpty(pendingFiles)) {
+            throw new ServiceException("当前商户没有待审核资料");
+        }
+        int rows = approveProfileTechnicianInfo(merchant, dto, auditRemark, loginUser);
+        if (!CollectionUtils.isEmpty(pendingFiles)) {
+            rows += approvePendingProfileFiles(merchant.getId(), pendingFiles, auditRemark, loginUser);
+        }
+        return rows;
+    }
+
+    private int rejectAllPendingProfile(MaTechnician merchant, String auditRemark, LoginUser loginUser) {
+        List<MerchantApplyFile> pendingFiles = listPendingProfileFiles(merchant.getId());
+        boolean hasPendingText = hasPendingProfileText(merchant);
+        if (!hasPendingText && CollectionUtils.isEmpty(pendingFiles)) {
+            throw new ServiceException("当前商户没有待审核资料");
+        }
+        int rows = 0;
+        if (hasPendingText) {
+            rows += rejectProfileTextInfo(merchant, auditRemark, loginUser);
+        }
+        if (!CollectionUtils.isEmpty(pendingFiles)) {
+            rows += rejectPendingProfileFiles(merchant.getId(), auditRemark, loginUser);
+        }
+        return rows;
+    }
+
+    private boolean hasPendingProfileText(MaTechnician merchant) {
+        return Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())
+                || Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus());
+    }
+
+    private List<MerchantApplyFile> listPendingProfileFiles(Integer merchantId) {
+        LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING);
+        return merchantApplyFileMapper.selectList(queryWrapper);
+    }
+
+    private int approveProfileTechnicianInfo(MaTechnician merchant, MerchantProfileAuditDTO dto, String auditRemark, LoginUser loginUser) {
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MaTechnician::getId, merchant.getId())
+                .set(MaTechnician::getIdCardExpirationDate, dto.getIdCardExpirationDate())
+                .set(MaTechnician::getHealthCertificateExpirationDate, dto.getHealthCertificateExpirationDate())
+                .set(MaTechnician::getQualificationCertificateExpirationDate, dto.getQualificationCertificateExpirationDate())
+                .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
+                .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
+        if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
+            updateWrapper.set(MaTechnician::getTeNickName, merchant.getPendingTeNickName())
+                    .set(MaTechnician::getPendingTeNickName, null)
+                    .set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_APPROVED)
+                    .set(MaTechnician::getTeNickNameAuditRemark, auditRemark);
+        }
+        if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
+            updateWrapper.set(MaTechnician::getTeBrief, merchant.getPendingTeBrief())
+                    .set(MaTechnician::getPendingTeBrief, null)
+                    .set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_APPROVED)
+                    .set(MaTechnician::getTeBriefAuditRemark, auditRemark);
+        }
+        return maTechnicianMapper.update(null, updateWrapper);
+    }
+
+    private int rejectProfileTextInfo(MaTechnician merchant, String auditRemark, LoginUser loginUser) {
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MaTechnician::getId, merchant.getId())
+                .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
+                .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
+        if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
+            updateWrapper.set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_REJECTED)
+                    .set(MaTechnician::getTeNickNameAuditRemark, auditRemark);
+        }
+        if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
+            updateWrapper.set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_REJECTED)
+                    .set(MaTechnician::getTeBriefAuditRemark, auditRemark);
+        }
+        return maTechnicianMapper.update(null, updateWrapper);
+    }
+
+    private int approvePendingProfileFiles(Integer merchantId, List<MerchantApplyFile> pendingFiles, String auditRemark, LoginUser loginUser) {
+        Set<String> fileTypes = pendingFiles.stream()
+                .map(MerchantApplyFile::getFileType)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (fileTypes.isEmpty()) {
+            return 0;
+        }
+        String updateBy = getAuditUserName(loginUser);
+        LambdaUpdateWrapper<MerchantApplyFile> deleteOldWrapper = Wrappers.lambdaUpdate();
+        deleteOldWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .in(MerchantApplyFile::getFileType, fileTypes)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
+                .set(MerchantApplyFile::getIsDelete, 1)
+                .set(MerchantApplyFile::getUpdateBy, updateBy)
+                .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+        int rows = merchantApplyFileMapper.update(null, deleteOldWrapper);
+
+        LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
+        auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
+                .set(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
+                .set(MerchantApplyFile::getAuditRemark, auditRemark)
+                .set(MerchantApplyFile::getUpdateBy, updateBy)
+                .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+        return rows + merchantApplyFileMapper.update(null, auditWrapper);
+    }
+
+    private int rejectPendingProfileFiles(Integer merchantId, String auditRemark, LoginUser loginUser) {
+        LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
+        auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
+                .set(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_REJECTED)
+                .set(MerchantApplyFile::getAuditRemark, auditRemark)
+                .set(MerchantApplyFile::getUpdateBy, getAuditUserName(loginUser))
+                .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+        return merchantApplyFileMapper.update(null, auditWrapper);
+    }
+
+    private MaTechnician getExistingMerchant(Integer merchantId) {
+        if (merchantId == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        MaTechnician merchant = maTechnicianMapper.selectById(merchantId);
+        if (merchant == null || (merchant.getIsDelete() != null && !NOT_DELETED.equals(merchant.getIsDelete()))) {
+            throw new ServiceException("商户不存在或已删除");
+        }
+        return merchant;
+    }
+
+    /**
+     * 提交文本资料
+     *
+     * @param merchant
+     * @param dto
+     * @return
+     */
+    private boolean submitProfileTextFields(MaTechnician merchant, MerchantProfileSubmitDTO dto) {
+        boolean changed = false;
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MaTechnician::getId, merchant.getId());
+
+        if (dto.getNickName() != null) {
+            String nickName = checkProfileTextValue(dto.getNickName(), "昵称");
+            if (PROFILE_AUDIT_PENDING == valueOrApproved(merchant.getTeNickNameAuditStatus())) {
+                throw new ServiceException("该资料正在审核中,请勿重复提交");
+            }
+            if (!nickName.equals(merchant.getTeNickName()) || PROFILE_AUDIT_REJECTED == valueOrApproved(merchant.getTeNickNameAuditStatus())) {
+                updateWrapper.set(MaTechnician::getPendingTeNickName, nickName)
+                        .set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_PENDING)
+                        .set(MaTechnician::getTeNickNameAuditRemark, null);
+                changed = true;
+            }
+        }
+
+        if (dto.getBrief() != null) {
+            String brief = checkProfileTextValue(dto.getBrief(), "简介");
+            if (PROFILE_AUDIT_PENDING == valueOrApproved(merchant.getTeBriefAuditStatus())) {
+                throw new ServiceException("该资料正在审核中,请勿重复提交");
+            }
+            if (!brief.equals(merchant.getTeBrief()) || PROFILE_AUDIT_REJECTED == valueOrApproved(merchant.getTeBriefAuditStatus())) {
+                updateWrapper.set(MaTechnician::getPendingTeBrief, brief)
+                        .set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_PENDING)
+                        .set(MaTechnician::getTeBriefAuditRemark, null);
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateWrapper.set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
+            int rows = maTechnicianMapper.update(null, updateWrapper);
+            if (rows <= 0) {
+                throw new ServiceException("提交商户资料审核失败");
+            }
+        }
+        return changed;
+    }
+
+    private String checkProfileTextValue(String value, String fieldName) {
+        if (StringUtils.isBlank(value)) {
+            throw new ServiceException(fieldName + "不能为空");
+        }
+        return value.trim();
+    }
+
+    private int valueOrApproved(Integer status) {
+        return status == null ? PROFILE_AUDIT_APPROVED : status;
+    }
+
+    private void submitProfileFiles(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()) {
+            String fileType = entry.getKey();
+            checkProfileFileType(fileType);
+            if (hasPendingProfileFile(merchantId, fileType)) {
+                throw new ServiceException("该资料正在审核中,请勿重复提交");
+            }
+            deleteRejectedProfileFiles(merchantId, fileType);
+            String batchNo = UUID.randomUUID().toString().replace("-", "");
+            for (MerchantApplyFileDto file : entry.getValue()) {
+                MerchantApplyFile applyFile = new MerchantApplyFile();
+                BeanUtils.copyProperties(file, applyFile);
+                applyFile.setMerchantId(merchantId);
+                applyFile.setApplyBatchNo(batchNo);
+                applyFile.setAuditStatus(PROFILE_AUDIT_PENDING);
+                applyFile.setAuditRemark(null);
+                applyFile.setCreateBy(merchantId.toString());
+                applyFile.setUpdateBy(merchantId.toString());
+                applyFile.setIsDelete(NOT_DELETED);
+                merchantApplyFileMapper.insert(applyFile);
+            }
+        }
+    }
+
+    private void checkProfileFileType(String fileType) {
+        if (!PROFILE_FILE_TYPES.contains(fileType)) {
+            throw new ServiceException("资料文件类型不支持修改");
+        }
+    }
+
+    private boolean hasPendingProfileFile(Integer merchantId, String fileType) {
+        LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getFileType, fileType)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING);
+        return merchantApplyFileMapper.selectCount(queryWrapper) > 0;
+    }
+
+    private void deleteRejectedProfileFiles(Integer merchantId, String fileType) {
+        LambdaUpdateWrapper<MerchantApplyFile> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getFileType, fileType)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_REJECTED)
+                .set(MerchantApplyFile::getIsDelete, 1)
+                .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+        merchantApplyFileMapper.update(null, updateWrapper);
+    }
+
+    private int auditProfileNickName(Integer merchantId, Integer auditStatus, String auditRemark, LoginUser loginUser) {
+        MaTechnician merchant = getExistingMerchant(merchantId);
+        if (!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
+            throw new ServiceException("昵称资料不是审核中状态");
+        }
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MaTechnician::getId, merchantId)
+                .set(MaTechnician::getTeNickNameAuditStatus, auditStatus)
+                .set(MaTechnician::getTeNickNameAuditRemark, auditRemark)
+                .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
+                .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
+        if (PROFILE_AUDIT_APPROVED == auditStatus) {
+            updateWrapper.set(MaTechnician::getTeNickName, merchant.getPendingTeNickName())
+                    .set(MaTechnician::getPendingTeNickName, null);
+        }
+        return maTechnicianMapper.update(null, updateWrapper);
+    }
+
+    private int auditProfileBrief(Integer merchantId, Integer auditStatus, String auditRemark, LoginUser loginUser) {
+        MaTechnician merchant = getExistingMerchant(merchantId);
+        if (!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
+            throw new ServiceException("简介资料不是审核中状态");
+        }
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MaTechnician::getId, merchantId)
+                .set(MaTechnician::getTeBriefAuditStatus, auditStatus)
+                .set(MaTechnician::getTeBriefAuditRemark, auditRemark)
+                .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
+                .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
+        if (PROFILE_AUDIT_APPROVED == auditStatus) {
+            updateWrapper.set(MaTechnician::getTeBrief, merchant.getPendingTeBrief())
+                    .set(MaTechnician::getPendingTeBrief, null);
+        }
+        return maTechnicianMapper.update(null, updateWrapper);
+    }
+
+    private int auditProfileFile(Integer merchantId, String fileType, Integer auditStatus, String auditRemark, LoginUser loginUser) {
+        if (StringUtils.isBlank(fileType)) {
+            throw new ServiceException("文件类型不能为空");
+        }
+        checkProfileFileType(fileType);
+        if (!hasPendingProfileFile(merchantId, fileType)) {
+            throw new ServiceException("该文件资料不是审核中状态");
+        }
+        String updateBy = getAuditUserName(loginUser);
+        if (PROFILE_AUDIT_APPROVED == auditStatus) {
+            LambdaUpdateWrapper<MerchantApplyFile> deleteOldWrapper = Wrappers.lambdaUpdate();
+            deleteOldWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                    .eq(MerchantApplyFile::getFileType, fileType)
+                    .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
+                    .set(MerchantApplyFile::getIsDelete, 1)
+                    .set(MerchantApplyFile::getUpdateBy, updateBy)
+                    .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+            merchantApplyFileMapper.update(null, deleteOldWrapper);
+        }
+        LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
+        auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
+                .eq(MerchantApplyFile::getFileType, fileType)
+                .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
+                .set(MerchantApplyFile::getAuditStatus, auditStatus)
+                .set(MerchantApplyFile::getAuditRemark, auditRemark)
+                .set(MerchantApplyFile::getUpdateBy, updateBy)
+                .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
+        return merchantApplyFileMapper.update(null, auditWrapper);
+    }
+
+    private String getAuditUserName(LoginUser loginUser) {
+        if (loginUser != null && loginUser.getUser() != null) {
+            return loginUser.getUser().getUserName();
+        }
+        return null;
+    }
+
+    private MerchantProfileTextItemVO buildTextItem(String value, String pendingValue, Integer auditStatus, String auditRemark) {
+        MerchantProfileTextItemVO item = new MerchantProfileTextItemVO();
+        item.setValue(value);
+        item.setPendingValue(pendingValue);
+        item.setAuditStatus(shouldShowAuditStatus(auditStatus) ? auditStatus : null);
+        item.setAuditStatusText(shouldShowAuditStatus(auditStatus) ? getProfileAuditStatusText(auditStatus) : null);
+        item.setAuditRemark(shouldShowAuditStatus(auditStatus) ? auditRemark : null);
+        item.setEditable(!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus));
+        return item;
+    }
+
+    private List<MerchantProfileFileGroupVO> buildFileGroups(List<MerchantApplyFile> files) {
+        Map<String, List<MerchantApplyFile>> filesByType = CollectionUtils.isEmpty(files)
+                ? Collections.emptyMap()
+                : files.stream()
+                .filter(Objects::nonNull)
+                .filter(file -> PROFILE_FILE_TYPES.contains(file.getFileType()))
+                .collect(Collectors.groupingBy(MerchantApplyFile::getFileType, LinkedHashMap::new, Collectors.toList()));
+        List<MerchantProfileFileGroupVO> groups = new ArrayList<>();
+        for (String fileType : PROFILE_FILE_TYPES) {
+            List<MerchantApplyFile> typeFiles = filesByType.getOrDefault(fileType, Collections.emptyList());
+            List<MerchantApplyFile> officialFiles = typeFiles.stream()
+                    .filter(file -> Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(file.getAuditStatus()))
+                    .collect(Collectors.toList());
+            List<MerchantApplyFile> pendingFiles = typeFiles.stream()
+                    .filter(file -> !Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(file.getAuditStatus()))
+                    .collect(Collectors.toList());
+            Integer auditStatus = resolveFileGroupAuditStatus(pendingFiles);
+            MerchantProfileFileGroupVO group = new MerchantProfileFileGroupVO();
+            group.setFileType(fileType);
+            group.setFileTypeName(FileTypeEnum.getDescByCode(fileType));
+            group.setOfficialFiles(toProfileFileVOList(officialFiles));
+            group.setPendingFiles(toProfileFileVOList(pendingFiles));
+            group.setAuditStatus(shouldShowAuditStatus(auditStatus) ? auditStatus : null);
+            group.setAuditStatusText(shouldShowAuditStatus(auditStatus) ? getProfileAuditStatusText(auditStatus) : null);
+            group.setAuditRemark(shouldShowAuditStatus(auditStatus) ? resolveFileGroupAuditRemark(pendingFiles) : null);
+            group.setEditable(!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus));
+            groups.add(group);
+        }
+        return groups;
+    }
+
+    private Integer resolveFileGroupAuditStatus(List<MerchantApplyFile> pendingFiles) {
+        if (CollectionUtils.isEmpty(pendingFiles)) {
+            return null;
+        }
+        if (pendingFiles.stream().anyMatch(file -> Integer.valueOf(PROFILE_AUDIT_PENDING).equals(file.getAuditStatus()))) {
+            return PROFILE_AUDIT_PENDING;
+        }
+        return pendingFiles.get(0).getAuditStatus();
+    }
+
+    private String resolveFileGroupAuditRemark(List<MerchantApplyFile> pendingFiles) {
+        if (CollectionUtils.isEmpty(pendingFiles)) {
+            return null;
+        }
+        return pendingFiles.stream()
+                .map(MerchantApplyFile::getAuditRemark)
+                .filter(StringUtils::isNotBlank)
+                .findFirst()
+                .orElse(null);
+    }
+
+    private List<MerchantProfileFileVO> toProfileFileVOList(List<MerchantApplyFile> files) {
+        if (CollectionUtils.isEmpty(files)) {
+            return Collections.emptyList();
+        }
+        return files.stream().map(this::toProfileFileVO).collect(Collectors.toList());
+    }
+
+    private MerchantProfileFileVO toProfileFileVO(MerchantApplyFile file) {
+        MerchantProfileFileVO vo = new MerchantProfileFileVO();
+        vo.setId(file.getId());
+        vo.setFileName(file.getFileName());
+        vo.setFileUrl(file.getFileUrl());
+        vo.setFileSize(file.getFileSize());
+        vo.setContentType(file.getContentType());
+        vo.setApplyBatchNo(file.getApplyBatchNo());
+        vo.setAuditStatus(file.getAuditStatus());
+        vo.setAuditRemark(file.getAuditRemark());
+        return vo;
+    }
+
+    private boolean shouldShowAuditStatus(Integer auditStatus) {
+        return auditStatus != null && !Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(auditStatus);
+    }
+
+    private String getProfileAuditStatusText(Integer auditStatus) {
+        if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus)) {
+            return "审核中";
+        }
+        if (Integer.valueOf(PROFILE_AUDIT_REJECTED).equals(auditStatus)) {
+            return "审核驳回";
+        }
+        return null;
     }
 
     /**
@@ -1127,7 +1643,8 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         if (StringUtils.isBlank(openid)) {
             throw new IllegalArgumentException("openid不能为空");
         }
-        return buildMerchantAuditFile(findMerchantByOpenid(openid));
+        MaTechnician merchant = findMerchantByOpenid(openid);
+        return buildMerchantAuditFile(merchant);
     }
 
     /**

+ 62 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TechnicianMomentServiceImpl.java

@@ -2,6 +2,7 @@ package com.ylx.massage.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.exception.ServiceException;
@@ -40,6 +41,10 @@ import java.util.stream.Collectors;
 @Slf4j
 public class TechnicianMomentServiceImpl extends ServiceImpl<TechnicianMomentMapper, TechnicianMoment> implements ITechnicianMomentService {
 
+    private static final int AUDIT_STATUS_PENDING = 1;
+    private static final int NOT_DELETED = 0;
+    private static final int DELETED = 1;
+
     @Autowired
     private TechnicianMomentMapper momentMapper;
 
@@ -675,6 +680,63 @@ public class TechnicianMomentServiceImpl extends ServiceImpl<TechnicianMomentMap
         return voPage;
     }
 
+    /**
+     * 删除我的动态。
+     *
+     * <p>审核中的动态不允许删除;其它属于当前商户且未删除的动态仅做逻辑删除。</p>
+     *
+     * @param momentId 动态ID
+     * @param openId   当前登录商户OpenID
+     * @return Boolean 是否删除成功
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteMyMoment(Long momentId, String openId) {
+        if (momentId == null) {
+            throw new ServiceException("动态ID不能为空");
+        }
+        if (StringUtils.isBlank(openId)) {
+            throw new ServiceException("openId不能为空");
+        }
+
+        LambdaQueryWrapper<MaTechnician> technicianWrapper = new LambdaQueryWrapper<>();
+        technicianWrapper.eq(MaTechnician::getCOpenid, openId);
+        MaTechnician technician = maTechnicianMapper.selectOne(technicianWrapper);
+        if (technician == null) {
+            throw new ServiceException("商户信息不存在");
+        }
+
+        TechnicianMoment moment = momentMapper.selectById(momentId);
+        if (moment == null || Objects.equals(moment.getIsDelete(), DELETED)) {
+            throw new ServiceException("动态不存在或已删除");
+        }
+        if (!Objects.equals(moment.getTechnicianId(), technician.getId())) {
+            throw new ServiceException("无权删除此动态");
+        }
+        if (Objects.equals(moment.getAuditStatus(), AUDIT_STATUS_PENDING)) {
+            throw new ServiceException("审核中的动态不支持删除");
+        }
+
+        TechnicianMoment update = new TechnicianMoment();
+        update.setIsDelete(DELETED);
+        update.setUpdateBy(openId);
+        update.setUpdateTime(LocalDateTime.now());
+
+        LambdaUpdateWrapper<TechnicianMoment> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(TechnicianMoment::getId, momentId)
+                .eq(TechnicianMoment::getTechnicianId, technician.getId())
+                .eq(TechnicianMoment::getIsDelete, NOT_DELETED);
+        int rows = momentMapper.update(update, updateWrapper);
+        if (rows <= 0) {
+            throw new ServiceException("删除动态失败");
+        }
+        LambdaQueryWrapper<MomentMedia> mediaQueryWrapper = new LambdaQueryWrapper<>();
+        mediaQueryWrapper.eq(MomentMedia::getMomentId, momentId);
+        mediaMapper.delete(mediaQueryWrapper);
+        log.info("删除动态成功,动态ID:{},商户ID:{}", momentId, technician.getId());
+        return true;
+    }
+
 
     /**
      * 根据技师ID查询动态列表(已发布的动态:审核中+审核通过)

+ 1 - 0
nightFragrance-massage/src/main/resources/mapper/massage/MomentMediaMapper.xml

@@ -22,6 +22,7 @@
             t_moment_media
         WHERE
             moment_id = #{momentId}
+            AND IFNULL(is_delete, 0) = 0
         ORDER BY
             sort_order ASC
     </select>

+ 1 - 1
nightFragrance-massage/src/main/resources/mapper/massage/TechnicianMomentMapper.xml

@@ -142,7 +142,7 @@
             GROUP_CONCAT(b.media_url ORDER BY b.sort_order SEPARATOR ',') as media_urls
         FROM
             t_technician_moment a
-            LEFT JOIN t_moment_media b ON a.id = b.moment_id
+            LEFT JOIN t_moment_media b ON a.id = b.moment_id AND IFNULL(b.is_delete, 0) = 0
         WHERE
             a.id = #{momentId}
         GROUP BY

+ 31 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/mapper/MomentMediaMapperXmlTest.java

@@ -0,0 +1,31 @@
+package com.ylx.massage.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MomentMediaMapperXmlTest {
+
+    @Test
+    public void selectMediaListByMomentIdExcludesDeletedMedia() throws Exception {
+        String xml = readMapperXml();
+
+        assertTrue(xml.contains("selectMediaListByMomentId"));
+        assertTrue(xml.contains("moment_id = #{momentId}"));
+        assertTrue(xml.contains("AND IFNULL(is_delete, 0) = 0"));
+    }
+
+    private String readMapperXml() throws Exception {
+        InputStream inputStream = getClass().getClassLoader()
+                .getResourceAsStream("mapper/massage/MomentMediaMapper.xml");
+        assertNotNull(inputStream, "MomentMediaMapper.xml should exist");
+        byte[] bytes = new byte[inputStream.available()];
+        int read = inputStream.read(bytes);
+        assertTrue(read > 0, "MomentMediaMapper.xml should not be empty");
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+}

+ 8 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TechnicianMomentMapperXmlTest.java

@@ -82,6 +82,14 @@ public class TechnicianMomentMapperXmlTest {
         assertTrue(xml.contains("is_delete = 0"));
     }
 
+    @Test
+    public void simpleDetailExcludesDeletedMedia() throws Exception {
+        String xml = readMapperXml();
+
+        assertTrue(xml.contains("selectMomentSimpleDetail"));
+        assertTrue(xml.contains("LEFT JOIN t_moment_media b ON a.id = b.moment_id AND IFNULL(b.is_delete, 0) = 0"));
+    }
+
     private String readMapperXml() throws Exception {
         InputStream inputStream = getClass().getClassLoader()
                 .getResourceAsStream("mapper/massage/TechnicianMomentMapper.xml");

+ 157 - 1
nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/MaTechnicianServiceImplTest.java

@@ -2,11 +2,14 @@ package com.ylx.massage.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.ylx.common.core.domain.model.LoginUser;
 import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.domain.MerchantApplyFile;
+import com.ylx.massage.domain.dto.MerchantProfileAuditDTO;
 import com.ylx.massage.domain.dto.MerchantApplyFileDto;
 import com.ylx.massage.domain.dto.MerchantApplyFileRequestDto;
 import com.ylx.massage.domain.vo.MaTechnicianCertificateVO;
+import com.ylx.massage.domain.vo.MerchantProfileVO;
 import com.ylx.massage.mapper.MaTechnicianMapper;
 import com.ylx.massage.mapper.MerchantApplyFileMapper;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
@@ -16,6 +19,7 @@ import org.mockito.ArgumentCaptor;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -43,6 +47,8 @@ public class MaTechnicianServiceImplTest {
         MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
         when(technicianMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
         when(applyFileMapper.delete(any(Wrapper.class))).thenReturn(1);
+        when(technicianMapper.selectById(11)).thenReturn(buildMerchant(11));
+        when(applyFileMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
 
         MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
         ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
@@ -74,7 +80,9 @@ public class MaTechnicianServiceImplTest {
         assertEquals("1", savedFile.getFileType());
         assertEquals("形象照", savedFile.getFileName());
         assertEquals(file.getFileUrl(), savedFile.getFileUrl());
+        assertEquals(0, savedFile.getAuditStatus());
         assertNull(savedFile.getId());
+        verify(applyFileMapper, never()).delete(any(Wrapper.class));
     }
 
     @Test
@@ -86,6 +94,8 @@ public class MaTechnicianServiceImplTest {
         MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
         when(technicianMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
         when(applyFileMapper.delete(any(Wrapper.class))).thenReturn(2);
+        when(technicianMapper.selectById(11)).thenReturn(buildMerchant(11));
+        when(applyFileMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
 
         MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
         ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
@@ -108,7 +118,7 @@ public class MaTechnicianServiceImplTest {
 
         service.updateTechnician(request);
 
-        verify(applyFileMapper, times(1)).delete(any(Wrapper.class));
+        verify(applyFileMapper, never()).delete(any(Wrapper.class));
         verify(applyFileMapper, never()).updateById(any(MerchantApplyFile.class));
         ArgumentCaptor<MerchantApplyFile> fileCaptor = ArgumentCaptor.forClass(MerchantApplyFile.class);
         verify(applyFileMapper, times(2)).insert(fileCaptor.capture());
@@ -119,10 +129,141 @@ public class MaTechnicianServiceImplTest {
         assertEquals("2", savedFiles.get(1).getFileType());
         assertEquals(11, savedFiles.get(0).getMerchantId());
         assertEquals(11, savedFiles.get(1).getMerchantId());
+        assertEquals(0, savedFiles.get(0).getAuditStatus());
+        assertEquals(0, savedFiles.get(1).getAuditStatus());
         assertNull(savedFiles.get(0).getId());
         assertNull(savedFiles.get(1).getId());
     }
 
+    @Test
+    public void updateTechnicianRejectsWhenSameFileTypeIsPending() {
+        initTableInfo(MaTechnician.class);
+        initTableInfo(MerchantApplyFile.class);
+
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(technicianMapper.selectById(11)).thenReturn(buildMerchant(11));
+        when(applyFileMapper.selectCount(any(Wrapper.class))).thenReturn(1L);
+
+        MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "merchantApplyFileMapper", applyFileMapper);
+
+        MerchantApplyFileRequestDto request = buildBaseRequest();
+        MerchantApplyFileDto group = new MerchantApplyFileDto();
+        group.setFileType("1");
+        group.setFiles(Collections.singletonList(buildApplyFile("avatar", "http://host/new.png")));
+        request.setReq(Collections.singletonList(group));
+
+        assertThrows(RuntimeException.class, () -> service.updateTechnician(request));
+
+        verify(applyFileMapper, never()).insert(any(MerchantApplyFile.class));
+    }
+
+    @Test
+    public void getMerchantProfileHidesApprovedStatusAndShowsPendingStatus() {
+        initTableInfo(MaTechnician.class);
+        initTableInfo(MerchantApplyFile.class);
+
+        MaTechnician merchant = buildMerchant(11);
+        merchant.setTeNickName("oldNick");
+        merchant.setPendingTeNickName("newNick");
+        merchant.setTeNickNameAuditStatus(0);
+
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(technicianMapper.selectById(11)).thenReturn(merchant);
+        when(applyFileMapper.selectList(any(Wrapper.class))).thenReturn(Arrays.asList(
+                buildMerchantApplyFile("1", "approved-avatar", 1),
+                buildMerchantApplyFile("1", "pending-avatar", 0)
+        ));
+
+        MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "merchantApplyFileMapper", applyFileMapper);
+
+        MerchantProfileVO profile = service.getMerchantProfile(11);
+
+        assertEquals("oldNick", profile.getNickName().getValue());
+        assertEquals("newNick", profile.getNickName().getPendingValue());
+        assertEquals(0, profile.getNickName().getAuditStatus());
+        assertNull(profile.getBrief().getAuditStatus());
+        assertEquals(1, profile.getFileGroups().get(0).getOfficialFiles().size());
+        assertEquals(1, profile.getFileGroups().get(0).getPendingFiles().size());
+        assertEquals(0, profile.getFileGroups().get(0).getAuditStatus());
+    }
+
+    @Test
+    public void auditMerchantProfileApprovesAllPendingProfileData() {
+        initTableInfo(MaTechnician.class);
+        initTableInfo(MerchantApplyFile.class);
+
+        MaTechnician merchant = buildMerchant(11);
+        merchant.setPendingTeNickName("newNick");
+        merchant.setTeNickNameAuditStatus(0);
+        merchant.setPendingTeBrief("newBrief");
+        merchant.setTeBriefAuditStatus(0);
+
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(technicianMapper.selectById(11)).thenReturn(merchant);
+        when(technicianMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+        when(applyFileMapper.selectList(any(Wrapper.class))).thenReturn(Arrays.asList(
+                buildMerchantApplyFile("1", "pending-avatar", 0),
+                buildMerchantApplyFile("2", "pending-life", 0)
+        ));
+        when(applyFileMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+
+        MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "merchantApplyFileMapper", applyFileMapper);
+
+        MerchantProfileAuditDTO auditDTO = new MerchantProfileAuditDTO();
+        auditDTO.setAuditStatus(2);
+        auditDTO.setIdCardExpirationDate(LocalDate.of(2027, 1, 1));
+        auditDTO.setHealthCertificateExpirationDate(LocalDate.of(2027, 1, 1));
+        auditDTO.setQualificationCertificateExpirationDate(LocalDate.of(2027, 1, 1));
+
+        int rows = service.auditMerchantProfile(11, auditDTO, (LoginUser) null);
+
+        assertEquals(3, rows);
+        verify(technicianMapper, times(1)).update(isNull(), any(Wrapper.class));
+        verify(applyFileMapper, times(2)).update(isNull(), any(Wrapper.class));
+    }
+
+    @Test
+    public void auditMerchantProfileRejectsAllPendingProfileData() {
+        initTableInfo(MaTechnician.class);
+        initTableInfo(MerchantApplyFile.class);
+
+        MaTechnician merchant = buildMerchant(11);
+        merchant.setPendingTeNickName("newNick");
+        merchant.setTeNickNameAuditStatus(0);
+
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MerchantApplyFileMapper applyFileMapper = mock(MerchantApplyFileMapper.class);
+        when(technicianMapper.selectById(11)).thenReturn(merchant);
+        when(technicianMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+        when(applyFileMapper.selectList(any(Wrapper.class))).thenReturn(Collections.singletonList(
+                buildMerchantApplyFile("1", "pending-avatar", 0)
+        ));
+        when(applyFileMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+
+        MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "merchantApplyFileMapper", applyFileMapper);
+
+        MerchantProfileAuditDTO auditDTO = new MerchantProfileAuditDTO();
+        auditDTO.setAuditStatus(3);
+        auditDTO.setAuditRemark("bad");
+
+        int rows = service.auditMerchantProfile(11, auditDTO, (LoginUser) null);
+
+        assertEquals(2, rows);
+        verify(technicianMapper, times(1)).update(isNull(), any(Wrapper.class));
+        verify(applyFileMapper, times(1)).update(isNull(), any(Wrapper.class));
+    }
+
     @Test
     public void updateTechnicianRejectsBlankNickName() {
         MaTechnicianServiceImpl service = new MaTechnicianServiceImpl();
@@ -235,6 +376,21 @@ public class MaTechnicianServiceImplTest {
         return file;
     }
 
+    private MerchantApplyFile buildMerchantApplyFile(String fileType, String fileUrl, Integer auditStatus) {
+        MerchantApplyFile file = buildMerchantApplyFile(fileType, fileUrl);
+        file.setAuditStatus(auditStatus);
+        return file;
+    }
+
+    private MaTechnician buildMerchant(Integer merchantId) {
+        MaTechnician merchant = new MaTechnician();
+        merchant.setId(merchantId);
+        merchant.setIsDelete(0);
+        merchant.setTeNickName("Ctrlv");
+        merchant.setTeBrief("brief");
+        return merchant;
+    }
+
     private void initTableInfo(Class<?> entityClass) {
         if (TableInfoHelper.getTableInfo(entityClass) == null) {
             MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), "");

+ 109 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/TechnicianMomentServiceImplTest.java

@@ -1,20 +1,114 @@
 package com.ylx.massage.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.ylx.common.exception.ServiceException;
+import com.ylx.massage.domain.MaTechnician;
+import com.ylx.massage.domain.MomentMedia;
+import com.ylx.massage.domain.TechnicianMoment;
 import com.ylx.massage.domain.vo.MomentAuditStatusCountVO;
+import com.ylx.massage.mapper.MaTechnicianMapper;
+import com.ylx.massage.mapper.MomentMediaMapper;
 import com.ylx.massage.mapper.TechnicianMomentMapper;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 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.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class TechnicianMomentServiceImplTest {
 
+    @Test
+    public void deleteMyMomentRejectsEmptyMomentId() {
+        TechnicianMomentServiceImpl service = new TechnicianMomentServiceImpl();
+
+        ServiceException exception = assertThrows(ServiceException.class,
+                () -> service.deleteMyMoment(null, "openid"));
+
+        assertEquals("动态ID不能为空", exception.getMessage());
+    }
+
+    @Test
+    public void deleteMyMomentRejectsPendingMoment() {
+        TechnicianMomentMapper mapper = mock(TechnicianMomentMapper.class);
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MomentMediaMapper mediaMapper = mock(MomentMediaMapper.class);
+        TechnicianMomentServiceImpl service = new TechnicianMomentServiceImpl();
+        ReflectionTestUtils.setField(service, "momentMapper", mapper);
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "mediaMapper", mediaMapper);
+        MaTechnician technician = buildTechnician(7);
+        TechnicianMoment moment = buildMoment(10L, 7, 1, 0);
+        when(technicianMapper.selectOne(any(Wrapper.class))).thenReturn(technician);
+        when(mapper.selectById(10L)).thenReturn(moment);
+
+        ServiceException exception = assertThrows(ServiceException.class,
+                () -> service.deleteMyMoment(10L, "openid"));
+
+        assertEquals("审核中的动态不支持删除", exception.getMessage());
+        verify(mapper, never()).update(any(TechnicianMoment.class), any(Wrapper.class));
+        verify(mediaMapper, never()).update(any(MomentMedia.class), any(Wrapper.class));
+    }
+
+    @Test
+    public void deleteMyMomentRejectsOtherTechnicianMoment() {
+        TechnicianMomentMapper mapper = mock(TechnicianMomentMapper.class);
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MomentMediaMapper mediaMapper = mock(MomentMediaMapper.class);
+        TechnicianMomentServiceImpl service = new TechnicianMomentServiceImpl();
+        ReflectionTestUtils.setField(service, "momentMapper", mapper);
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "mediaMapper", mediaMapper);
+        MaTechnician technician = buildTechnician(7);
+        TechnicianMoment moment = buildMoment(10L, 8, 2, 0);
+        when(technicianMapper.selectOne(any(Wrapper.class))).thenReturn(technician);
+        when(mapper.selectById(10L)).thenReturn(moment);
+
+        ServiceException exception = assertThrows(ServiceException.class,
+                () -> service.deleteMyMoment(10L, "openid"));
+
+        assertEquals("无权删除此动态", exception.getMessage());
+        verify(mapper, never()).update(any(TechnicianMoment.class), any(Wrapper.class));
+        verify(mediaMapper, never()).update(any(MomentMedia.class), any(Wrapper.class));
+    }
+
+    @Test
+    public void deleteMyMomentLogicDeletesApprovedMomentAndMedia() {
+        TechnicianMomentMapper mapper = mock(TechnicianMomentMapper.class);
+        MaTechnicianMapper technicianMapper = mock(MaTechnicianMapper.class);
+        MomentMediaMapper mediaMapper = mock(MomentMediaMapper.class);
+        TechnicianMomentServiceImpl service = new TechnicianMomentServiceImpl();
+        ReflectionTestUtils.setField(service, "momentMapper", mapper);
+        ReflectionTestUtils.setField(service, "maTechnicianMapper", technicianMapper);
+        ReflectionTestUtils.setField(service, "mediaMapper", mediaMapper);
+        MaTechnician technician = buildTechnician(7);
+        TechnicianMoment moment = buildMoment(10L, 7, 2, 0);
+        when(technicianMapper.selectOne(any(Wrapper.class))).thenReturn(technician);
+        when(mapper.selectById(10L)).thenReturn(moment);
+        when(mapper.update(any(TechnicianMoment.class), any(Wrapper.class))).thenReturn(1);
+        when(mediaMapper.update(any(MomentMedia.class), any(Wrapper.class))).thenReturn(2);
+
+        Boolean result = service.deleteMyMoment(10L, "openid");
+
+        assertTrue(result);
+        ArgumentCaptor<TechnicianMoment> momentCaptor = ArgumentCaptor.forClass(TechnicianMoment.class);
+        verify(mapper).update(momentCaptor.capture(), any(Wrapper.class));
+        TechnicianMoment update = momentCaptor.getValue();
+        assertEquals(1, update.getIsDelete());
+        assertEquals("openid", update.getUpdateBy());
+        assertNotNull(update.getUpdateTime());
+        ArgumentCaptor<MomentMedia> mediaCaptor = ArgumentCaptor.forClass(MomentMedia.class);
+        verify(mediaMapper).update(mediaCaptor.capture(), any(Wrapper.class));
+        assertEquals(1, mediaCaptor.getValue().getIsDelete());
+    }
+
     @Test
     public void updateRecommendStatusRejectsEmptyMomentId() {
         TechnicianMomentServiceImpl service = new TechnicianMomentServiceImpl();
@@ -139,4 +233,19 @@ public class TechnicianMomentServiceImplTest {
         assertEquals(0L, result.getApprovedCount());
         assertEquals(0L, result.getRejectedCount());
     }
+
+    private MaTechnician buildTechnician(Integer id) {
+        MaTechnician technician = new MaTechnician();
+        technician.setId(id);
+        return technician;
+    }
+
+    private TechnicianMoment buildMoment(Long id, Integer technicianId, Integer auditStatus, Integer isDelete) {
+        TechnicianMoment moment = new TechnicianMoment();
+        moment.setId(id);
+        moment.setTechnicianId(technicianId);
+        moment.setAuditStatus(auditStatus);
+        moment.setIsDelete(isDelete);
+        return moment;
+    }
 }