From c0fa58d2bcf51ec0caecf3d82a5d7229a2cfd2e8 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Thu, 30 Jul 2026 11:42:04 +0800 Subject: [PATCH] 11 --- sql/mysql/member/member-2024-01-18.sql | 4 +- .../member/member_user_add_user_code.sql | 6 ++ .../api/user/dto/MemberUserRespDTO.java | 4 ++ .../module/member/client/service/ChatApi.java | 5 ++ .../member/client/service/ChatApiImpl.java | 32 ++++++++++ .../member/client/tool/ChatApiTool.java | 1 + .../chat/user/ChatUserOnLineStatusReqVo.java | 15 +++++ .../chat/user/ChatUserOnLineStatusRespVo.java | 16 +++++ .../admin/recent/vo/RecentPageResVo.java | 3 + .../admin/user/MemberUserController.java | 9 +++ .../admin/user/vo/MemberUserRespVO.java | 3 + .../app/user/vo/AppMemberUserInfoRespVO.java | 4 ++ .../dal/dataobject/user/MemberUserDO.java | 5 ++ .../dal/mysql/user/MemberUserMapper.java | 18 ++++++ .../service/recent/RecentServiceImpl.java | 26 +++++++++ .../service/user/MemberUserService.java | 7 +++ .../service/user/MemberUserServiceImpl.java | 58 +++++++++++++++++++ .../src/test/resources/sql/create_tables.sql | 4 +- .../src/main/resources/application-dev.yaml | 1 + 19 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 sql/mysql/member/member_user_add_user_code.sql create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusReqVo.java create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusRespVo.java diff --git a/sql/mysql/member/member-2024-01-18.sql b/sql/mysql/member/member-2024-01-18.sql index 03d66bb..3d1f14e 100644 --- a/sql/mysql/member/member-2024-01-18.sql +++ b/sql/mysql/member/member-2024-01-18.sql @@ -300,6 +300,7 @@ COMMIT; DROP TABLE IF EXISTS `member_user`; CREATE TABLE `member_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户 Code,大小写敏感', `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码', `status` tinyint NOT NULL COMMENT '状态', @@ -325,7 +326,8 @@ CREATE TABLE `member_user` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_user_code` (`user_code`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 286 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员用户'; -- ---------------------------- diff --git a/sql/mysql/member/member_user_add_user_code.sql b/sql/mysql/member/member_user_add_user_code.sql new file mode 100644 index 0000000..7f50c36 --- /dev/null +++ b/sql/mysql/member/member_user_add_user_code.sql @@ -0,0 +1,6 @@ +-- 先执行本脚本增加字段和大小写敏感的唯一索引。 +-- 再调用 PUT /admin-api/member/user/fill-missing-user-codes,为所有租户的历史用户补齐 Code。 +ALTER TABLE `member_user` + ADD COLUMN `user_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL + COMMENT '用户 Code,大小写敏感' AFTER `id`, + ADD UNIQUE KEY `uk_user_code` (`user_code`) USING BTREE; diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java index 50548f9..9334aa4 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java @@ -17,6 +17,10 @@ public class MemberUserRespDTO { * 用户ID */ private Long id; + /** + * 用户 Code + */ + private String userCode; /** * 用户昵称 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java index f6c3563..bffa8c1 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java @@ -19,6 +19,11 @@ public interface ChatApi { */ ChatLoginRespVO login(@Valid ChatLoginReqVO dto); + /** + * 查询用户在线状态 + */ + List getUserOnlineStatusById(@Valid ChatUserOnLineStatusReqVo dto); + /** * 创建或者更新聊天用户 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java index b10feb8..32aadd8 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java @@ -62,6 +62,38 @@ public class ChatApiImpl implements ChatApi { } } + /** + * 查询用户在线状态 + */ + @Override + public List getUserOnlineStatusById(ChatUserOnLineStatusReqVo dto) { + ChatApiTool.API api = setApiConfig(ChatApiTool.GET_USER_ONLINE_STATUS_BY_ID); + try { + String body = JSONUtil.toJsonStr(dto); + String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body(); + log.debug("查询用户在线状态,res:{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("查询用户在线状态,res为空"); + return null; + } + ChatBaseRespVO baseRes = JSONUtil.toBean(res, ChatBaseRespVO.class); + if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) { + log.error("查询用户在线状态,返回错误"); + return null; + } + List vo = JSONUtil.toList(baseRes.getData(), ChatUserOnLineStatusRespVo.class); + if (ObjectUtil.isEmpty(vo)) { + log.error("查询用户在线状态,返回json解析错误"); + return null; + } + return vo; + } catch (Exception e) { + e.printStackTrace(); + log.error("查询用户在线状态,意外的错误", e); + return null; + } + } + /** * 创建或者更新聊天用户 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java index 208b6eb..d4f8731 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java @@ -21,6 +21,7 @@ public class ChatApiTool { public static final API USER_LOGIN = API.builder().summary("用户登录").url("/third/user/login").method(HttpMethod.POST).build(); public static final API USER_GET = API.builder().summary("根据userId查询用户详情").url("/third/user/getUserByUserId").method(HttpMethod.POST).build(); public static final API USER_SAVE_OR_UPDATE = API.builder().summary("创建或者更新用户").url("/third/user/createOrUpdate").method(HttpMethod.POST).build(); + public static final API GET_USER_ONLINE_STATUS_BY_ID = API.builder().summary("查询用户在线状态").url("/third/user/getUserOnlineStatusById").method(HttpMethod.POST).build(); // 朋友圈 public static final API FRIEND_ADD = API.builder().summary("添加好友").url("/third/friend/add").method(HttpMethod.POST).build(); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusReqVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusReqVo.java new file mode 100644 index 0000000..86066cd --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusReqVo.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.member.client.vo.chat.user; + +import lombok.Data; + +import java.util.List; + +@Data +public class ChatUserOnLineStatusReqVo { + + /** + * 聊天用户ids + */ + private List userIdList; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusRespVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusRespVo.java new file mode 100644 index 0000000..23da407 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatUserOnLineStatusRespVo.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.member.client.vo.chat.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "chat用户发送消息resp") +public class ChatUserOnLineStatusRespVo { + + @Schema(description = "用户id") + private Long id; + + @Schema(description = "是否在线") + private Boolean online; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/recent/vo/RecentPageResVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/recent/vo/RecentPageResVo.java index 95d0f44..88f8d37 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/recent/vo/RecentPageResVo.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/recent/vo/RecentPageResVo.java @@ -43,6 +43,9 @@ public class RecentPageResVo extends RecentDO { @Schema(description = "距离(km)") private Double distance; + @Schema(description = "是否在线") + private Boolean onLine; + @Data public static class MemberPhoto { diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java index ed5fa73..6cdfff6 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java @@ -28,7 +28,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.annotation.security.PermitAll; import javax.validation.Valid; +import javax.validation.constraints.AssertFalse; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -69,6 +71,13 @@ public class MemberUserController { return success(true); } + @GetMapping("/fill-missing-user-codes") + @Operation(summary = "为历史会员补齐用户 Code") + @PreAuthorize("@ss.hasPermission('member:user:update')") + public CommonResult fillMissingUserCodes() { + return success(memberUserService.fillMissingUserCodes()); + } + @PutMapping("/update-level") @Operation(summary = "更新会员用户等级") @PreAuthorize("@ss.hasPermission('member:user:update-level')") diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java index 74423e1..163bbaa 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java @@ -19,6 +19,9 @@ public class MemberUserRespVO extends MemberUserBaseVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788") private Long id; + @Schema(description = "用户 Code,6 位数字和大小写字母组合", example = "aB3xY9") + private String userCode; + @Schema(description = "注册 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") private String registerIp; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java index 1e515c5..a55f6bc 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java @@ -21,6 +21,10 @@ public class AppMemberUserInfoRespVO { @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Long id; + @Schema(description = "用户 Code,6 位数字和大小写字母组合", requiredMode = Schema.RequiredMode.REQUIRED, + example = "aB3xY9") + private String userCode; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") private String nickname; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java index 369f3ca..396af44 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java @@ -43,6 +43,11 @@ public class MemberUserDO extends TenantBaseDO { @TableId private Long id; + /** + * 用户 Code,6 位数字和大小写字母组合,全局唯一且大小写敏感 + */ + private String userCode; + /** * 手机 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java index 3f87102..5722d48 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java @@ -26,6 +26,24 @@ public interface MemberUserMapper extends BaseMapperX { return selectOne(MemberUserDO::getMobile, mobile); } + default List selectIdListByEmptyUserCode(int limit) { + return selectList(new LambdaQueryWrapperX() + .select(MemberUserDO::getId) + .and(wrapper -> wrapper.isNull(MemberUserDO::getUserCode) + .or().eq(MemberUserDO::getUserCode, "")) + .orderByAsc(MemberUserDO::getId) + .last("LIMIT " + limit)) + .stream().map(MemberUserDO::getId).collect(Collectors.toList()); + } + + default int updateUserCodeIfEmpty(Long id, String userCode) { + return update(null, new LambdaUpdateWrapper() + .set(MemberUserDO::getUserCode, userCode) + .eq(MemberUserDO::getId, id) + .and(wrapper -> wrapper.isNull(MemberUserDO::getUserCode) + .or().eq(MemberUserDO::getUserCode, ""))); + } + default List selectListByNicknameLike(String nickname) { return selectList(new LambdaQueryWrapperX() .likeIfPresent(MemberUserDO::getNickname, nickname)); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java index c366c65..78d5717 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java @@ -10,6 +10,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.member.api.thirdauth.ThirdAuthVo; import cn.iocoder.yudao.module.member.client.service.ChatApi; import cn.iocoder.yudao.module.member.client.service.TxMapApi; +import cn.iocoder.yudao.module.member.client.vo.chat.user.ChatUserOnLineStatusReqVo; +import cn.iocoder.yudao.module.member.client.vo.chat.user.ChatUserOnLineStatusRespVo; import cn.iocoder.yudao.module.member.client.vo.txmap.geocoder.TxMapGeocoderReqVO; import cn.iocoder.yudao.module.member.client.vo.txmap.geocoder.TxMapGeocoderRespVO; import cn.iocoder.yudao.module.member.controller.admin.recent.vo.*; @@ -490,6 +492,8 @@ public class RecentServiceImpl implements RecentService { List resultList = BeanUtils.toBean(recentPageList.getRecords(), RecentPageResVo.class); // 为RecentPageResVo设置Member信息 convertRecentPageResVoSetupMember(resultList, isVip); + // 远程查询用户是否在线 + convertRecentPageResVoSetupIsOnline(resultList); // 如果是`新人``女生`模糊`头像`和`用户名` List dimList = Arrays.asList(MemberRecentSceneEnum.SQUARE_NEW_PERSON, MemberRecentSceneEnum.SQUARE_FEMALE); // 查询用户tag @@ -785,6 +789,28 @@ public class RecentServiceImpl implements RecentService { } } + /** + * 为RecentPageResVo设置isOnline信息 + */ + private void convertRecentPageResVoSetupIsOnline(List list) { + if (ObjectUtil.isEmpty(list)) return; + List memberIds = list.stream().map(RecentPageResVo::getMemberId).collect(Collectors.toList()); + + ChatUserOnLineStatusReqVo dto = new ChatUserOnLineStatusReqVo(); + dto.setUserIdList(memberIds); + + List userOnlineList = chatApi.getUserOnlineStatusById(dto); + if (ObjectUtil.isEmpty(userOnlineList)) return; + Map userOnlineMap = IterUtil.toMap(userOnlineList, ChatUserOnLineStatusRespVo::getId); + + for (RecentPageResVo entity : list) { + ChatUserOnLineStatusRespVo userOnLineStatus = userOnlineMap.get(entity.getMemberId()); + if(ObjectUtil.isNotEmpty(userOnLineStatus)){ + entity.setOnLine(userOnLineStatus.getOnline()); + } + } + } + /** * 为RecentPageResVo设置OperationTotal信息 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java index b5b71c0..cc64a79 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java @@ -60,6 +60,13 @@ public interface MemberUserService { */ MemberUserDO createUser(String nickname, String avtar, String registerIp, Integer terminal); + /** + * 为所有租户中尚无用户 Code 的历史用户补齐 Code + * + * @return 本次补齐的用户数量 + */ + int fillMissingUserCodes(); + /** * 更新用户的最后登陆信息 * diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java index e1ed569..93a2329 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.member.client.service.ChatApi; import cn.iocoder.yudao.module.member.client.vo.chat.user.ChatCreateOrUpdateUserReqVO; import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO; @@ -35,6 +36,7 @@ import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import cn.iocoder.yudao.module.trade.api.brokerage.TradeBrokerageUserApi; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -61,6 +63,12 @@ import static cn.iocoder.yudao.module.member.enums.MemberErrorCode.*; @Slf4j public class MemberUserServiceImpl implements MemberUserService { + private static final String USER_CODE_CHARACTERS = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int USER_CODE_LENGTH = 6; + private static final int USER_CODE_GENERATE_MAX_RETRY = 20; + private static final int USER_CODE_FILL_BATCH_SIZE = 1000; + @Resource private MemberUserMapper memberUserMapper; @Resource @@ -132,6 +140,7 @@ public class MemberUserServiceImpl implements MemberUserService { user.setNickname("用户" + RandomUtil.randomNumbers(6)); } memberUserMapper.insert(user); + assignUserCode(user); // 发送 MQ 消息:用户创建 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @@ -145,6 +154,55 @@ public class MemberUserServiceImpl implements MemberUserService { return user; } + @Override + @TenantIgnore + public int fillMissingUserCodes() { + int fillCount = 0; + while (true) { + List userIds = memberUserMapper.selectIdListByEmptyUserCode(USER_CODE_FILL_BATCH_SIZE); + if (CollUtil.isEmpty(userIds)) { + return fillCount; + } + for (Long userId : userIds) { + MemberUserDO user = new MemberUserDO().setId(userId); + if (assignUserCode(user)) { + fillCount++; + } + } + } + } + + /** + * 通过数据库唯一索引解决并发碰撞;更新方式只修改 user_code,便于安全重试。 + */ + private boolean assignUserCode(MemberUserDO user) { + for (int i = 0; i < USER_CODE_GENERATE_MAX_RETRY; i++) { + String userCode = generateUserCode(); + try { + if (memberUserMapper.updateUserCodeIfEmpty(user.getId(), userCode) > 0) { + user.setUserCode(userCode); + return true; + } + } catch (DuplicateKeyException ex) { + log.debug("生成会员用户 Code 发生碰撞,准备重试,userId={}", user.getId()); + continue; + } + MemberUserDO currentUser = memberUserMapper.selectById(user.getId()); + if (currentUser == null) { + return false; + } + if (StrUtil.isNotBlank(currentUser.getUserCode())) { + user.setUserCode(currentUser.getUserCode()); + return false; + } + } + throw new IllegalStateException("生成会员用户 Code 失败,请稍后重试,userId=" + user.getId()); + } + + private String generateUserCode() { + return RandomUtil.randomString(USER_CODE_CHARACTERS, USER_CODE_LENGTH); + } + @Override public void updateUserLogin(Long id, String loginIp) { memberUserMapper.updateById(new MemberUserDO().setId(id) diff --git a/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql b/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql index 782a818..af2bedc 100644 --- a/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql @@ -1,6 +1,7 @@ CREATE TABLE IF NOT EXISTS "member_user" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "user_code" varchar(6) NULL COMMENT '用户 Code', "nickname" varchar(30) NOT NULL DEFAULT '' COMMENT '用户昵称', "name" varchar(30) NULL COMMENT '真实名字', sex tinyint null comment '性别', @@ -25,7 +26,8 @@ CREATE TABLE IF NOT EXISTS "member_user" "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', "deleted" bit(1) NOT NULL DEFAULT '0' COMMENT '是否删除', "tenant_id" bigint not null default '0', - PRIMARY KEY ("id") + PRIMARY KEY ("id"), + UNIQUE ("user_code") ) COMMENT '会员表'; CREATE TABLE IF NOT EXISTS "member_address" ( diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 8e4f0c4..b8c4bee 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -234,6 +234,7 @@ pf4j: chat: base-url: http://im-platform.htysapp.com + # base-url: http://127.0.0.1:8888 app-key: 4H2HkBLygMsxaQKO app-secret: JIizm84av3TK5xaQdsng7X5mqLPjbeQ0