From 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 27 四月 2026 10:13:53 +0800
Subject: [PATCH] 小程序对接finish

---
 backend/src/main/java/com/sinata/appointment/service/MiniAuthApplicationService.java |  214 +++++++++++++++++++++++++++++++++--------------------
 1 files changed, 134 insertions(+), 80 deletions(-)

diff --git a/backend/src/main/java/com/sinata/appointment/service/MiniAuthApplicationService.java b/backend/src/main/java/com/sinata/appointment/service/MiniAuthApplicationService.java
index 256ea59..42d87ab 100644
--- a/backend/src/main/java/com/sinata/appointment/service/MiniAuthApplicationService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/MiniAuthApplicationService.java
@@ -2,11 +2,11 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sinata.appointment.constants.BizErrorCode;
-import com.sinata.appointment.constants.MiniLoginIntent;
 import com.sinata.appointment.constants.MiniUserType;
 import com.sinata.appointment.constants.MpStaffConstants;
 import com.sinata.appointment.config.MiniStpUtil;
 import com.sinata.appointment.dto.MiniLoginResponse;
+import com.sinata.appointment.dto.MiniProfileUpdateRequest;
 import com.sinata.appointment.dto.MiniWxLoginRequest;
 import com.sinata.appointment.entity.MpUser;
 import com.sinata.appointment.entity.SysDept;
