“mitao”
2026-05-12 4f367056ab0e73d044e920d44b1d7dd0c8b65d80
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java
@@ -2,7 +2,10 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tcm.training.admin.app.auth.service.AppAuthSessionService;
import com.tcm.training.admin.app.auth.service.AppSmsCodeService;
import com.tcm.training.admin.app.auth.vo.AppLoginResponse;
import com.tcm.training.admin.app.auth.vo.AppLoginUserVO;
import com.tcm.training.admin.app.auth.vo.AppSmsCodeSendResponse;
import com.tcm.training.admin.app.integration.haizhentong.entity.HztAppUser;
import com.tcm.training.admin.app.integration.haizhentong.entity.HztClinic;
@@ -16,14 +19,13 @@
import com.tcm.training.admin.app.register.vo.AppRegisterOperatorPrepareResponse;
import com.tcm.training.admin.app.register.vo.AppRegisterRegionDatasetVO;
import com.tcm.training.admin.app.register.vo.AppRegisterRegionOptionVO;
import com.tcm.training.admin.app.register.vo.AppRegisterResultVO;
import com.tcm.training.admin.common.PhoneMasker;
import com.tcm.training.admin.live.entity.Clinic;
import com.tcm.training.admin.live.mapper.ClinicMapper;
import com.tcm.training.admin.member.entity.AppUser;
import com.tcm.training.admin.member.mapper.UserMapper;
import cn.dev33.satoken.stp.SaTokenInfo;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -40,7 +42,6 @@
public class AppRegisterService {
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
    private static final Pattern PASSWORD_PATTERN = Pattern.compile("^(?![0-9]+$)(?![a-zA-Z]+$)(?![^0-9a-zA-Z]+$).{8,32}$");
    private static final String REGISTER_TICKET_CACHE_PREFIX = "app:register:operator:";
    private static final String INVITE_CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    private static final int INVITE_CODE_LENGTH = 6;
@@ -48,8 +49,8 @@
    private final UserMapper userMapper;
    private final ClinicMapper clinicMapper;
    private final BCryptPasswordEncoder passwordEncoder;
    private final AppSmsCodeService appSmsCodeService;
    private final AppAuthSessionService appAuthSessionService;
    private final AppRegisterRegionService appRegisterRegionService;
    private final HztAppUserMapper hztAppUserMapper;
    private final HztClinicMapper hztClinicMapper;
@@ -62,8 +63,8 @@
     */
    public AppRegisterService(UserMapper userMapper,
                              ClinicMapper clinicMapper,
                              BCryptPasswordEncoder passwordEncoder,
                              AppSmsCodeService appSmsCodeService,
                              AppAuthSessionService appAuthSessionService,
                              AppRegisterRegionService appRegisterRegionService,
                              HztAppUserMapper hztAppUserMapper,
                              HztClinicMapper hztClinicMapper,
@@ -72,8 +73,8 @@
                              AppShareService appShareService) {
        this.userMapper = userMapper;
        this.clinicMapper = clinicMapper;
        this.passwordEncoder = passwordEncoder;
        this.appSmsCodeService = appSmsCodeService;
        this.appAuthSessionService = appAuthSessionService;
        this.appRegisterRegionService = appRegisterRegionService;
        this.hztAppUserMapper = hztAppUserMapper;
        this.hztClinicMapper = hztClinicMapper;
@@ -109,9 +110,8 @@
     * 完成普通用户注册,并按需绑定上级诊所用户。
     * 普通用户注册会先查询海诊通数据源,因此这里不包裹本地事务,避免事务先绑定 master 导致跨库查询失效。
     */
    public AppRegisterResultVO registerUser(AppRegisterUserRequest request) {
    public AppLoginResponse registerUser(AppRegisterUserRequest request) {
        String phone = normalizePhone(request.getPhone());
        String password = normalizePassword(request.getPassword());
        String inviteCode = normalizeInviteCode(request.getInviteCode());
        String bindTicket = normalizeBindTicket(request.getBindTicket());
@@ -120,7 +120,6 @@
        AppUser user = new AppUser();
        user.setPhone(phone);
        user.setPassword(passwordEncoder.encode(password));
        user.setType(0);
        user.setStatus(1);
@@ -132,7 +131,7 @@
        bindSuperiorClinicIfNecessary(user, inviteCode, bindTicket);
        userMapper.insert(user);
        return new AppRegisterResultVO(user.getId(), user.getType(), user.getClinicId(), null);
        return createLoginResponse(user);
    }
    /**
@@ -140,13 +139,12 @@
     */
    public AppRegisterOperatorPrepareResponse prepareOperatorRegister(AppRegisterOperatorPrepareRequest request) {
        String phone = normalizePhone(request.getPhone());
        String password = normalizePassword(request.getPassword());
        ensurePhoneAvailable(phone);
        appSmsCodeService.validateRegisterCode(phone, normalizeCode(request.getCode()));
        HztClinic hztClinic = findHztClinicByPhone(phone);
        OperatorRegisterDraft draft = buildOperatorDraft(phone, password, hztClinic);
        OperatorRegisterDraft draft = buildOperatorDraft(phone, hztClinic);
        String registerTicket = createRegisterTicket();
        cacheDraft(registerTicket, draft);
@@ -161,7 +159,7 @@
     * 完成经营者第二步,创建本地诊所与诊所用户。
     */
    @Transactional
    public AppRegisterResultVO completeOperatorRegister(AppRegisterOperatorCompleteRequest request) {
    public AppLoginResponse completeOperatorRegister(AppRegisterOperatorCompleteRequest request) {
        OperatorRegisterDraft draft = loadDraft(normalizeRegisterTicket(request.getRegisterTicket()));
        ensurePhoneAvailable(draft.getPhone());
@@ -184,14 +182,13 @@
        AppUser user = new AppUser();
        user.setNickname(clinic.getName());
        user.setPhone(draft.getPhone());
        user.setPassword(draft.getEncodedPassword());
        user.setType(1);
        user.setStatus(1);
        user.setClinicId(clinic.getId());
        userMapper.insert(user);
        clearDraft(request.getRegisterTicket());
        return new AppRegisterResultVO(user.getId(), user.getType(), clinic.getId(), clinic.getInviteCode());
        return createLoginResponse(user);
    }
    /**
@@ -218,6 +215,42 @@
        }
        user.setClinicId(bindingTarget.getClinicId());
        user.setSuperiorClinicUserId(bindingTarget.getSuperiorClinicUserId());
    }
    /**
     * 注册成功后立即建立 APP 登录态并返回统一登录响应。
     */
    private AppLoginResponse createLoginResponse(AppUser user) {
        appAuthSessionService.login(user.getId());
        return buildLoginResponse(user, appAuthSessionService.getTokenInfo());
    }
    /**
     * 使用 Sa-Token 信息构造 APP 登录响应。
     */
    private AppLoginResponse buildLoginResponse(AppUser user, SaTokenInfo tokenInfo) {
        return new AppLoginResponse(
                tokenInfo.getTokenValue(),
                "Bearer",
                tokenInfo.getTokenTimeout(),
                buildLoginUser(user)
        );
    }
    /**
     * 将注册后的 APP 用户实体转换为登录响应用户摘要。
     */
    private AppLoginUserVO buildLoginUser(AppUser user) {
        AppLoginUserVO loginUser = new AppLoginUserVO();
        loginUser.setId(user.getId());
        loginUser.setNickname(resolveFallbackNickname(user));
        loginUser.setPhone(user.getPhone());
        loginUser.setAvatar(trimToNull(user.getAvatar()));
        loginUser.setType(user.getType());
        loginUser.setStatus(user.getStatus());
        loginUser.setClinicId(user.getClinicId());
        loginUser.setMemberExpireAt(user.getMemberExpireAt());
        return loginUser;
    }
    /**
@@ -258,12 +291,11 @@
    }
    /**
     * 构造经营者注册草稿,避免第二步再次输入验证码与密码。
     * 构造经营者注册草稿,避免第二步再次输入验证码。
     */
    private OperatorRegisterDraft buildOperatorDraft(String phone, String password, HztClinic hztClinic) {
    private OperatorRegisterDraft buildOperatorDraft(String phone, HztClinic hztClinic) {
        OperatorRegisterDraft draft = new OperatorRegisterDraft();
        draft.setPhone(phone);
        draft.setEncodedPassword(passwordEncoder.encode(password));
        if (hztClinic == null) {
            return draft;
        }
@@ -423,6 +455,17 @@
    }
    /**
     * 优先使用用户昵称,缺失时回退到手机号脱敏昵称。
     */
    private String resolveFallbackNickname(AppUser user) {
        String nickname = trimToNull(user.getNickname());
        if (nickname != null) {
            return nickname;
        }
        return resolveFallbackNickname(user.getPhone());
    }
    /**
     * 归一化并校验手机号格式。
     */
    private String normalizePhone(String phone) {
@@ -440,20 +483,6 @@
        String normalized = trimToNull(code);
        if (normalized == null) {
            throw new IllegalArgumentException("请输入验证码");
        }
        return normalized;
    }
    /**
     * 归一化并校验注册密码规则。
     */
    private String normalizePassword(String password) {
        String normalized = trimToNull(password);
        if (normalized == null) {
            throw new IllegalArgumentException("请输入密码");
        }
        if (!PASSWORD_PATTERN.matcher(normalized).matches()) {
            throw new IllegalArgumentException("密码需为8-32位,且包含数字、字母、符号至少2种");
        }
        return normalized;
    }
@@ -515,8 +544,6 @@
    public static class OperatorRegisterDraft {
        private String phone;
        private String encodedPassword;
        private String contactName;