✨ 新增小程序账号查找或创建与按openid查询接口

This commit is contained in:
2026-10-23 15:48:31 +08:00
parent 0c72a72f13
commit 3475ec1f87
5 changed files with 179 additions and 0 deletions
@@ -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<MpUser> userByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account);
/**
* 按租户 + openid 查询
*/
@GetMapping(USER_BY_OPENID)
R<MpUser> userByOpenid(@RequestParam("tenantId") String tenantId, @RequestParam("openid") String openid);
/**
* 查找或创建小程序用户(登录路径专用,已存在时不抛异常、不改身份)
*/
@PostMapping(ENSURE_USER)
R<MpUser> ensureUser(@RequestBody MpUserEnsureDTO param);
/**
* 按主键查询登录信息
*/
@@ -0,0 +1,55 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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;
}
@@ -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<MpUser> userByOpenid(String tenantId, String openid) {
return R.data(mpUserService.getByOpenid(tenantId, openid));
}
@Override
@PostMapping(ENSURE_USER)
public R<MpUser> ensureUser(@RequestBody MpUserEnsureDTO param) {
return R.data(mpUserService.ensureUser(param));
}
@Override
@GetMapping(USER_INFO_BY_ID)
public R<MpUserInfo> userInfoById(Long userId) {
@@ -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> {
*/
MpUser getByAccount(String tenantId, String account);
/**
* 按租户 + 微信小程序 openid 查询
*/
MpUser getByOpenid(String tenantId, String openid);
/**
* 查找或创建小程序用户(登录路径专用)
* <p>
* 与 {@link #submit} 的提交式语义相反:手机号已存在时直接返回已有账号,且不改其身份。
*/
MpUser ensureUser(MpUserEnsureDTO param);
/**
* 按主键组装登录信息
*/
@@ -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<MpUserMapper, MpUser> 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<MpUserMapper, MpUser> imp
.last("LIMIT 1"));
}
@Override
public MpUser getByOpenid(String tenantId, String openid) {
if (Func.isBlank(tenantId) || Func.isBlank(openid)) {
return null;
}
return getOne(Wrappers.<MpUser>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;
}
/**
* 登录路径的租户解析:入参 → 当前登录租户 → 默认租户
* <p>
* 写库前必须得到非空值:唯一键 (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)) {