@@ -28,8 +28,6 @@
 @Service
 public class MiniAuthApplicationService {
 
-    private static final String H5_PHONE_OPEN_ID_PREFIX = "H5_PHONE_";
-
     private final MpUserMapper mpUserMapper;
     private final SysDeptMapper sysDeptMapper;
     private final WechatMiniGateway wechatMiniGateway;
@@ -47,68 +45,24 @@
     public MiniLoginResponse loginByWx(MiniWxLoginRequest req) {
         WxCode2SessionResult session = wechatMiniGateway.code2Session(req.getWxCode());
         String openId = session.getOpenId();
-        String phoneRaw = wechatMiniGateway.getPhoneNumber(req.getPhoneCode());
-        String phone = PhoneNormalize.mainland(phoneRaw);
 
         MpUser user =
                 mpUserMapper.selectOne(
                         new LambdaQueryWrapper<MpUser>()
                                 .eq(MpUser::getOpenId, openId)
                                 .eq(MpUser::getDelFlag, 0));
-
-        if (MiniLoginIntent.EMPLOYEE.equals(req.getLoginIntent())) {
-            if (user == null) {
-                user = tryBindPreRegisteredEmployee(openId, session.getUnionId(), phone, phoneRaw);
+        if (user == null) {
+            if (!StringUtils.hasText(req.getPhoneCode())) {
+                throw new BizException(BizErrorCode.MINI_PHONE_AUTH_REQUIRED, "请先授权手机号完成登录");
             }
-            if (user == null) {
-                throw new BizException(BizErrorCode.MINI_STAFF_NOT_BOUND, "员工未登记,请联系管理员开通后再登录");
-            }
-            assertAccountEnabled(user);
-            if (!MiniUserType.EMPLOYEE.equals(user.getUserType())) {
-                throw new BizException(
-                        BizErrorCode.MINI_LOGIN_INTENT_MISMATCH, "当前微信为访客身份,请使用访客入口登录");
-            }
-            if (user.getDeptId() == null) {
-                throw new BizException(BizErrorCode.MINI_STAFF_NOT_BOUND, "员工未分配部门");
-            }
-            assertPhoneMatchesStored(user, phone);
-            syncMpUserMobile(user, phoneRaw);
-            if (user.getUnionId() == null && session.getUnionId() != null) {
-                user.setUnionId(session.getUnionId());
-                mpUserMapper.updateById(user);
-            }
-            MiniStpUtil.login(user.getId());
-            return buildResponse(user);
+            String phoneRaw = wechatMiniGateway.getPhoneNumber(req.getPhoneCode());
+            String phone = PhoneNormalize.mainland(phoneRaw);
+            user = resolveUserByPhoneForWxLogin(openId, session.getUnionId(), phone, phoneRaw);
         }
-
-        if (MiniLoginIntent.VISITOR.equals(req.getLoginIntent())) {
-            if (user == null) {
-                MpUser created = new MpUser();
-                created.setOpenId(openId);
-                created.setUnionId(session.getUnionId());
-                created.setUserType(MiniUserType.VISITOR);
-                created.setMobile(phoneRaw);
-                created.setStatus(1);
-                mpUserMapper.insert(created);
-                user = mpUserMapper.selectById(created.getId());
-            } else {
-                assertAccountEnabled(user);
-                if (!MiniUserType.VISITOR.equals(user.getUserType())) {
-                    throw new BizException(
-                            BizErrorCode.MINI_LOGIN_INTENT_MISMATCH, "当前微信已登记为员工,请使用员工入口登录");
-                }
-                assertPhoneMatchesStored(user, phone);
-                syncMpUserMobile(user, phoneRaw);
-                if (user.getUnionId() == null && session.getUnionId() != null) {
-                    user.setUnionId(session.getUnionId());
-                    mpUserMapper.updateById(user);
-                }
-            }
-            MiniStpUtil.login(user.getId());
-            return buildResponse(user);
-        }
-
-        throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "loginIntent 无效");
+        assertUserCanLogin(user);
+        syncUnionIdIfNeeded(user, session.getUnionId());
+        MiniStpUtil.login(user.getId());
+        return buildResponse(user);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -130,15 +84,44 @@
                         .orElse(null);
         if (user == null) {
             user = registerVisitorForPhoneLogin(mobileRaw);
-        } else {
-            assertAccountEnabled(user);
         }
+        assertUserCanLogin(user);
+        MiniStpUtil.login(user.getId());
+        return buildResponse(user);
+    }
+
+    private MpUser resolveUserByPhoneForWxLogin(
+            String openId, String unionId, String normalizedPhone, String phoneRaw) {
+        if (!normalizedPhone.matches("^\\d{11}$")) {
+            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "手机号格式不正确");
+        }
+        List<MpUser> users =
+                mpUserMapper.selectList(
+                        new LambdaQueryWrapper<MpUser>()
+                                .eq(MpUser::getDelFlag, 0));
+        MpUser user =
+                users.stream()
+                        .filter(Objects::nonNull)
+                        .filter(u -> normalizedPhone.equals(PhoneNormalize.mainland(u.getMobile())))
+                        .sorted(this::compareByLoginPriority)
+                        .findFirst()
+                        .orElse(null);
+        if (user == null) {
+            MpUser created = new MpUser();
+            created.setOpenId(openId);
+            created.setUnionId(unionId);
+            created.setUserType(MiniUserType.VISITOR);
+            created.setMobile(phoneRaw);
+            created.setStatus(1);
+            mpUserMapper.insert(created);
+            return mpUserMapper.selectById(created.getId());
+        }
+        assertAccountEnabled(user);
         if (MiniUserType.EMPLOYEE.equals(user.getUserType()) && user.getDeptId() == null) {
             throw new BizException(BizErrorCode.MINI_STAFF_NOT_BOUND, "员工未分配部门");
         }
-        ensureOpenIdForPhoneLogin(user);
-        MiniStpUtil.login(user.getId());
-        return buildResponse(user);
+        bindOpenIdIfNeeded(user, openId, unionId, phoneRaw);
+        return mpUserMapper.selectById(user.getId());
     }
 
     /**
@@ -171,16 +154,6 @@
         return null;
     }
 
-    private void assertPhoneMatchesStored(MpUser user, String normalizedPhone) {
-        if (user.getMobile() == null || user.getMobile().isBlank()) {
-            return;
-        }
-        String stored = PhoneNormalize.mainland(user.getMobile());
-        if (!stored.isBlank() && !stored.equals(normalizedPhone)) {
-            throw new BizException(BizErrorCode.MINI_PHONE_MISMATCH, "手机号与登记信息不一致");
-        }
-    }
-
     private void syncMpUserMobile(MpUser user, String phoneRaw) {
         boolean needUpdate = false;
         if (user.getMobile() == null || user.getMobile().isBlank()) {
@@ -190,6 +163,57 @@
         if (needUpdate) {
             mpUserMapper.updateById(user);
         }
+    }
+
+    private void bindOpenIdIfNeeded(MpUser user, String openId, String unionId, String phoneRaw) {
+        if (user == null) {
+            return;
+        }
+        String currentOpenId = user.getOpenId();
+        if (StringUtils.hasText(currentOpenId)
+                && !Objects.equals(currentOpenId, openId)
+                && !MpStaffConstants.isPlaceholderOpenId(currentOpenId)) {
+            throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "该手机号已绑定其他微信账号");
+        }
+        if (MiniUserType.EMPLOYEE.equals(user.getUserType())
+                && MpStaffConstants.isPlaceholderOpenId(currentOpenId)) {
+            MpUser bound = tryBindPreRegisteredEmployee(openId, unionId, PhoneNormalize.mainland(phoneRaw), phoneRaw);
+            if (bound == null) {
+                throw new BizException(BizErrorCode.MINI_STAFF_NOT_BOUND, "员工未登记,请联系管理员开通后再登录");
+            }
+            return;
+        }
+        boolean needUpdate = false;
+        if (!Objects.equals(currentOpenId, openId)) {
+            user.setOpenId(openId);
+            needUpdate = true;
+        }
+        if (!StringUtils.hasText(user.getUnionId()) && StringUtils.hasText(unionId)) {
+            user.setUnionId(unionId);
+            needUpdate = true;
+        }
+        if (!StringUtils.hasText(user.getMobile()) && StringUtils.hasText(phoneRaw)) {
+            user.setMobile(phoneRaw);
+            needUpdate = true;
+        }
+        if (needUpdate) {
+            mpUserMapper.updateById(user);
+        }
+    }
+
+    private void assertUserCanLogin(MpUser user) {
+        assertAccountEnabled(user);
+        if (MiniUserType.EMPLOYEE.equals(user.getUserType()) && user.getDeptId() == null) {
+            throw new BizException(BizErrorCode.MINI_STAFF_NOT_BOUND, "员工未分配部门");
+        }
+    }
+
+    private void syncUnionIdIfNeeded(MpUser user, String unionId) {
+        if (user == null || !StringUtils.hasText(unionId) || StringUtils.hasText(user.getUnionId())) {
+            return;
+        }
+        user.setUnionId(unionId);
+        mpUserMapper.updateById(user);
     }
 
     private void assertAccountEnabled(MpUser user) {
@@ -223,14 +247,6 @@
         return 2;
     }
 
-    private void ensureOpenIdForPhoneLogin(MpUser user) {
-        if (StringUtils.hasText(user.getOpenId())) {
-            return;
-        }
-        user.setOpenId(H5_PHONE_OPEN_ID_PREFIX + user.getId());
-        mpUserMapper.updateById(user);
-    }
-
     private MpUser registerVisitorForPhoneLogin(String mobileRaw) {
         MpUser created = new MpUser();
         created.setUserType(MiniUserType.VISITOR);
@@ -248,6 +264,27 @@
         }
         assertAccountEnabled(u);
         return u;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public MpUser updateProfile(MiniProfileUpdateRequest request) {
+        MpUser user = currentMpUser();
+        String nickName = trimRequired(request.getNickName(), "姓名不能为空");
+        if (nickName.length() > 30) {
+            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "姓名长度不能超过30个字符");
+        }
+        String gender = trimToNull(request.getGender());
+        if (StringUtils.hasText(gender) && !"男".equals(gender) && !"女".equals(gender)) {
+            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "性别仅支持男或女");
+        }
+        String companyName = trimToNull(request.getCompanyName());
+        String avatarUrl = trimToNull(request.getAvatarUrl());
+        user.setNickName(nickName);
+        user.setGender(gender);
+        user.setCompanyName(companyName);
+        user.setAvatarUrl(avatarUrl);
+        mpUserMapper.updateById(user);
+        return mpUserMapper.selectById(user.getId());
     }
 
     public void logout() {
@@ -276,7 +313,24 @@
                 .deptName(deptName)
                 .deptLeader(leader)
                 .avatarUrl(user.getAvatarUrl())
+                .companyName(user.getCompanyName())
                 .displayName(name)
                 .build();
     }
+
+    private static String trimToNull(String value) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
+    }
+
+    private static String trimRequired(String value, String message) {
+        String trimmed = trimToNull(value);
+        if (!StringUtils.hasText(trimmed)) {
+            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, message);
+        }
+        return trimmed;
+    }
 }

--
Gitblit v1.7.1