微信号问题修改

This commit is contained in:
周鹏 committed 2026-08-15 08:00:20 +08:00
1 parent d473d5d04a
commit b029b08cee
16 files changed
+749 -14

No files matched your search

+283
View File
@@ -0,0 +1,283 @@
# 清库操作文档:清空全部用户数据(含 IM)
> 适用环境:荷塘月色业务后端 `friend_back` + IM 系统 `chat-im`
> 目标:清除**所有会员用户**相关数据(资料、会话、钱包/分销用户侧数据、IM 好友与聊天等)
> **不是**「注销单个用户」;也不是清空系统配置/管理员账号。
---
## 1. 先搞清楚有两套系统
| 系统 | 代码目录 | MySQL 库 | Redis | 文件 |
|------|----------|----------|-------|------|
| 业务 API | `friend_back` | **`chat_v1`** | **db 7** | `infra_file` + 对应对象存储(七牛/MinIO 等) |
| IM | `chat-im`(`im-platform` / `im-server`) | **`im_platform`** | **db 10** | MinIO bucket **`im-platform`** |
- 会员 ID 对齐:`member_user.id` == `im_user.id`
- `friend_back` 只通过 HTTP 调 IM(`chat.base-url`),**没有「删除用户」的第三方 API**,IM 必须在 `im_platform` 库直接清
- 特殊 IM 账号(**不要删**):
- `-100` 系统消息通知号
- `-200` 客服号
生产配置参考:
- 业务:`friend_back/yudao-server/src/main/resources/application-prod.yaml`(`chat_v1`、Redis db7、`chat.base-url`)
- IM:`chat-im/im-platform/src/main/resources/application-prod.yml`(`im_platform`、Redis db10)
---
## 2. 操作前检查清单
1. **确认环境**(测试 / 预发 / 生产),禁止在未备份的生产库直接执行。
2. **全量备份**
```bash
mysqldump -h... -u... -p chat_v1 > chat_v1_backup_$(date +%Y%m%d).sql
mysqldump -h... -u... -p im_platform > im_platform_backup_$(date +%Y%m%d).sql
# Redis:按运维规范导出 RDB/AOF(业务 db7、IM db10)
# 对象存储:按需快照或确认可丢弃用户上传文件
```
3. **停写流量**(强烈建议)
- 停或摘流:`yudao-server`、`im-platform`、`im-server`
- 避免删一半又有新注册/新消息写入
4. **合规确认**
- 支付订单、退款、提现流水是否允许物理删除(默认建议**保留或脱敏归档**,见第 5 节)
5. **现有后台没有「一键清会员」接口**,只能运维 SQL + Redis + 存储清理。
---
## 3. 建议操作顺序(总览)
```text
① 停服务
② 备份 MySQL / Redis / 文件
③ 清业务库 chat_v1(用户相关表)
④ 清 IM 库 im_platform(保留 -100/-200)
⑤ 清 Redis db7、db10
⑥ 清用户上传文件(可选)
⑦ 启服务并验收
```
---
## 4. 业务库 `chat_v1`:清用户数据
> 执行前先 `USE chat_v1;`
> 下面按「先子表、后主表」;若表不存在可跳过。
> 全量重置测试环境可用 `TRUNCATE`;生产更建议 `DELETE` + 条件(如按 `tenant_id`)。
### 4.1 会员扩展与行为(必清)
```sql
-- 相册 / 位置 / 动态相关
TRUNCATE TABLE member_photo;
TRUNCATE TABLE member_location;
TRUNCATE TABLE member_recent;
TRUNCATE TABLE member_operation;
TRUNCATE TABLE member_view_record;
TRUNCATE TABLE member_recent_tag;
TRUNCATE TABLE member_recent_cfg;
-- 认证 / 反馈 / 开通记录 / 积分经验签到
TRUNCATE TABLE member_com_auth;
TRUNCATE TABLE member_feedback;
TRUNCATE TABLE member_open_type_user;
TRUNCATE TABLE member_point_record;
TRUNCATE TABLE member_level_record;
TRUNCATE TABLE member_experience_record;
TRUNCATE TABLE member_sign_in_record;
TRUNCATE TABLE member_address;
```
### 4.2 登录态 / 社交绑定(会员侧)
```sql
-- user_type = 1 表示会员(Member)
DELETE FROM system_oauth2_access_token WHERE user_type = 1;
DELETE FROM system_oauth2_refresh_token WHERE user_type = 1;
DELETE FROM system_oauth2_approve WHERE user_type = 1;
DELETE FROM system_oauth2_code WHERE user_type = 1;
DELETE FROM system_social_user_bind WHERE user_type = 1;
-- 可选:清理已无绑定的 social_user 孤儿数据(按你们库实际字段调整)
DELETE FROM system_login_log WHERE user_type = 1;
DELETE FROM system_notify_message WHERE user_type = 1;
-- 短信验证码可按需清空
TRUNCATE TABLE system_sms_code;
```
### 4.3 分销用户数据(必清用户侧)
```sql
TRUNCATE TABLE trade_brokerage_record;
TRUNCATE TABLE trade_brokerage_withdraw;
TRUNCATE TABLE trade_brokerage_code;
TRUNCATE TABLE trade_brokerage_user;
```
### 4.4 钱包用户数据(必清用户侧)
```sql
TRUNCATE TABLE pay_wallet_transaction;
TRUNCATE TABLE pay_wallet_recharge;
TRUNCATE TABLE pay_wallet_user_transition; -- 若存在
TRUNCATE TABLE pay_wallet_tx_auth; -- 若存在
DELETE FROM pay_wallet WHERE user_type = 1;
```
### 4.5 商城用户行为(若启用 mall)
```sql
TRUNCATE TABLE trade_cart;
TRUNCATE TABLE trade_order_log;
TRUNCATE TABLE trade_order_item;
TRUNCATE TABLE trade_after_sale_log;
TRUNCATE TABLE trade_after_sale;
TRUNCATE TABLE trade_order;
TRUNCATE TABLE product_browse_history;
TRUNCATE TABLE product_favorite;
TRUNCATE TABLE product_comment;
-- 优惠券领取记录等带 user_id 的 promotion_* 表按实际启用情况清理
```
### 4.6 最后删会员主表
```sql
TRUNCATE TABLE member_user;
-- 或:DELETE FROM member_user;
```
### 4.7 业务库「不要清」的配置表(示例)
保留系统能继续跑的配置:
- `member_config`、`member_level`、`member_tag`、`member_group`
- `member_sign_in_config`、`member_open_type`(会员套餐)
- `pay_app`、`pay_channel`、`pay_wallet_recharge_package`
- `trade_config`
- `system_oauth2_client`、`system_social_client`、短信模板/渠道
- `system_users` 等**后台管理员**账号(除非连运营账号一起重置)
---
## 5. 支付流水是否删除(单独决策)
| 策略 | 处理 |
|------|------|
| **推荐(测试可删、生产慎删)** | 保留 `pay_order` / `pay_order_extension` / `pay_refund` / `pay_transfer` / `pay_notify_*`,仅清用户侧钱包与会员 |
| **测试环境彻底重置** | 可一并 `TRUNCATE` 上述支付流水表,并确认与渠道对账无冲突 |
若生产必须销毁支付痕迹,需法务/财务确认后另出工单,不建议与「清用户」混为一谈。
---
## 6. IM 库 `im_platform`:清 IM 数据
> 执行前先 `USE im_platform;`
> 建表参考:`chat-im/db/im-platform.sql`
```sql
-- 消息与关系
TRUNCATE TABLE im_private_message;
TRUNCATE TABLE im_group_message;
TRUNCATE TABLE im_friend;
TRUNCATE TABLE im_group_member;
TRUNCATE TABLE im_group;
TRUNCATE TABLE im_file_info;
-- 用户:保留系统号
DELETE FROM im_user WHERE id NOT IN (-100, -200);
-- 敏感词库一般保留
-- TRUNCATE TABLE im_sensitive_word; -- 仅当要重置词库时
```
**说明:**
- 不要依赖 `friend_back` 的 ChatApi 删用户(接口不支持)
- 清完后会员重新登录会走同步逻辑(`syncChatUser`)在 IM 侧重建用户
---
## 7. Redis
**务必指定 DB,禁止误 FLUSH 其它库。**
```bash
# 业务 Redis(application-prod: database 7)
redis-cli -n 7 FLUSHDB
# IM Redis(im-platform/im-server: database 10)
redis-cli -n 10 FLUSHDB
```
若不能整库清空,至少清理:
- 业务:`oauth2_access_token:*`、会员相关 lock/cache、`social_auth_state:*` 等
- IM:`im:user:*`、`im:cache:*`、`im:message:*`、`im:readed:*` 等
---
## 8. 文件存储(可选)
| 来源 | 处理建议 |
|------|----------|
| 业务头像/相册/认证图 | 根据 `infra_file` + `member_user.avatar` / `member_photo` 对应对象存储清理;测试环境可按配置整桶策略清理 |
| IM 图片视频 | MinIO bucket `im-platform`;清 `im_file_info` 后按 path 删对象,或测试环境整桶清理 |
生产环境删除对象存储前务必确认无合规留存要求。
---
## 9. 启动与验收
1. 启动 `im-platform` → `im-server` → `yudao-server`
2. 检查:
```sql
SELECT COUNT(*) FROM chat_v1.member_user; -- 期望 0
SELECT COUNT(*) FROM im_platform.im_user
WHERE id NOT IN (-100, -200); -- 期望 0
SELECT id, user_name FROM im_platform.im_user; -- 应还能看到 -100/-200
```
3. App 侧:
- 旧 token 应失效,需重新注册/登录
- 登录后 IM 可正常建链、发消息
- 后台管理账号仍可登录(若未清 `system_users`)
4. 配置仍在:会员套餐、支付渠道、分销开关、敏感词等
---
## 10. 回滚
仅能依赖操作前备份:
```bash
mysql -h... -u... -p chat_v1 < chat_v1_backup_YYYYMMDD.sql
mysql -h... -u... -p im_platform < im_platform_backup_YYYYMMDD.sql
# 恢复 Redis / 文件快照后重启服务
```
---
## 11. 常见问题
**Q:只清 `member_user` 行不行?**
不行。登录 token、IM 消息、钱包、分销、相册等都会残留,且可能脏数据关联异常。
**Q:清完业务库,IM 会不会自动空?**
不会。必须单独清 `im_platform`(和 Redis db10)。
**Q:有没有管理后台按钮?**
没有会员「注销/全量删除」接口;管理员删除只作用于后台 `system_users`。
**Q:测试环境最快怎么干?**
停服务 → 备份 → 按第 4~7 节 TRUNCATE/DELETE + `FLUSHDB` → 启服务。
---
## 12. 变更记录
| 日期 | 说明 |
|------|------|
| 2026-08-12 | 初版:基于当前 `friend_back` + `chat-im` 配置与表结构整理 |
+18
View File
@@ -0,0 +1,18 @@
-- 会员互动消息(被查看 / 微信被解锁)
CREATE TABLE IF NOT EXISTS `member_interaction` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`receiver_id` bigint NOT NULL COMMENT '接收人(被查看 / 被解锁用户)',
`actor_id` bigint NOT NULL COMMENT '操作人',
`biz_type` varchar(32) NOT NULL COMMENT 'VIEWED / WECHAT_UNLOCKED',
`ref_id` bigint NULL DEFAULT NULL COMMENT '关联业务 id(访客记录 / 操作记录)',
`read_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_receiver_read` (`receiver_id`, `read_status`) USING BTREE,
KEY `idx_receiver_time` (`receiver_id`, `id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员互动消息';
+18
View File
@@ -0,0 +1,18 @@
-- 会员推送设备(极光 RegistrationID 绑定)
CREATE TABLE IF NOT EXISTS `member_push_device` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`member_id` bigint NOT NULL COMMENT '会员编号',
`registration_id` varchar(64) NOT NULL COMMENT '极光 RegistrationID',
`alias` varchar(64) NULL DEFAULT NULL COMMENT '极光别名,如 u{memberId}',
`platform` varchar(16) NOT NULL COMMENT 'ios / android',
`provider` varchar(16) NOT NULL DEFAULT 'jpush' COMMENT '推送服务商',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_registration_id` (`registration_id`) USING BTREE,
KEY `idx_member_id` (`member_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员推送设备';
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.member.enums.interaction;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 互动消息类型
*/
@Getter
@AllArgsConstructor
public enum MemberInteractionBizTypeEnum {
VIEWED("VIEWED", "查看了你"),
WECHAT_UNLOCKED("WECHAT_UNLOCKED", "解锁了你的微信");
private final String type;
private final String contentSuffix;
}
@@ -19,8 +19,7 @@ public class CreateWechatAuthReqVo {
@NotEmpty(message = "微信号不能为空")
private String wechatNumber;
@Schema(description = "微信二维码")
@NotEmpty(message = "微信二维码不能为空")
@Schema(description = "微信二维码(选填)")
private String wechatQrcode;
}
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.member.controller.app.interaction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionRespVO;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionSummaryRespVO;
import cn.iocoder.yudao.module.member.service.interaction.MemberInteractionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 互动消息")
@RestController
@RequestMapping("/member/interaction")
@Validated
public class AppMemberInteractionController {
@Resource
private MemberInteractionService interactionService;
@GetMapping("/summary")
@Operation(summary = "互动消息摘要(未读数 + 最近一条)")
public CommonResult<AppInteractionSummaryRespVO> getSummary() {
return success(interactionService.getSummary(getLoginUserId()));
}
@GetMapping("/page")
@Operation(summary = "互动消息分页")
public CommonResult<PageResult<AppInteractionRespVO>> getPage(@Valid PageParam pageParam) {
return success(interactionService.getPage(getLoginUserId(), pageParam));
}
@PostMapping("/read-all")
@Operation(summary = "全部标为已读")
public CommonResult<Boolean> readAll() {
interactionService.readAll(getLoginUserId());
return success(true);
}
}
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.member.controller.app.interaction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - 互动消息 Response VO")
@Data
public class AppInteractionRespVO {
@Schema(description = "编号")
private Long id;
@Schema(description = "类型:VIEWED / WECHAT_UNLOCKED")
private String bizType;
@Schema(description = "文案,如「张三 查看了你」")
private String content;
@Schema(description = "操作人编号")
private Long actorId;
@Schema(description = "操作人头像")
private String actorAvatar;
@Schema(description = "操作人昵称")
private String actorNickname;
@Schema(description = "是否已读")
private Boolean readStatus;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.member.controller.app.interaction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - 互动消息摘要 Response VO")
@Data
public class AppInteractionSummaryRespVO {
@Schema(description = "未读数")
private Long unreadCount;
@Schema(description = "最近一条文案")
private String lastContent;
@Schema(description = "最近一条时间")
private LocalDateTime lastTime;
}
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.member.dal.dataobject.interaction;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 会员互动消息 DO
*/
@TableName("member_interaction")
@KeySequence("member_interaction_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberInteractionDO extends TenantBaseDO {
@TableId
private Long id;
/**
* 接收人(被查看 / 被解锁)
*/
private Long receiverId;
/**
* 操作人
*/
private Long actorId;
/**
* VIEWED / WECHAT_UNLOCKED
*/
private String bizType;
/**
* 关联业务 id
*/
private Long refId;
/**
* 是否已读
*/
private Boolean readStatus;
}
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.member.dal.mysql.interaction;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.dal.dataobject.interaction.MemberInteractionDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberInteractionMapper extends BaseMapperX<MemberInteractionDO> {
default PageResult<MemberInteractionDO> selectPageByReceiver(Long receiverId, PageParam pageParam) {
return selectPage(pageParam, new LambdaQueryWrapperX<MemberInteractionDO>()
.eq(MemberInteractionDO::getReceiverId, receiverId)
.orderByDesc(MemberInteractionDO::getId));
}
default Long selectUnreadCount(Long receiverId) {
return selectCount(new LambdaQueryWrapperX<MemberInteractionDO>()
.eq(MemberInteractionDO::getReceiverId, receiverId)
.eq(MemberInteractionDO::getReadStatus, false));
}
default MemberInteractionDO selectLatest(Long receiverId) {
return selectOne(new LambdaQueryWrapperX<MemberInteractionDO>()
.eq(MemberInteractionDO::getReceiverId, receiverId)
.orderByDesc(MemberInteractionDO::getId)
.last("LIMIT 1"));
}
default int markAllRead(Long receiverId) {
return update(null, new LambdaUpdateWrapper<MemberInteractionDO>()
.set(MemberInteractionDO::getReadStatus, true)
.eq(MemberInteractionDO::getReceiverId, receiverId)
.eq(MemberInteractionDO::getReadStatus, false));
}
}
@@ -27,6 +27,24 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
return selectOne(MemberUserDO::getMobile, mobile);
}
/**
* 按微信号查其他用户(用于唯一性校验,排除指定会员)
*/
default MemberUserDO selectByWechatNumExcludeId(String wechatNum, Long excludeMemberId) {
return selectOne(new LambdaQueryWrapperX<MemberUserDO>()
.eq(MemberUserDO::getWechatNum, wechatNum)
.neIfPresent(MemberUserDO::getId, excludeMemberId));
}
/**
* 按微信号查其他用户(用于唯一性校验,排除本人)
*/
default MemberUserDO selectByWechatNumExcludeId(String wechatNum, Long excludeMemberId) {
return selectOne(new LambdaQueryWrapperX<MemberUserDO>()
.eq(MemberUserDO::getWechatNum, wechatNum)
.ne(excludeMemberId != null, MemberUserDO::getId, excludeMemberId));
}
default List<Long> selectIdListByEmptyUserCode(int limit) {
return selectList(new LambdaQueryWrapperX<MemberUserDO>()
.select(MemberUserDO::getId)
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -149,34 +150,39 @@ public class ComAuthServiceImpl implements ComAuthService {
}
/**
* 创建微信认证
* 创建微信认证:校验微信号未被他人占用后自动审核通过并落库,不再走人工审核。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ComAuthDO createWechatAuth(CreateWechatAuthReqVo dto) {
String wechatNumber = StrUtil.trim(dto.getWechatNumber());
if (StrUtil.isBlank(wechatNumber)) {
throw exception(MemberErrorCode.EMPTY, "微信号不能为空");
}
dto.setWechatNumber(wechatNumber);
// 查重:其他用户已占用则直接拒绝
MemberUserDO occupied = memberUserMapper.selectByWechatNumExcludeId(wechatNumber, dto.getMemberId());
if (ObjectUtil.isNotEmpty(occupied)) {
throw exception(MemberErrorCode.EMPTY, "请勿填写重复的微信号");
}
ComAuthDO.WechatAuthExtra extraObj = new ComAuthDO.WechatAuthExtra();
extraObj.setWechatNumber(dto.getWechatNumber());
extraObj.setWechatNumber(wechatNumber);
extraObj.setWechatQrcode(dto.getWechatQrcode());
ComAuthDO saveObj = new ComAuthDO();
saveObj.setAuthMemberId(dto.getMemberId());
saveObj.setBizType(MemberComAuthEnum.WECHAT_NUMBER.getType());
saveObj.setAuthStatus(MemberComAuthStatusEnum.WAIT_AUTH.getType());
// 无重复则自动通过,不再进入人工审核队列
saveObj.setAuthStatus(MemberComAuthStatusEnum.CHECK_PASS.getType());
saveObj.setAuthTime(LocalDateTime.now());
saveObj.setAuthPic(dto.getWechatQrcode());
saveObj.setAuthExtraJson(JSONUtil.toJsonStr(extraObj));
authMapper.insert(saveObj);
ComAuthDO detail = authMapper.selectById(saveObj.getId());
// 查询配置
MemberConfigDO config = memberConfigService.getConfig();
if (ObjectUtil.isNotEmpty(config) && ObjectUtil.isNotEmpty(config.getAutoWechatNumAuth())) {
if (config.getAutoWechatNumAuth()) {
getSelf().wechatVerify(detail);
}
}
getSelf().wechatVerify(detail);
return detail;
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.member.service.interaction;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionRespVO;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionSummaryRespVO;
import cn.iocoder.yudao.module.member.enums.interaction.MemberInteractionBizTypeEnum;
public interface MemberInteractionService {
/**
* 写入一条互动消息(接收人=被操作方)
*/
void create(Long receiverId, Long actorId, MemberInteractionBizTypeEnum bizType, Long refId);
AppInteractionSummaryRespVO getSummary(Long receiverId);
PageResult<AppInteractionRespVO> getPage(Long receiverId, PageParam pageParam);
void readAll(Long receiverId);
}
@@ -0,0 +1,126 @@
package cn.iocoder.yudao.module.member.service.interaction;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionRespVO;
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionSummaryRespVO;
import cn.iocoder.yudao.module.member.dal.dataobject.interaction.MemberInteractionDO;
import cn.iocoder.yudao.module.member.dal.mysql.interaction.MemberInteractionMapper;
import cn.iocoder.yudao.module.member.enums.interaction.MemberInteractionBizTypeEnum;
import cn.iocoder.yudao.module.member.service.user.BO.MemberExtBO;
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Validated
public class MemberInteractionServiceImpl implements MemberInteractionService {
@Resource
private MemberInteractionMapper interactionMapper;
@Resource
private MemberUserService memberUserService;
@Override
public void create(Long receiverId, Long actorId, MemberInteractionBizTypeEnum bizType, Long refId) {
if (receiverId == null || actorId == null || bizType == null) {
return;
}
if (ObjectUtil.equals(receiverId, actorId)) {
return;
}
MemberInteractionDO row = MemberInteractionDO.builder()
.receiverId(receiverId)
.actorId(actorId)
.bizType(bizType.getType())
.refId(refId)
.readStatus(false)
.build();
interactionMapper.insert(row);
}
@Override
public AppInteractionSummaryRespVO getSummary(Long receiverId) {
AppInteractionSummaryRespVO vo = new AppInteractionSummaryRespVO();
Long unread = interactionMapper.selectUnreadCount(receiverId);
vo.setUnreadCount(unread == null ? 0L : unread);
MemberInteractionDO latest = interactionMapper.selectLatest(receiverId);
if (latest == null) {
vo.setLastContent("");
return vo;
}
List<AppInteractionRespVO> filled = fillActors(Collections.singletonList(latest));
AppInteractionRespVO first = filled.isEmpty() ? null : filled.get(0);
vo.setLastContent(first != null ? first.getContent() : "");
vo.setLastTime(latest.getCreateTime());
return vo;
}
@Override
public PageResult<AppInteractionRespVO> getPage(Long receiverId, PageParam pageParam) {
PageResult<MemberInteractionDO> page = interactionMapper.selectPageByReceiver(receiverId, pageParam);
if (ObjectUtil.isEmpty(page.getList())) {
return PageResult.empty(page.getTotal());
}
List<AppInteractionRespVO> list = fillActors(page.getList());
return new PageResult<>(list, page.getTotal());
}
@Override
public void readAll(Long receiverId) {
interactionMapper.markAllRead(receiverId);
}
private List<AppInteractionRespVO> fillActors(List<MemberInteractionDO> rows) {
if (ObjectUtil.isEmpty(rows)) {
return Collections.emptyList();
}
List<Long> actorIds = rows.stream().map(MemberInteractionDO::getActorId).distinct().collect(Collectors.toList());
List<MemberExtBO> members = memberUserService.getUserExtList(actorIds);
Map<Long, MemberExtBO> memberMap = IterUtil.toMap(members, MemberExtBO::getMemberId);
return rows.stream().map(row -> {
AppInteractionRespVO vo = new AppInteractionRespVO();
vo.setId(row.getId());
vo.setBizType(row.getBizType());
vo.setActorId(row.getActorId());
vo.setReadStatus(Boolean.TRUE.equals(row.getReadStatus()));
vo.setCreateTime(row.getCreateTime());
MemberExtBO actor = memberMap.get(row.getActorId());
String nickname = "用户";
if (actor != null) {
vo.setActorAvatar(actor.getMemberAvatar());
if (StrUtil.isNotBlank(actor.getMemberNickname())) {
nickname = actor.getMemberNickname();
}
vo.setActorNickname(nickname);
} else {
vo.setActorNickname(nickname);
}
vo.setContent(buildContent(nickname, row.getBizType()));
return vo;
}).collect(Collectors.toList());
}
private String buildContent(String nickname, String bizType) {
MemberInteractionBizTypeEnum type = Arrays.stream(MemberInteractionBizTypeEnum.values())
.filter(e -> ObjectUtil.equals(e.getType(), bizType))
.findFirst()
.orElse(null);
if (type == null) {
return nickname + " 与你产生了互动";
}
return nickname + " " + type.getContentSuffix();
}
}
@@ -25,8 +25,10 @@ import cn.iocoder.yudao.module.member.enums.MemberRecentAuditSourceEnum;
import cn.iocoder.yudao.module.member.enums.MemberRecentAuditStatusEnum;
import cn.iocoder.yudao.module.member.enums.MemberRecentOperateEnum;
import cn.iocoder.yudao.module.member.enums.MemberRecentSceneEnum;
import cn.iocoder.yudao.module.member.enums.interaction.MemberInteractionBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.tag.MemberRecentTagEnum;
import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
import cn.iocoder.yudao.module.member.service.interaction.MemberInteractionService;
import cn.iocoder.yudao.module.member.service.location.LocationBO;
import cn.iocoder.yudao.module.member.service.location.LocationService;
import cn.iocoder.yudao.module.member.service.opentype.OpenTypeService;
@@ -105,6 +107,8 @@ public class RecentServiceImpl implements RecentService {
private TxMapApi txMapApi;
@Resource
private ChatApi chatApi;
@Resource
private MemberInteractionService memberInteractionService;
/**
* 创建朋友圈
@@ -402,6 +406,13 @@ public class RecentServiceImpl implements RecentService {
operationObj.setBizType(MemberRecentOperateEnum.MEMBER_LOOK_WECHAT.getType());
operationObj.setTapTime(LocalDateTime.now());
operationMapper.insert(operationObj);
// 首次解锁:通知被解锁用户
memberInteractionService.create(
memberId,
operateMemberId,
MemberInteractionBizTypeEnum.WECHAT_UNLOCKED,
operationObj.getId()
);
return memberWechat;
}
@@ -24,6 +24,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.member.dal.mysql.viewrecord.ViewRecordMapper;
import cn.iocoder.yudao.module.member.enums.interaction.MemberInteractionBizTypeEnum;
import cn.iocoder.yudao.module.member.service.interaction.MemberInteractionService;
import java.util.List;
import java.util.Map;
@@ -42,6 +44,8 @@ public class ViewRecordServiceImpl implements ViewRecordService {
private ViewRecordMapper viewRecordMapper;
@Resource
private MemberUserService memberUserService;
@Resource
private MemberInteractionService memberInteractionService;
/**
* 创建用户查看记录
@@ -56,6 +60,19 @@ public class ViewRecordServiceImpl implements ViewRecordService {
viewRecord.setTitle("访问朋友圈");
}
viewRecordMapper.insert(viewRecord);
// 查看用户主页:给被查看方写入互动消息
if (ObjectUtil.equals(viewRecord.getBizType(), MemberRecentViewRecordEnum.USER.getType())
&& viewRecord.getBizId() != null
&& viewRecord.getMemberId() != null
&& ObjectUtil.notEqual(viewRecord.getBizId(), viewRecord.getMemberId())) {
memberInteractionService.create(
viewRecord.getBizId(),
viewRecord.getMemberId(),
MemberInteractionBizTypeEnum.VIEWED,
viewRecord.getId()
);
}
return viewRecord.getId();
}