diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IMpUserClient.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IMpUserClient.java index 0a0a6e4b..b381750b 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IMpUserClient.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IMpUserClient.java @@ -24,6 +24,7 @@ package org.springblade.system.feign; import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.dto.MpUserEnsureDTO; import org.springblade.system.pojo.entity.MpUser; import org.springblade.system.pojo.entity.MpUserInfo; import org.springframework.cloud.openfeign.FeignClient; @@ -46,6 +47,7 @@ public interface IMpUserClient { String USER_BY_ID = API_PREFIX + "/user-by-id"; String USER_BY_PHONE = API_PREFIX + "/user-by-phone"; String USER_BY_ACCOUNT = API_PREFIX + "/user-by-account"; + String USER_BY_OPENID = API_PREFIX + "/user-by-openid"; String USER_INFO_BY_ID = API_PREFIX + "/info-by-id"; String USER_INFO_BY_PHONE = API_PREFIX + "/info-by-phone"; String USER_INFO_BY_ACCOUNT = API_PREFIX + "/info-by-account"; @@ -53,6 +55,7 @@ public interface IMpUserClient { String UPDATE_USER = API_PREFIX + "/update-user"; String BIND_OPENID = API_PREFIX + "/bind-openid"; String SET_PASSWORD = API_PREFIX + "/set-password"; + String ENSURE_USER = API_PREFIX + "/ensure-user"; /** * 按主键查询 @@ -72,6 +75,18 @@ public interface IMpUserClient { @GetMapping(USER_BY_ACCOUNT) R userByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account); + /** + * 按租户 + openid 查询 + */ + @GetMapping(USER_BY_OPENID) + R userByOpenid(@RequestParam("tenantId") String tenantId, @RequestParam("openid") String openid); + + /** + * 查找或创建小程序用户(登录路径专用,已存在时不抛异常、不改身份) + */ + @PostMapping(ENSURE_USER) + R ensureUser(@RequestBody MpUserEnsureDTO param); + /** * 按主键查询登录信息 */ diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/MpUserEnsureDTO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/MpUserEnsureDTO.java new file mode 100644 index 00000000..adb27378 --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/MpUserEnsureDTO.java @@ -0,0 +1,55 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 小程序用户「查找或创建」参数 + * + * @author Chill + */ +@Data +@Schema(description = "小程序用户查找或创建参数") +public class MpUserEnsureDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "租户ID,为空时依次取当前登录租户与默认租户") + private String tenantId; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "微信小程序openid,非空时随首次建号一并写入") + private String openid; + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/MpUserClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/MpUserClient.java index 4d8eabfe..fae7ebfa 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/MpUserClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/MpUserClient.java @@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Hidden; import lombok.AllArgsConstructor; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.dto.MpUserEnsureDTO; import org.springblade.system.pojo.entity.MpUser; import org.springblade.system.pojo.entity.MpUserInfo; import org.springblade.system.service.IMpUserService; @@ -65,6 +66,18 @@ public class MpUserClient implements IMpUserClient { return R.data(mpUserService.getByAccount(tenantId, account)); } + @Override + @GetMapping(USER_BY_OPENID) + public R userByOpenid(String tenantId, String openid) { + return R.data(mpUserService.getByOpenid(tenantId, openid)); + } + + @Override + @PostMapping(ENSURE_USER) + public R ensureUser(@RequestBody MpUserEnsureDTO param) { + return R.data(mpUserService.ensureUser(param)); + } + @Override @GetMapping(USER_INFO_BY_ID) public R userInfoById(Long userId) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java index c0294ced..6b20f61d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java @@ -24,6 +24,7 @@ package org.springblade.system.service; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springblade.core.mp.base.BaseService; +import org.springblade.system.pojo.dto.MpUserEnsureDTO; import org.springblade.system.pojo.entity.MpUser; import org.springblade.system.pojo.entity.MpUserInfo; @@ -47,6 +48,18 @@ public interface IMpUserService extends BaseService { */ MpUser getByAccount(String tenantId, String account); + /** + * 按租户 + 微信小程序 openid 查询 + */ + MpUser getByOpenid(String tenantId, String openid); + + /** + * 查找或创建小程序用户(登录路径专用) + *

+ * 与 {@link #submit} 的提交式语义相反:手机号已存在时直接返回已有账号,且不改其身份。 + */ + MpUser ensureUser(MpUserEnsureDTO param); + /** * 按主键组装登录信息 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java index 93f744ba..a0a12bab 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java @@ -25,6 +25,7 @@ package org.springblade.system.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.common.utils.PasswordRuleUtil; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; @@ -36,6 +37,7 @@ import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; import org.springblade.system.cache.MpUserCache; import org.springblade.system.mapper.MpUserMapper; +import org.springblade.system.pojo.dto.MpUserEnsureDTO; import org.springblade.system.pojo.entity.MpUser; import org.springblade.system.pojo.entity.MpUserInfo; import org.springblade.system.pojo.enums.MpUserIdentity; @@ -53,10 +55,16 @@ import java.util.Objects; * * @author Chill */ +@Slf4j @Service @RequiredArgsConstructor public class MpUserServiceImpl extends BaseServiceImpl implements IMpUserService { + /** + * 兜底租户:与 blade_mp_user.tenant_id 的建表默认值一致 + */ + private static final String DEFAULT_TENANT_ID = "000000"; + @Override public MpUser getByPhone(String tenantId, String phone) { if (Func.isBlank(tenantId) || Func.isBlank(phone)) { @@ -81,6 +89,81 @@ public class MpUserServiceImpl extends BaseServiceImpl imp .last("LIMIT 1")); } + @Override + public MpUser getByOpenid(String tenantId, String openid) { + if (Func.isBlank(tenantId) || Func.isBlank(openid)) { + return null; + } + return getOne(Wrappers.lambdaQuery() + .eq(MpUser::getTenantId, tenantId) + .eq(MpUser::getOpenid, openid) + .orderByDesc(MpUser::getCreateTime) + .last("LIMIT 1")); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public MpUser ensureUser(MpUserEnsureDTO param) { + if (param == null) { + throw new ServiceException("小程序用户参数不能为空"); + } + String tenantId = resolveEnsureTenantId(param.getTenantId()); + String phone = trimToNull(param.getPhone()); + if (Func.isBlank(phone)) { + throw new ServiceException("手机号不能为空"); + } + // 先查后插:命中即返回,不新建也不改身份。租户为空时唯一键对 NULL 不去重, + // 故 resolveEnsureTenantId 已在写库前把租户兜到非空。 + MpUser existed = getByPhone(tenantId, phone); + if (existed != null && Func.isNotEmpty(existed.getId())) { + return existed; + } + MpUser user = new MpUser(); + user.setTenantId(tenantId); + user.setPhone(phone); + user.setAccount(phone); + user.setIdentities(MpUserIdentity.DRIVER.getCode()); + user.setOpenid(trimToNull(param.getOpenid())); + user.setStatus(1); + prepare(user); + validate(user); + // 登录路径不设密码:密码留空,避免调用方误传弱口令 + user.setPassword(null); + try { + save(user); + } catch (Exception exception) { + // 并发建号撞 (tenant_id, phone) 唯一键:重读一次返回,登录方不该因重试失败 + MpUser concurrent = getByPhone(tenantId, phone); + if (concurrent != null && Func.isNotEmpty(concurrent.getId())) { + return concurrent; + } + log.warn("小程序用户建号失败,租户={} 手机号={}", tenantId, phone, exception); + throw new ServiceException("创建小程序用户失败"); + } + if (Func.isEmpty(user.getId())) { + throw new ServiceException("创建小程序用户失败"); + } + MpUserCache.clear(); + return user; + } + + /** + * 登录路径的租户解析:入参 → 当前登录租户 → 默认租户 + *

+ * 写库前必须得到非空值:唯一键 (tenant_id, phone) 对 NULL 不去重,租户为 NULL + * 会为同一手机号造出重复账号。 + */ + private String resolveEnsureTenantId(String tenantId) { + String resolved = trimToNull(tenantId); + if (resolved == null) { + resolved = trimToNull(AuthUtil.getTenantId()); + } + if (resolved == null) { + resolved = DEFAULT_TENANT_ID; + } + return resolved; + } + @Override public MpUserInfo userInfo(Long userId) { if (Func.isEmpty(userId)) {