package com.sinata.appointment.service;
|
|
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.MiniWxLoginRequest;
|
import com.sinata.appointment.entity.MpUser;
|
import com.sinata.appointment.entity.SysDept;
|
import com.sinata.appointment.exception.BizException;
|
import com.sinata.appointment.integration.WechatMiniGateway;
|
import com.sinata.appointment.integration.WxCode2SessionResult;
|
import com.sinata.appointment.mapper.SysDeptMapper;
|
import com.sinata.appointment.mapper.MpUserMapper;
|
import java.util.List;
|
import java.util.Objects;
|
import com.sinata.appointment.util.PhoneNormalize;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
/**
|
* 小程序登录:js_code → openId;phoneCode + access_token → 手机号;访客/员工与平台 sys_user 无关联。
|
*/
|
@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;
|
|
public MiniAuthApplicationService(
|
MpUserMapper mpUserMapper,
|
SysDeptMapper sysDeptMapper,
|
WechatMiniGateway wechatMiniGateway) {
|
this.mpUserMapper = mpUserMapper;
|
this.sysDeptMapper = sysDeptMapper;
|
this.wechatMiniGateway = wechatMiniGateway;
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
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) {
|
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);
|
}
|
|
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 无效");
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public MiniLoginResponse loginByPhone(String mobileRaw) {
|
String phone = PhoneNormalize.mainland(mobileRaw);
|
if (!phone.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 -> phone.equals(PhoneNormalize.mainland(u.getMobile())))
|
.sorted(this::compareByLoginPriority)
|
.findFirst()
|
.orElse(null);
|
if (user == null) {
|
user = registerVisitorForPhoneLogin(mobileRaw);
|
} else {
|
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);
|
}
|
|
/**
|
* 管理端预建档员工(openId 为 PLHD_ 占位):按手机号绑定当前微信 openId。
|
*/
|
private MpUser tryBindPreRegisteredEmployee(
|
String openId, String unionId, String normalizedPhone, String phoneRaw) {
|
List<MpUser> candidates =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
|
.eq(MpUser::getDelFlag, 0)
|
.likeRight(MpUser::getOpenId, MpStaffConstants.PLACEHOLDER_OPEN_ID_PREFIX));
|
for (MpUser u : candidates) {
|
if (u.getOpenId() == null || !MpStaffConstants.isPlaceholderOpenId(u.getOpenId())) {
|
continue;
|
}
|
String stored = PhoneNormalize.mainland(u.getMobile());
|
if (stored.isBlank() || !stored.equals(normalizedPhone)) {
|
continue;
|
}
|
u.setOpenId(openId);
|
if (unionId != null) {
|
u.setUnionId(unionId);
|
}
|
syncMpUserMobile(u, phoneRaw);
|
mpUserMapper.updateById(u);
|
return mpUserMapper.selectById(u.getId());
|
}
|
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()) {
|
user.setMobile(phoneRaw);
|
needUpdate = true;
|
}
|
if (needUpdate) {
|
mpUserMapper.updateById(user);
|
}
|
}
|
|
private void assertAccountEnabled(MpUser user) {
|
if (user == null || user.getStatus() == null || user.getStatus() != 0) {
|
return;
|
}
|
if (MiniUserType.EMPLOYEE.equals(user.getUserType())) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "员工账号已禁用");
|
}
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "账号已被禁用");
|
}
|
|
private int compareByLoginPriority(MpUser a, MpUser b) {
|
int pa = loginPriority(a);
|
int pb = loginPriority(b);
|
if (pa != pb) {
|
return Integer.compare(pa, pb);
|
}
|
Long aid = a.getId() == null ? Long.MAX_VALUE : a.getId();
|
Long bid = b.getId() == null ? Long.MAX_VALUE : b.getId();
|
return Long.compare(aid, bid);
|
}
|
|
private int loginPriority(MpUser u) {
|
if (MiniUserType.EMPLOYEE.equals(u.getUserType())) {
|
return 0;
|
}
|
if (MiniUserType.VISITOR.equals(u.getUserType())) {
|
return 1;
|
}
|
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);
|
created.setMobile(mobileRaw);
|
created.setStatus(1);
|
mpUserMapper.insert(created);
|
return mpUserMapper.selectById(created.getId());
|
}
|
|
public MpUser currentMpUser() {
|
long id = MiniStpUtil.getLoginIdAsLong();
|
MpUser u = mpUserMapper.selectById(id);
|
if (u == null || u.getDelFlag() != null && u.getDelFlag() == 1) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "用户不存在或已禁用");
|
}
|
assertAccountEnabled(u);
|
return u;
|
}
|
|
public void logout() {
|
MiniStpUtil.logout();
|
}
|
|
private MiniLoginResponse buildResponse(MpUser user) {
|
String name =
|
user.getNickName() != null && !user.getNickName().isBlank()
|
? user.getNickName()
|
: (MiniUserType.EMPLOYEE.equals(user.getUserType()) ? "员工" : "访客");
|
String deptName = null;
|
if (MiniUserType.EMPLOYEE.equals(user.getUserType()) && user.getDeptId() != null) {
|
SysDept dept = sysDeptMapper.selectById(user.getDeptId());
|
deptName = dept == null ? null : dept.getDeptName();
|
}
|
Boolean leader =
|
MiniUserType.EMPLOYEE.equals(user.getUserType())
|
? Boolean.TRUE.equals(user.getDeptLeaderFlag())
|
: null;
|
return MiniLoginResponse.builder()
|
.token(MiniStpUtil.getTokenValue())
|
.mpUserId(user.getId())
|
.userType(user.getUserType())
|
.deptId(user.getDeptId())
|
.deptName(deptName)
|
.deptLeader(leader)
|
.avatarUrl(user.getAvatarUrl())
|
.displayName(name)
|
.build();
|
}
|
}
|