bug修改

This commit is contained in:
周鹏 committed 2026-08-15 19:17:52 +08:00
1 parent 855327ebab
commit 1c2006d05c
12 files changed
+158 -2

No files matched your search

@@ -387,6 +387,12 @@ public class GlobalExceptionHandler {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
}
// 7.1 会员互动消息
if (message.contains("member_interaction")) {
log.error("[会员互动消息表 member_interaction 未导入][请执行 friend_back/sql/mysql/member/member_interaction.sql]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"会员互动消息表未导入,请执行 sql/mysql/member/member_interaction.sql");
}
// 8. AI 大模型
if (message.contains("ai_")) {
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
@@ -71,6 +71,14 @@ public class MemberUserController {
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新会员用户状态(禁用后踢出 App 登录,且无法再登录)")
@PreAuthorize("@ss.hasPermission('member:user:update')")
public CommonResult<Boolean> updateUserStatus(@Valid @RequestBody MemberUserUpdateStatusReqVO reqVO) {
memberUserService.updateUserStatus(reqVO.getId(), reqVO.getStatus());
return success(true);
}
@GetMapping("/fill-missing-user-codes")
@Operation(summary = "为历史会员补齐用户 Code")
// @PreAuthorize("@ss.hasPermission('member:user:update')")
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.member.controller.admin.user.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 会员用户更新状态 Request VO")
@Data
public class MemberUserUpdateStatusReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "用户编号不能为空")
private Long id;
@Schema(description = "状态,见 CommonStatusEnum 枚举。0 开启,1 禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
private Integer status;
}
@@ -173,6 +173,11 @@ public class MemberAuthServiceImpl implements MemberAuthService {
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile,
LoginLogTypeEnum logType, String openid) {
// 统一拦截:已禁用账号不可登录 / 注册后自动登录
if (CommonStatusEnum.isDisable(user.getStatus())) {
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
}
// 插入登陆日志
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
// 创建 Token 令牌
@@ -339,6 +344,13 @@ public class MemberAuthServiceImpl implements MemberAuthService {
public AppAuthLoginRespVO refreshToken(String refreshToken) {
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
MemberUserDO user = userService.getUser(accessTokenDO.getUserId());
if (user == null || CommonStatusEnum.isDisable(user.getStatus())) {
// 禁用后禁止续期,并清掉该用户全部 token
oauth2TokenApi.removeAccessTokenByUserId(accessTokenDO.getUserId(), getUserType().getValue());
throw exception(AUTH_LOGIN_USER_DISABLED);
}
MemberUserUpdateReqVO update = new MemberUserUpdateReqVO();
update.setId(accessTokenDO.getUserId());
update.setAppRefreshDate(LocalDateTime.now());
@@ -371,9 +383,13 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Override
public AppAuthLoginRespVO.ChatToken reissueChatToken(Long userId) {
if (userService.getUser(userId) == null) {
MemberUserDO user = userService.getUser(userId);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
if (CommonStatusEnum.isDisable(user.getStatus())) {
throw exception(AUTH_LOGIN_USER_DISABLED);
}
if (!userService.syncChatUser(userId)) {
throw exception(CHAT_TOKEN_CREATE_FAILED);
}
@@ -147,6 +147,14 @@ public interface MemberUserService {
*/
void updateUser(@Valid MemberUserUpdateReqVO updateReqVO);
/**
* 【管理员】更新会员用户状态(禁用时踢出全部登录态)
*
* @param id 用户编号
* @param status 状态,见 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
void updateUserStatus(Long id, Integer status);
/**
* 【管理员】获得会员用户分页
*
@@ -30,6 +30,7 @@ import cn.iocoder.yudao.module.member.enums.tag.MemberRecentTagEnum;
import cn.iocoder.yudao.module.member.mq.producer.user.MemberUserProducer;
import cn.iocoder.yudao.module.member.service.user.BO.MemberExtBO;
import cn.iocoder.yudao.module.member.service.user.BO.MemberWechatBO;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@@ -100,6 +101,8 @@ public class MemberUserServiceImpl implements MemberUserService {
private ChatApi chatApi;
@Resource
private TradeBrokerageUserApi tradeBrokerageUserApi;
@Resource
private OAuth2TokenApi oauth2TokenApi;
@Override
public MemberUserDO getUserByMobile(String mobile) {
@@ -416,7 +419,7 @@ public class MemberUserServiceImpl implements MemberUserService {
@Transactional(rollbackFor = Exception.class)
public void updateUser(MemberUserUpdateReqVO updateReqVO) {
// 校验存在
validateUserExists(updateReqVO.getId());
MemberUserDO dbUser = validateUserExists(updateReqVO.getId());
// 校验手机唯一
validateMobileUnique(updateReqVO.getId(), updateReqVO.getMobile());
@@ -446,6 +449,44 @@ public class MemberUserServiceImpl implements MemberUserService {
// 更新用户新人tag
memberRecentTagService.createOrUpdateNewPersonTag(updateReqVO.getId());
memberUserMapper.updateById(updateObj);
// 变为禁用:踢出全部 App 登录态
Integer newStatus = updateObj.getStatus();
if (newStatus == null && updateReqVO.getStatus() != null) {
newStatus = updateReqVO.getStatus().intValue();
}
if (CommonStatusEnum.isDisable(newStatus) && !CommonStatusEnum.isDisable(dbUser.getStatus())) {
kickMemberLogin(updateReqVO.getId());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateUserStatus(Long id, Integer status) {
MemberUserDO dbUser = validateUserExists(id);
if (ObjectUtil.equal(dbUser.getStatus(), status)) {
// 已禁用时再点一次,仍强制踢一次,避免漏踢
if (CommonStatusEnum.isDisable(status)) {
kickMemberLogin(id);
}
return;
}
memberUserMapper.updateById(new MemberUserDO().setId(id).setStatus(status));
if (CommonStatusEnum.isDisable(status)) {
kickMemberLogin(id);
}
}
/**
* 清理会员全部 OAuth2 access + refresh,App 下次请求会 401 并退出登录。
*/
private void kickMemberLogin(Long userId) {
try {
oauth2TokenApi.removeAccessTokenByUserId(userId, UserTypeEnum.MEMBER.getValue());
} catch (Exception e) {
log.warn("踢出会员登录态失败, userId={}", userId, e);
throw e;
}
}
@VisibleForTesting
@@ -37,6 +37,15 @@ public interface OAuth2TokenApi {
*/
OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
/**
* 移除指定用户的全部访问令牌与刷新令牌(踢下线)
*
* @param userId 用户编号
* @param userType 用户类型
* @return 删除的访问令牌数量
*/
int removeAccessTokenByUserId(Long userId, Integer userType);
/**
* 刷新访问令牌
*
@@ -40,6 +40,11 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class);
}
@Override
public int removeAccessTokenByUserId(Long userId, Integer userType) {
return oauth2TokenService.removeAccessTokenByUserId(userId, userType);
}
@Override
public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
@@ -23,6 +23,12 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
}
default List<OAuth2AccessTokenDO> selectListByUserIdAndUserType(Long userId, Integer userType) {
return selectList(new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
.eq(OAuth2AccessTokenDO::getUserId, userId)
.eq(OAuth2AccessTokenDO::getUserType, userType));
}
default PageResult<OAuth2AccessTokenDO> selectPage(OAuth2AccessTokenPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
.eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())
@@ -14,6 +14,12 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
.eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
}
default int deleteByUserIdAndUserType(Long userId, Integer userType) {
return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
.eq(OAuth2RefreshTokenDO::getUserId, userId)
.eq(OAuth2RefreshTokenDO::getUserType, userType));
}
@TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
@@ -69,6 +69,15 @@ public interface OAuth2TokenService {
*/
OAuth2AccessTokenDO removeAccessToken(String accessToken);
/**
* 移除指定用户的全部访问令牌与刷新令牌(踢下线)
*
* @param userId 用户编号
* @param userType 用户类型
* @return 删除的访问令牌数量
*/
int removeAccessTokenByUserId(Long userId, Integer userType);
/**
* 获得访问令牌分页
*
@@ -153,6 +153,25 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return accessTokenDO;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int removeAccessTokenByUserId(Long userId, Integer userType) {
if (userId == null || userType == null) {
return 0;
}
List<OAuth2AccessTokenDO> accessTokenDOs =
oauth2AccessTokenMapper.selectListByUserIdAndUserType(userId, userType);
int removed = 0;
if (CollUtil.isNotEmpty(accessTokenDOs)) {
oauth2AccessTokenMapper.deleteByIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId));
oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken));
removed = accessTokenDOs.size();
}
// 清理可能残留的 refresh token(无对应 access 的场景)
oauth2RefreshTokenMapper.deleteByUserIdAndUserType(userId, userType);
return removed;
}
@Override
public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {
return oauth2AccessTokenMapper.selectPage(reqVO);