liujie
2026-09-15 ad491191ab50a259da548863d805c57d8a95d083
update
6个文件已修改
101 ■■■■■ 已修改文件
backend/src/main/java/com/tourguide/modules/user/mapper/GuideUserMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/test/java/com/tourguide/modules/user/service/GuideAccountServiceTest.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/test/java/com/tourguide/modules/user/service/GuideUserServiceProfileTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/tourguide/modules/user/mapper/GuideUserMapper.java
@@ -9,6 +9,22 @@
import java.math.BigDecimal;
public interface GuideUserMapper extends BaseMapper<GuideUser> {
    @Update("UPDATE guide_user SET username = #{username}, mobile = NULL, certificate_no = NULL, " +
            "id_card_no = NULL, id_card_front_url = NULL, id_card_back_url = NULL, status = 3, " +
            "auto_disable_whitelisted = 0, auto_disabled = 0, updated_at = NOW() " +
            "WHERE id = #{guideId} AND deleted = 0")
    int cancelAccount(@Param("guideId") Long guideId, @Param("username") String username);
    @Update("UPDATE guide_user SET username = #{username}, mobile = NULL, certificate_no = NULL, " +
            "id_card_no = NULL, id_card_front_url = NULL, id_card_back_url = NULL, updated_at = NOW() " +
            "WHERE id = #{guideId} AND status = 3 AND deleted = 0")
    int anonymizeCancelledGuide(@Param("guideId") Long guideId, @Param("username") String username);
    @Update("UPDATE guide_user SET id_card_no = NULL, id_card_front_url = NULL, " +
            "id_card_back_url = NULL, updated_at = NOW() " +
            "WHERE id_card_no = #{idCardNo} AND status = 3 AND deleted = 0")
    int clearCancelledIdentityByIdCard(@Param("idCardNo") String idCardNo);
    @Select("SELECT * FROM guide_user WHERE id = #{guideId} AND deleted = 0 FOR UPDATE")
    GuideUser selectForUpdate(@Param("guideId") Long guideId);
backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java
@@ -1,7 +1,6 @@
package com.tourguide.modules.user.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.order.domain.OrderStatus;
import com.tourguide.modules.order.entity.TourOrder;
@@ -58,17 +57,8 @@
        if (idCardNo != null && !idCardNo.trim().isEmpty()) {
            guideApplicationMapper.clearIdentityByIdCard(idCardNo);
        }
        guideUserMapper.update(null, new LambdaUpdateWrapper<GuideUser>()
                .eq(GuideUser::getId, guideId)
                .set(GuideUser::getUsername, "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24))
                .set(GuideUser::getMobile, null)
                .set(GuideUser::getCertificateNo, null)
                .set(GuideUser::getIdCardNo, null)
                .set(GuideUser::getIdCardFrontUrl, null)
                .set(GuideUser::getIdCardBackUrl, null)
                .set(GuideUser::getStatus, GuideUserService.STATUS_CANCELLED)
                .set(GuideUser::getAutoDisableWhitelisted, 0)
                .set(GuideUser::getAutoDisabled, 0));
        String deletedUsername = "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24);
        guideUserMapper.cancelAccount(guideId, deletedUsername);
        tokenSessionService.remove(payload);
    }
}
backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
@@ -1,7 +1,6 @@
package com.tourguide.modules.user.service;
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.tourguide.common.api.PageResponse;
import com.tourguide.common.exception.BusinessException;
@@ -333,25 +332,27 @@
    }
    private void anonymizeCancelledGuide(GuideUser user) {
        userMapper.update(null, new LambdaUpdateWrapper<GuideUser>()
                .eq(GuideUser::getId, user.getId())
                .set(GuideUser::getUsername, "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24))
                .set(GuideUser::getMobile, null)
                .set(GuideUser::getCertificateNo, null)
                .set(GuideUser::getIdCardNo, null));
        String deletedUsername = "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24);
        userMapper.anonymizeCancelledGuide(user.getId(), deletedUsername);
    }
    private void ensureUniqueGuideIdentity(GuideApplication application, GuideUser existingUser) {
        if (StringUtils.hasText(application.getIdCardNo())) {
            // 兼容旧注销数据:释放已注销账号占用的身份证唯一索引。
            userMapper.clearCancelledIdentityByIdCard(application.getIdCardNo().trim().toUpperCase());
        }
        if (StringUtils.hasText(application.getCertificateNo())
                && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
                .eq(GuideUser::getCertificateNo, application.getCertificateNo())
                .ne(GuideUser::getStatus, STATUS_CANCELLED)
                .ne(existingUser != null && existingUser.getId() != null,
                        GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
            throw new BusinessException(409, "该导游证号已存在导游账号");
        }
        if (StringUtils.hasText(application.getIdCardNo())
                && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
                .eq(GuideUser::getIdCardNo, application.getIdCardNo())
                .eq(GuideUser::getIdCardNo, application.getIdCardNo().trim().toUpperCase())
                .ne(GuideUser::getStatus, STATUS_CANCELLED)
                .ne(existingUser != null && existingUser.getId() != null,
                        GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
            throw new BusinessException(409, "该身份证号已存在导游账号");
backend/src/test/java/com/tourguide/modules/user/service/GuideAccountServiceTest.java
@@ -11,11 +11,11 @@
import com.tourguide.security.TokenSessionService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -50,7 +50,7 @@
                .isInstanceOf(BusinessException.class)
                .hasMessage("存在未完成订单,暂时无法注销账号");
        verify(guideUserMapper, never()).updateById(any());
        verify(guideUserMapper, never()).cancelAccount(any(), any());
        verify(tokenSessionService, never()).remove(payload);
    }
@@ -71,16 +71,8 @@
        accountService.cancel(8L, "guide-token");
        ArgumentCaptor<GuideUser> userCaptor = ArgumentCaptor.forClass(GuideUser.class);
        verify(guideUserMapper).updateById(userCaptor.capture());
        assertThat(userCaptor.getValue().getStatus()).isEqualTo(GuideUserService.STATUS_CANCELLED);
        assertThat(userCaptor.getValue().getAutoDisableWhitelisted()).isZero();
        assertThat(userCaptor.getValue().getUsername()).startsWith("deleted_").hasSize(32);
        assertThat(userCaptor.getValue().getMobile()).isNull();
        assertThat(userCaptor.getValue().getCertificateNo()).isNull();
        assertThat(userCaptor.getValue().getIdCardNo()).isNull();
        assertThat(userCaptor.getValue().getIdCardFrontUrl()).isNull();
        assertThat(userCaptor.getValue().getIdCardBackUrl()).isNull();
        verify(guideUserMapper).cancelAccount(eq(8L), org.mockito.ArgumentMatchers.argThat(
                username -> username.startsWith("deleted_") && username.length() == 32));
        verify(guideApplicationMapper).clearIdentityByIdCard("11010519491231002X");
        verify(tokenSessionService).remove(payload);
    }
backend/src/test/java/com/tourguide/modules/user/service/GuideUserServiceProfileTest.java
@@ -24,6 +24,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -114,14 +115,9 @@
        service.createPendingFromApplication(application);
        ArgumentCaptor<GuideUser> updateCaptor = ArgumentCaptor.forClass(GuideUser.class);
        verify(userMapper).updateById(updateCaptor.capture());
        GuideUser anonymizedGuide = updateCaptor.getValue();
        assertThat(anonymizedGuide.getStatus()).isEqualTo(GuideUserService.STATUS_CANCELLED);
        assertThat(anonymizedGuide.getUsername()).startsWith("deleted_").hasSize(32);
        assertThat(anonymizedGuide.getMobile()).isNull();
        assertThat(anonymizedGuide.getCertificateNo()).isNull();
        assertThat(anonymizedGuide.getIdCardNo()).isNull();
        verify(userMapper).anonymizeCancelledGuide(eq(12L), org.mockito.ArgumentMatchers.argThat(
                username -> username.startsWith("deleted_") && username.length() == 32));
        verify(userMapper).clearCancelledIdentityByIdCard("11010519491231002X");
        ArgumentCaptor<GuideUser> insertCaptor = ArgumentCaptor.forClass(GuideUser.class);
        verify(userMapper).insert(insertCaptor.capture());
@@ -134,6 +130,20 @@
    }
    @Test
    void shouldReleaseIdentityFromStaleCancelledGuideBeforeNewRegistration() {
        GuideApplication application = new GuideApplication();
        application.setMobile("13900139000");
        application.setRealName("新导游");
        application.setIdCardNo("11010519491231002X");
        when(userMapper.selectOne(any())).thenReturn(null);
        service.createPendingFromApplication(application);
        verify(userMapper).clearCancelledIdentityByIdCard("11010519491231002X");
        verify(userMapper).insert(any(GuideUser.class));
    }
    @Test
    void shouldTreatOneYearOrLessAsZeroWorkYears() {
        GuideApplication application = new GuideApplication();
        application.setMobile("13800138000");
mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue
@@ -174,12 +174,12 @@
                  <text class="payment-service">{{ normalizedSelectedServiceTitle }}</text>
                  <text class="payment-desc">{{ selectedServiceDuration }}</text>
                </view>
                <text class="payment-unit-price">¥ {{ serviceUnitPrice }}/人</text>
                <text class="payment-unit-price">¥ {{ serviceOriginalUnitPrice }}/人</text>
              </view>
              <view class="payment-row">
                <text class="payment-label">人数</text>
                <text class="payment-value">{{ selectedServiceQuantity }}人</text>
                <text class="payment-price">¥{{ serviceSubtotalDisplay }}</text>
                <text class="payment-price">¥{{ serviceOriginalSubtotalDisplay }}</text>
              </view>
              <view class="payment-row">
                <text class="payment-label">平台折扣</text>
@@ -350,7 +350,9 @@
  return Number.isFinite(amount) ? amount.toFixed(2) : '0.00'
}
const serviceUnitPrice = computed(() => formatMoney(selectedService.value.price))
const serviceOriginalUnitPrice = computed(() => formatMoney(
  selectedService.value.originalPrice ?? selectedService.value.price
))
const serviceOriginalSubtotal = computed(() => Number(
  selectedService.value.originalPrice ?? selectedService.value.price ?? 0
@@ -363,10 +365,10 @@
))
const payableAmount = computed(() => Number(
  Math.max(0, serviceOriginalSubtotal.value - platformDiscount.value).toFixed(2)
  Math.max(0, serviceSubtotal.value).toFixed(2)
))
const serviceSubtotalDisplay = computed(() => formatMoney(serviceSubtotal.value))
const serviceOriginalSubtotalDisplay = computed(() => formatMoney(serviceOriginalSubtotal.value))
const platformDiscountDisplay = computed(() => formatMoney(platformDiscount.value))
const payableAmountDisplay = computed(() => formatMoney(payableAmount.value))