|
|
@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import shop.alien.store.config.BaseRedisService;
|
|
|
@@ -15,11 +14,9 @@ import shop.alien.store.dto.UserProfileUpdateDto;
|
|
|
import shop.alien.store.feign.AlienStoreFeign;
|
|
|
import shop.alien.store.service.DiningUserService;
|
|
|
import shop.alien.store.util.TokenUtil;
|
|
|
-import shop.alien.store.util.WeChatMiniProgramUtil;
|
|
|
-import shop.alien.store.util.WeChatMiniProgramUtil.WeChatSessionInfo;
|
|
|
-import shop.alien.entity.result.R;
|
|
|
import shop.alien.store.vo.DiningUserVo;
|
|
|
import shop.alien.store.vo.TokenVerifyVo;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
import shop.alien.entity.store.LifeUser;
|
|
|
import shop.alien.mapper.LifeUserMapper;
|
|
|
import shop.alien.util.common.JwtUtil;
|
|
|
@@ -29,16 +26,9 @@ import io.jsonwebtoken.MalformedJwtException;
|
|
|
import io.jsonwebtoken.SignatureException;
|
|
|
|
|
|
import java.util.Date;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.Map;
|
|
|
|
|
|
/**
|
|
|
- * 点餐用户服务实现类
|
|
|
- * 标准流程:通过 wx.login() 获取 code,调用 jscode2session 获取 openid
|
|
|
- *
|
|
|
- * @author ssk
|
|
|
- * @version 2.0
|
|
|
- * @date 2024/12/4
|
|
|
+ * 点餐用户服务:与 C 端 users 体系一致,登录发 token 由网关等原有链路完成;store 侧仅校验 {@code user_{phone}} Redis 与 JWT。
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
@@ -47,363 +37,28 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
|
|
|
private final LifeUserMapper lifeUserMapper;
|
|
|
private final BaseRedisService baseRedisService;
|
|
|
- private final WeChatMiniProgramUtil weChatMiniProgramUtil;
|
|
|
private final AlienStoreFeign alienStoreFeign;
|
|
|
|
|
|
- @Value("${jwt.expiration-time}")
|
|
|
- private String effectiveTime;
|
|
|
-
|
|
|
- /**
|
|
|
- * Redis key 常量(小程序专用,避免与 APP 端冲突)
|
|
|
- */
|
|
|
- private static final String REDIS_KEY_OPENID_PREFIX = "wechat:openid:";
|
|
|
- private static final String REDIS_KEY_TOKEN_PREFIX = "miniprogram_user_token:";
|
|
|
- private static final String REDIS_KEY_USER_PHONE_PREFIX = "miniprogram_user_";
|
|
|
- private static final long OPENID_MAPPING_EXPIRE_SECONDS = 30 * 24 * 60 * 60L; // 30天
|
|
|
- private static final long TOKEN_EXPIRE_SECONDS = 7 * 24 * 60 * 60L; // 7天
|
|
|
-
|
|
|
- @Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
- public DiningUserVo wechatLogin(String code, String phoneCode, String macIp) {
|
|
|
- // 1. 通过 code2session 获取 openid 和 session_key
|
|
|
- String openid = getOpenidFromCode(code);
|
|
|
- if (StringUtils.isBlank(openid)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 如果提供了 phoneCode,先解析手机号(用于返回给前端和更新用户)
|
|
|
- String parsedPhone = null;
|
|
|
- if (StringUtils.isNotBlank(phoneCode)) {
|
|
|
- parsedPhone = weChatMiniProgramUtil.getPhoneNumberByCode(phoneCode);
|
|
|
- if (StringUtils.isNotBlank(parsedPhone)) {
|
|
|
- log.info("成功解析手机号: {}", maskString(parsedPhone, 7));
|
|
|
- } else {
|
|
|
- log.warn("解析手机号失败,phoneCode可能已过期或无效");
|
|
|
- }
|
|
|
- }
|
|
|
- if (StringUtils.isBlank(parsedPhone)) {
|
|
|
- log.info("微信登录未传手机号或解析失败: openid={}, phoneCode={}", maskString(openid, 8), StringUtils.isNotBlank(phoneCode) ? "已传" : "未传");
|
|
|
- }
|
|
|
-
|
|
|
- // 3. 查找或创建用户(传入解析后的手机号,避免重复解析)
|
|
|
- LifeUser user = findOrCreateUser(openid, parsedPhone);
|
|
|
- if (user == null) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 检查用户状态(提前检查,避免不必要的操作)
|
|
|
- if (!isUserValid(user)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 生成并存储 token(传入本次解析的手机号,确保有手机号时一定写入 miniprogram_user_{phone})
|
|
|
- String token = generateAndStoreToken(openid, user, parsedPhone);
|
|
|
-
|
|
|
- // 6. 构建返回对象,优先使用解析后的手机号返回给前端
|
|
|
- return buildDiningUserVo(user, token, openid, parsedPhone);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 通过 code 获取 openid
|
|
|
- */
|
|
|
- private String getOpenidFromCode(String code) {
|
|
|
- WeChatSessionInfo sessionInfo = weChatMiniProgramUtil.code2Session(code);
|
|
|
- if (sessionInfo == null || sessionInfo.getErrcode() != null) {
|
|
|
- Integer errcode = sessionInfo != null ? sessionInfo.getErrcode() : null;
|
|
|
- String errmsg = sessionInfo != null ? sessionInfo.getErrmsg() : "调用微信接口失败";
|
|
|
- log.error("登录失败:code2session 失败, errcode={}, errmsg={}", errcode, errmsg);
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- String openid = sessionInfo.getOpenid();
|
|
|
- if (StringUtils.isBlank(openid)) {
|
|
|
- log.error("登录失败:无法获取 openid");
|
|
|
- return null;
|
|
|
- }
|
|
|
- log.info("成功获取 openid: {}", maskString(openid, 8));
|
|
|
- return openid;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 查找或创建用户
|
|
|
- * 规则:只要 user_phone 存在,就代表存在账号,不创建新账号
|
|
|
- *
|
|
|
- * @param openid 微信OpenID
|
|
|
- * @param phone 解析后的手机号(如果为null,说明没有提供phoneCode或解析失败)
|
|
|
- */
|
|
|
- private LifeUser findOrCreateUser(String openid, String phone) {
|
|
|
- LifeUser user = null;
|
|
|
-
|
|
|
- // 1. 如果提供了手机号,优先通过手机号查找(只要手机号存在就代表账号存在)
|
|
|
- if (StringUtils.isNotBlank(phone)) {
|
|
|
- user = findUserByPhone(phone);
|
|
|
- if (user != null) {
|
|
|
- // 账号已存在,建立 openid 和 userId 的映射关系
|
|
|
- saveOpenidMapping(openid, user.getId());
|
|
|
- log.info("通过手机号找到已存在账号: phone={}, userId={}, openid={}",
|
|
|
- maskString(phone, 7), user.getId(), maskString(openid, 8));
|
|
|
- // 如果用户已有手机号,但新解析的手机号不同,更新手机号
|
|
|
- if (StringUtils.isNotBlank(user.getUserPhone()) && !user.getUserPhone().equals(phone)) {
|
|
|
- log.info("检测到手机号变更,更新手机号: userId={}, oldPhone={}, newPhone={}",
|
|
|
- user.getId(), maskString(user.getUserPhone(), 7), maskString(phone, 7));
|
|
|
- updateUserPhone(user, phone);
|
|
|
- }
|
|
|
- return user;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 如果通过手机号找不到(或未提供手机号),尝试通过 openid 查找
|
|
|
- user = findUserByOpenid(openid);
|
|
|
-
|
|
|
- // 3. 如果通过 openid 找到了用户,且提供了手机号,更新手机号
|
|
|
- if (user != null) {
|
|
|
- if (StringUtils.isNotBlank(phone) && StringUtils.isBlank(user.getUserPhone())) {
|
|
|
- // 用户存在但没有手机号,更新手机号
|
|
|
- updateUserPhone(user, phone);
|
|
|
- } else if (StringUtils.isNotBlank(phone) && StringUtils.isNotBlank(user.getUserPhone())
|
|
|
- && !user.getUserPhone().equals(phone)) {
|
|
|
- // 用户已有手机号,但新解析的手机号不同,也更新
|
|
|
- log.info("检测到手机号变更,更新手机号: userId={}, oldPhone={}, newPhone={}",
|
|
|
- user.getId(), maskString(user.getUserPhone(), 7), maskString(phone, 7));
|
|
|
- updateUserPhone(user, phone);
|
|
|
- }
|
|
|
- return user;
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 如果都找不到,且未提供手机号(或手机号不存在),才创建新账号
|
|
|
- // 注意:如果提供了手机号但找不到,说明这个手机号没有注册过,可以创建新账号
|
|
|
- user = createNewUser(openid, phone);
|
|
|
- if (user != null) {
|
|
|
- saveOpenidMapping(openid, user.getId());
|
|
|
- }
|
|
|
-
|
|
|
- return user;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 通过 openid 查找用户
|
|
|
- */
|
|
|
- private LifeUser findUserByOpenid(String openid) {
|
|
|
- String openidKey = REDIS_KEY_OPENID_PREFIX + openid;
|
|
|
- String userIdStr = baseRedisService.getString(openidKey);
|
|
|
-
|
|
|
- if (StringUtils.isBlank(userIdStr)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- Integer userId = Integer.parseInt(userIdStr);
|
|
|
- LifeUser user = lifeUserMapper.selectById(userId);
|
|
|
- if (user != null) {
|
|
|
- log.info("通过 openid 找到用户: openid={}, userId={}", maskString(openid, 8), userId);
|
|
|
- }
|
|
|
- return user;
|
|
|
- } catch (NumberFormatException e) {
|
|
|
- log.warn("Redis 中的 userId 格式错误: {}", userIdStr);
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 通过手机号查找用户
|
|
|
- */
|
|
|
- private LifeUser findUserByPhone(String phone) {
|
|
|
- LambdaQueryWrapper<LifeUser> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
- queryWrapper.eq(LifeUser::getUserPhone, phone);
|
|
|
- LifeUser user = lifeUserMapper.selectOne(queryWrapper);
|
|
|
- if (user != null) {
|
|
|
- log.info("通过手机号找到用户: phone={}, userId={}", maskString(phone, 7), user.getId());
|
|
|
- }
|
|
|
- return user;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 创建新用户
|
|
|
- */
|
|
|
- private LifeUser createNewUser(String openid, String phone) {
|
|
|
- LifeUser user = new LifeUser();
|
|
|
- if (StringUtils.isNotBlank(phone)) {
|
|
|
- user.setUserPhone(phone);
|
|
|
- user.setUserName(phone);
|
|
|
- user.setRealName(phone);
|
|
|
- } else {
|
|
|
- // 没有手机号时,使用 openid 后8位作为临时用户名
|
|
|
- String tempName = "微信用户" + openid.substring(Math.max(0, openid.length() - 8));
|
|
|
- user.setUserName(tempName);
|
|
|
- user.setRealName(tempName);
|
|
|
- }
|
|
|
- user.setCreatedTime(new Date());
|
|
|
-
|
|
|
- int ret = lifeUserMapper.insert(user);
|
|
|
- if (ret != 1) {
|
|
|
- log.error("创建用户失败");
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // MyBatis-Plus 的 insert 会自动填充 ID,不需要重新查询
|
|
|
- log.info("创建新用户: openid={}, userId={}, phone={}", maskString(openid, 8), user.getId(), maskString(phone, 7));
|
|
|
- return user;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 更新用户手机号
|
|
|
- */
|
|
|
- private void updateUserPhone(LifeUser user, String phone) {
|
|
|
- user.setUserPhone(phone);
|
|
|
- if (StringUtils.isBlank(user.getUserName()) || user.getUserName().startsWith("微信用户")) {
|
|
|
- user.setUserName(phone);
|
|
|
- }
|
|
|
- if (StringUtils.isBlank(user.getRealName()) || user.getRealName().startsWith("微信用户")) {
|
|
|
- user.setRealName(phone);
|
|
|
- }
|
|
|
- user.setUpdatedTime(new Date());
|
|
|
- lifeUserMapper.updateById(user);
|
|
|
- log.info("更新用户手机号: userId={}, phone={}", user.getId(), maskString(phone, 7));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 保存 openid 和 userId 的映射关系
|
|
|
- */
|
|
|
- private void saveOpenidMapping(String openid, Integer userId) {
|
|
|
- String openidKey = REDIS_KEY_OPENID_PREFIX + openid;
|
|
|
- baseRedisService.setString(openidKey, userId.toString(), OPENID_MAPPING_EXPIRE_SECONDS);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 检查用户状态是否有效
|
|
|
- */
|
|
|
- private boolean isUserValid(LifeUser user) {
|
|
|
- if (user.getIsBanned() != null && user.getIsBanned() == 1) {
|
|
|
- log.warn("用户已被封禁: userId={}", user.getId());
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (user.getLogoutFlag() != null && user.getLogoutFlag() == 1) {
|
|
|
- log.warn("用户已注销: userId={}", user.getId());
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成并存储 token
|
|
|
- * @param openid 微信 openid
|
|
|
- * @param user 用户实体(可能尚未持久化手机号)
|
|
|
- * @param currentLoginPhone 本次登录解析到的手机号(可选),有则优先用其写入 miniprogram_user_{phone},避免只存 openid 不存 phone 的情况
|
|
|
- */
|
|
|
- private String generateAndStoreToken(String openid, LifeUser user, String currentLoginPhone) {
|
|
|
- // 构建 token 信息
|
|
|
- Map<String, String> tokenMap = buildTokenMap(openid, user);
|
|
|
- String userName = StringUtils.isNotBlank(user.getUserName()) ? user.getUserName() : "用户";
|
|
|
- String token = generateToken(openid, userName, tokenMap);
|
|
|
-
|
|
|
- // 存入 Redis(使用 openid 作为 key,始终写入)
|
|
|
- baseRedisService.setString(REDIS_KEY_TOKEN_PREFIX + openid, token, TOKEN_EXPIRE_SECONDS);
|
|
|
-
|
|
|
- // 兼容旧版本:有手机号则同时按手机号存一份(优先用本次登录解析到的手机号,否则用用户实体中的手机号)
|
|
|
- String phoneToStore = StringUtils.isNotBlank(currentLoginPhone) ? currentLoginPhone : user.getUserPhone();
|
|
|
- if (StringUtils.isNotBlank(phoneToStore)) {
|
|
|
- baseRedisService.setString(REDIS_KEY_USER_PHONE_PREFIX + phoneToStore, token, TOKEN_EXPIRE_SECONDS);
|
|
|
- }
|
|
|
-
|
|
|
- return token;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 构建 token 信息 Map
|
|
|
- */
|
|
|
- private Map<String, String> buildTokenMap(String openid, LifeUser user) {
|
|
|
- Map<String, String> tokenMap = new HashMap<>();
|
|
|
- tokenMap.put("openid", openid);
|
|
|
- tokenMap.put("phone", StringUtils.isNotBlank(user.getUserPhone()) ? user.getUserPhone() : "");
|
|
|
- tokenMap.put("userName", StringUtils.isNotBlank(user.getUserName()) ? user.getUserName() : "用户");
|
|
|
- tokenMap.put("userId", user.getId().toString());
|
|
|
- tokenMap.put("userType", "miniprogram_user");
|
|
|
- return tokenMap;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 构建返回对象
|
|
|
- */
|
|
|
- private DiningUserVo buildDiningUserVo(LifeUser user, String token, String openid) {
|
|
|
- DiningUserVo diningUserVo = new DiningUserVo();
|
|
|
- diningUserVo.setId(user.getId().longValue());
|
|
|
- // 优先使用 user 对象中的手机号(如果通过 phoneCode 解析并更新了,这里会是最新的)
|
|
|
- diningUserVo.setPhone(user.getUserPhone());
|
|
|
- diningUserVo.setNickName(user.getUserName());
|
|
|
- diningUserVo.setAvatarUrl(user.getUserImage());
|
|
|
- diningUserVo.setStatus(0);
|
|
|
- diningUserVo.setCreatedTime(user.getCreatedTime());
|
|
|
- diningUserVo.setToken(token);
|
|
|
- diningUserVo.setOpenId(openid);
|
|
|
- return diningUserVo;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 构建返回对象(带解析后的手机号)
|
|
|
- */
|
|
|
- private DiningUserVo buildDiningUserVo(LifeUser user, String token, String openid, String parsedPhone) {
|
|
|
- DiningUserVo diningUserVo = buildDiningUserVo(user, token, openid);
|
|
|
- // 如果解析到了手机号,优先使用解析后的手机号返回给前端
|
|
|
- if (StringUtils.isNotBlank(parsedPhone)) {
|
|
|
- diningUserVo.setPhone(parsedPhone);
|
|
|
- }
|
|
|
- return diningUserVo;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 掩码字符串(用于日志脱敏)
|
|
|
- */
|
|
|
- private String maskString(String str, int visibleLength) {
|
|
|
- if (StringUtils.isBlank(str) || str.length() <= visibleLength) {
|
|
|
- return "****";
|
|
|
- }
|
|
|
- return str.substring(0, Math.min(visibleLength, str.length())) + "****";
|
|
|
- }
|
|
|
-
|
|
|
- private String generateToken(String userId, String userName, Map<String, String> tokenMap) {
|
|
|
- int effectiveTimeInt = Integer.parseInt(effectiveTime.substring(0, effectiveTime.length() - 1));
|
|
|
- String effectiveTimeUnit = effectiveTime.substring(effectiveTime.length() - 1);
|
|
|
- long effectiveTimeIntLong = 0L;
|
|
|
- switch (effectiveTimeUnit) {
|
|
|
- case "s":
|
|
|
- effectiveTimeIntLong = effectiveTimeInt * 1000L;
|
|
|
- break;
|
|
|
- case "m":
|
|
|
- effectiveTimeIntLong = effectiveTimeInt * 60L * 1000L;
|
|
|
- break;
|
|
|
- case "h":
|
|
|
- effectiveTimeIntLong = effectiveTimeInt * 60L * 60L * 1000L;
|
|
|
- break;
|
|
|
- case "d":
|
|
|
- effectiveTimeIntLong = effectiveTimeInt * 24L * 60L * 60L * 1000L;
|
|
|
- break;
|
|
|
- default:
|
|
|
- effectiveTimeIntLong = effectiveTimeInt * 24L * 60L * 60L * 1000L;
|
|
|
- }
|
|
|
- return JwtUtil.createJWT("user_" + userId, userName, JSONObject.toJSONString(tokenMap), effectiveTimeIntLong);
|
|
|
- }
|
|
|
+ /** 与网关 {@code LifeUserService#userLogin} 写入 Redis 的 key 一致:{@code user_} + 手机号 */
|
|
|
+ private static final String REDIS_USER_TOKEN_PREFIX = "user_";
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public DiningUserVo updateProfile(UserProfileUpdateDto dto) {
|
|
|
- // 1. 查询用户是否存在
|
|
|
LifeUser user = lifeUserMapper.selectById(dto.getUserId().intValue());
|
|
|
if (user == null) {
|
|
|
log.warn("更新个人信息失败:用户不存在, userId={}", dto.getUserId());
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 2. 获取当前用户ID(用于设置 updatedUserId)
|
|
|
Integer currentUserId = TokenUtil.getCurrentUserId();
|
|
|
if (currentUserId == null) {
|
|
|
- currentUserId = dto.getUserId().intValue(); // 如果没有 token,使用被更新的用户ID
|
|
|
+ currentUserId = dto.getUserId().intValue();
|
|
|
}
|
|
|
|
|
|
- // 3. 使用 LambdaUpdateWrapper 只更新需要更新的字段,避免更新不应该更新的字段
|
|
|
LambdaUpdateWrapper<LifeUser> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
updateWrapper.eq(LifeUser::getId, dto.getUserId().intValue());
|
|
|
|
|
|
- // 只更新非空字段
|
|
|
if (StringUtils.isNotBlank(dto.getNickName())) {
|
|
|
updateWrapper.set(LifeUser::getUserName, dto.getNickName());
|
|
|
}
|
|
|
@@ -435,11 +90,9 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
updateWrapper.set(LifeUser::getJianjie, dto.getJianjie());
|
|
|
}
|
|
|
|
|
|
- // 设置更新时间和更新人ID
|
|
|
updateWrapper.set(LifeUser::getUpdatedTime, new Date());
|
|
|
updateWrapper.set(LifeUser::getUpdatedUserId, currentUserId);
|
|
|
|
|
|
- // 4. 执行更新
|
|
|
int result = lifeUserMapper.update(null, updateWrapper);
|
|
|
if (result != 1) {
|
|
|
log.error("更新用户信息失败, userId={}", dto.getUserId());
|
|
|
@@ -447,27 +100,23 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
}
|
|
|
log.info("用户信息更新成功, userId={}, updatedUserId={}", dto.getUserId(), currentUserId);
|
|
|
|
|
|
- // 5. 重新查询用户信息(因为使用 updateWrapper 后,user 对象不会自动更新)
|
|
|
LifeUser updatedUser = lifeUserMapper.selectById(dto.getUserId().intValue());
|
|
|
if (updatedUser == null) {
|
|
|
log.error("更新后查询用户信息失败, userId={}", dto.getUserId());
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 6. 返回更新后的用户信息
|
|
|
return buildDiningUserVo(updatedUser);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public DiningUserVo getUserInfo(Long userId) {
|
|
|
- // 1. 查询用户
|
|
|
LifeUser user = lifeUserMapper.selectById(userId.intValue());
|
|
|
if (user == null) {
|
|
|
log.warn("获取用户信息失败:用户不存在, userId={}", userId);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 2. 检查用户状态
|
|
|
if (user.getIsBanned() != null && user.getIsBanned() == 1) {
|
|
|
log.warn("用户已被封禁: userId={}", userId);
|
|
|
return null;
|
|
|
@@ -477,7 +126,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 3. 返回用户信息
|
|
|
return buildDiningUserVo(user);
|
|
|
}
|
|
|
|
|
|
@@ -487,7 +135,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
String newPhone = dto.getNewPhone().trim();
|
|
|
String codeStr = dto.getCode().trim();
|
|
|
|
|
|
- // 1. 校验验证码(Feign 调 store ali/checkSmsCode,appType=0 用户端,businessType=3 修改手机号)
|
|
|
int codeInt;
|
|
|
try {
|
|
|
codeInt = Integer.parseInt(codeStr);
|
|
|
@@ -501,7 +148,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 2. 查询用户
|
|
|
LifeUser user = lifeUserMapper.selectById(dto.getUserId().intValue());
|
|
|
if (user == null) {
|
|
|
log.warn("更换手机号失败:用户不存在, userId={}", dto.getUserId());
|
|
|
@@ -516,13 +162,11 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 3. 新手机号与当前相同则无需更新
|
|
|
if (newPhone.equals(user.getUserPhone())) {
|
|
|
log.info("新手机号与当前相同,无需更换, userId={}", dto.getUserId());
|
|
|
return buildDiningUserVo(user);
|
|
|
}
|
|
|
|
|
|
- // 4. 新手机号是否已被其他用户使用
|
|
|
LambdaQueryWrapper<LifeUser> q = new LambdaQueryWrapper<>();
|
|
|
q.eq(LifeUser::getUserPhone, newPhone);
|
|
|
LifeUser existing = lifeUserMapper.selectOne(q);
|
|
|
@@ -531,7 +175,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 5. 更新 user_phone
|
|
|
String oldPhone = user.getUserPhone();
|
|
|
user.setUserPhone(newPhone);
|
|
|
user.setUpdatedTime(new Date());
|
|
|
@@ -541,10 +184,11 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 6. 只清除小程序旧手机号对应的 token,APP 的 token 保持不变
|
|
|
- // 因为小程序更换手机号只影响小程序平台,APP 可以继续使用旧手机号登录
|
|
|
- baseRedisService.delete(REDIS_KEY_USER_PHONE_PREFIX + oldPhone);
|
|
|
- log.info("更换手机号成功, userId={}, oldPhone={}, newPhone={}(仅清除小程序 token,APP token 保持不变)", dto.getUserId(), oldPhone, newPhone);
|
|
|
+ if (StringUtils.isNotBlank(oldPhone)) {
|
|
|
+ baseRedisService.delete(REDIS_USER_TOKEN_PREFIX + oldPhone);
|
|
|
+ }
|
|
|
+ log.info("更换手机号成功, userId={}, oldPhone={}, newPhone={}(已清除 C 端 Redis token,需重新登录)",
|
|
|
+ dto.getUserId(), oldPhone, newPhone);
|
|
|
|
|
|
return buildDiningUserVo(user);
|
|
|
}
|
|
|
@@ -554,28 +198,24 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
TokenVerifyVo verifyVo = new TokenVerifyVo();
|
|
|
verifyVo.setValid(false);
|
|
|
|
|
|
- // 1. 检查 token 是否为空
|
|
|
if (StringUtils.isBlank(token)) {
|
|
|
verifyVo.setReason("Token 不能为空");
|
|
|
return verifyVo;
|
|
|
}
|
|
|
|
|
|
- // 去除 "Bearer " 前缀(不区分大小写)
|
|
|
token = token.trim();
|
|
|
if (token.length() > 7 && token.substring(0, 7).equalsIgnoreCase("Bearer ")) {
|
|
|
token = token.substring(7).trim();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (StringUtils.isBlank(token)) {
|
|
|
verifyVo.setReason("Token 去除前缀后为空");
|
|
|
return verifyVo;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- // 2. 解析 token,验证格式和签名
|
|
|
Claims claims = JwtUtil.parseJWT(token);
|
|
|
-
|
|
|
- // 3. 检查 token 是否过期
|
|
|
+
|
|
|
Date expiration = claims.getExpiration();
|
|
|
if (expiration != null && expiration.before(new Date())) {
|
|
|
verifyVo.setReason("Token 已过期");
|
|
|
@@ -583,38 +223,24 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
return verifyVo;
|
|
|
}
|
|
|
|
|
|
- // 4. 从 token 中提取用户信息
|
|
|
String sub = claims.get("sub").toString();
|
|
|
JSONObject tokenInfo = JSONObject.parseObject(sub);
|
|
|
- String openid = tokenInfo.getString("openid");
|
|
|
String phone = tokenInfo.getString("phone");
|
|
|
String userIdStr = tokenInfo.getString("userId");
|
|
|
String userName = tokenInfo.getString("userName");
|
|
|
+ String openid = tokenInfo.getString("openid");
|
|
|
|
|
|
- // 5. 验证 Redis 中是否存在该 token(使用小程序专用的 key 前缀)
|
|
|
- boolean tokenExists = false;
|
|
|
- if (StringUtils.isNotBlank(openid)) {
|
|
|
- // 优先使用 openid 查找
|
|
|
- String redisToken = baseRedisService.getString(REDIS_KEY_TOKEN_PREFIX + openid);
|
|
|
- if (token.equals(redisToken)) {
|
|
|
- tokenExists = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 兼容旧版本:通过手机号查找
|
|
|
- if (!tokenExists && StringUtils.isNotBlank(phone)) {
|
|
|
- String redisToken = baseRedisService.getString(REDIS_KEY_USER_PHONE_PREFIX + phone);
|
|
|
- if (token.equals(redisToken)) {
|
|
|
- tokenExists = true;
|
|
|
- }
|
|
|
+ if (StringUtils.isBlank(phone)) {
|
|
|
+ verifyVo.setReason("Token 中无手机号,请使用 C 端统一登录获取 token");
|
|
|
+ return verifyVo;
|
|
|
}
|
|
|
|
|
|
- if (!tokenExists) {
|
|
|
+ String redisToken = baseRedisService.getString(REDIS_USER_TOKEN_PREFIX + phone);
|
|
|
+ if (!token.equals(redisToken)) {
|
|
|
verifyVo.setReason("Token 不存在或已失效(可能已退出登录)");
|
|
|
return verifyVo;
|
|
|
}
|
|
|
|
|
|
- // 6. 验证用户状态(如果提供了 userId)
|
|
|
if (StringUtils.isNotBlank(userIdStr)) {
|
|
|
try {
|
|
|
Integer userId = Integer.parseInt(userIdStr);
|
|
|
@@ -636,7 +262,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 7. Token 验证通过,返回用户信息
|
|
|
verifyVo.setValid(true);
|
|
|
if (StringUtils.isNotBlank(userIdStr)) {
|
|
|
try {
|
|
|
@@ -650,7 +275,7 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
verifyVo.setOpenid(openid);
|
|
|
verifyVo.setExpirationTime(expiration);
|
|
|
|
|
|
- log.info("Token 验证成功: userId={}, openid={}, phone={}", userIdStr, openid, phone);
|
|
|
+ log.info("Token 验证成功: userId={}, phone={}", userIdStr, phone);
|
|
|
return verifyVo;
|
|
|
|
|
|
} catch (ExpiredJwtException e) {
|
|
|
@@ -672,9 +297,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 构建 DiningUserVo
|
|
|
- */
|
|
|
private DiningUserVo buildDiningUserVo(LifeUser user) {
|
|
|
DiningUserVo diningUserVo = new DiningUserVo();
|
|
|
diningUserVo.setId(user.getId().longValue());
|
|
|
@@ -683,7 +305,6 @@ public class DiningUserServiceImpl implements DiningUserService {
|
|
|
diningUserVo.setAvatarUrl(user.getUserImage());
|
|
|
diningUserVo.setStatus(0);
|
|
|
diningUserVo.setCreatedTime(user.getCreatedTime());
|
|
|
- // 补充更多字段
|
|
|
diningUserVo.setGender(user.getUserSex());
|
|
|
diningUserVo.setBirthday(user.getUserBirthday());
|
|
|
diningUserVo.setRealName(user.getRealName());
|