fix 创建群
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
package com.tailbet.controller;
|
||||
|
||||
import com.tailbet.model.dto.CreateGroupDTO;
|
||||
import com.tailbet.model.dto.PointsDTO;
|
||||
import com.tailbet.model.dto.PullGroupDTO;
|
||||
import com.tailbet.model.entity.Group;
|
||||
import com.tailbet.model.vo.R;
|
||||
import com.tailbet.model.vo.UserContext;
|
||||
import com.tailbet.service.IPointsService;
|
||||
import com.tailbet.service.IGroupService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,21 @@ public class OpsController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建群(指定群ID、名称,群主为传入用户,并自动把机器人拉入群)
|
||||
*/
|
||||
@PostMapping("/group/create")
|
||||
public R<Long> createGroup(@Valid @RequestBody CreateGroupDTO dto) {
|
||||
if (!UserContext.isOps()) {
|
||||
return R.fail("无权限:仅运营可操作");
|
||||
}
|
||||
Long operatorId = UserContext.getUserId();
|
||||
Group group = groupService.createGroup(dto.getName(),
|
||||
dto.getUserId(), dto.getRoundInterval(), operatorId);
|
||||
return R.ok(group.getId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拉入群
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.tailbet.model.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建群请求
|
||||
*/
|
||||
@Data
|
||||
public class CreateGroupDTO {
|
||||
|
||||
/**
|
||||
* 群名称
|
||||
*/
|
||||
@NotBlank(message = "群名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 群主用户ID
|
||||
*/
|
||||
@NotNull(message = "群主用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 接注间隔(秒),不传则走库默认值60
|
||||
*/
|
||||
@Min(value = 10, message = "接注间隔不能小于10秒")
|
||||
@Max(value = 3600, message = "接注间隔不能大于3600秒")
|
||||
private Integer roundInterval;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import java.time.LocalDateTime;
|
||||
* 群表
|
||||
*/
|
||||
@Data
|
||||
@TableName("group")
|
||||
@TableName("g_group")
|
||||
public class Group {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
|
||||
@@ -25,6 +25,18 @@ public interface IGroupService {
|
||||
*/
|
||||
List<Group> getUserGroups(Long userId);
|
||||
|
||||
/**
|
||||
* 创建群(群ID自增生成,群主为传入用户,并自动把机器人拉入群)
|
||||
* <p>机器人取 sys_config 中 cfg_key=robot_id 的配置值</p>
|
||||
*
|
||||
* @param name 群名称
|
||||
* @param ownerUserId 群主用户ID
|
||||
* @param roundInterval 接注间隔(秒),可为null走库默认值
|
||||
* @param operatorId 操作人用户ID
|
||||
* @return 创建出的群
|
||||
*/
|
||||
Group createGroup( String name, Long ownerUserId, Integer roundInterval, Long operatorId);
|
||||
|
||||
/**
|
||||
* 拉用户入群
|
||||
*/
|
||||
|
||||
@@ -2,9 +2,13 @@ package com.tailbet.service.impl;
|
||||
|
||||
import com.tailbet.model.entity.Group;
|
||||
import com.tailbet.model.entity.GroupMember;
|
||||
import com.tailbet.model.entity.SysConfig;
|
||||
import com.tailbet.model.entity.User;
|
||||
import com.tailbet.mapper.GroupMapper;
|
||||
import com.tailbet.mapper.GroupMemberMapper;
|
||||
import com.tailbet.mapper.SysConfigMapper;
|
||||
import com.tailbet.mapper.UserMapper;
|
||||
import com.tailbet.openim.OpenIMApiClient;
|
||||
import com.tailbet.service.IGroupService;
|
||||
import com.tailbet.service.IAuditService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -24,10 +28,17 @@ import java.util.List;
|
||||
@Service
|
||||
public class GroupServiceImpl implements IGroupService {
|
||||
|
||||
/**
|
||||
* 系统配置键:机器人用户ID
|
||||
*/
|
||||
private static final String CFG_KEY_ROBOT_ID = "robot_id";
|
||||
|
||||
private final GroupMapper groupMapper;
|
||||
private final GroupMemberMapper groupMemberMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final SysConfigMapper sysConfigMapper;
|
||||
private final IAuditService auditService;
|
||||
private final OpenIMApiClient openIMApiClient;
|
||||
|
||||
/**
|
||||
* 获取群信息
|
||||
@@ -58,6 +69,117 @@ public class GroupServiceImpl implements IGroupService {
|
||||
.eq(Group::getStatus, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建群(群ID自增生成,群主为传入用户,并自动把机器人拉入群)
|
||||
* <p>
|
||||
* 机器人取 sys_config 中 cfg_key=robot_id 的配置值。
|
||||
* 先落本地库,再调 OpenIM 建群:OpenIM 失败会抛异常回滚本地事务,
|
||||
* 保证不出现"本地有群、IM 无群"的脏数据。反向残留(IM 有群、本地回滚)可安全重试,
|
||||
* 因为 {@link OpenIMApiClient#createGroup} 对已存在的群做了幂等跳过。
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Group createGroup( String name, Long ownerUserId, Integer roundInterval, Long operatorId) {
|
||||
|
||||
if (name == null || name.isBlank()) {
|
||||
throw new RuntimeException("群名称不能为空");
|
||||
}
|
||||
if (ownerUserId == null) {
|
||||
throw new RuntimeException("群主用户ID不能为空");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 群主必须是已存在的用户
|
||||
User owner = userMapper.selectById(ownerUserId);
|
||||
if (owner == null) {
|
||||
throw new RuntimeException("群主用户不存在: " + ownerUserId);
|
||||
}
|
||||
|
||||
// 机器人账号(sys_config 中 cfg_key=robot_id)
|
||||
Long botUserId = getRobotUserId();
|
||||
if (userMapper.selectById(botUserId) == null) {
|
||||
throw new RuntimeException("机器人用户不存在: " + botUserId + ",请检查 sys_config.robot_id 配置");
|
||||
}
|
||||
// 群主本人就是机器人时,只需建一条群主记录
|
||||
boolean ownerIsBot = botUserId.equals(ownerUserId);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 建群:id 由数据库自增生成
|
||||
// openim_group_id 为 NOT NULL 且要与自增 id 保持一致,
|
||||
// 故先占位插入拿到 id,再回填。同一事务内,不存在中间态外泄。
|
||||
Group group = new Group();
|
||||
group.setName(name);
|
||||
group.setOpenimGroupId("");
|
||||
group.setOwnerUserId(ownerUserId);
|
||||
group.setRoundInterval(roundInterval);
|
||||
group.setStatus(1);
|
||||
group.setCreateTime(now);
|
||||
group.setUpdateTime(now);
|
||||
groupMapper.insert(group);
|
||||
|
||||
Long groupId = group.getId();
|
||||
group.setOpenimGroupId(String.valueOf(groupId));
|
||||
groupMapper.updateById(group);
|
||||
|
||||
// 群主入群
|
||||
groupMemberMapper.insert(buildMember(groupId, ownerUserId, ROLE_OWNER, now));
|
||||
|
||||
// 机器人入群
|
||||
if (!ownerIsBot) {
|
||||
groupMemberMapper.insert(buildMember(groupId, botUserId, ROLE_NORMAL, now));
|
||||
}
|
||||
|
||||
// OpenIM 侧建群:群主为 owner,机器人作为初始成员
|
||||
List<String> memberUserIDs = ownerIsBot
|
||||
? List.of()
|
||||
: List.of(String.valueOf(botUserId));
|
||||
openIMApiClient.createGroup(String.valueOf(groupId), name, String.valueOf(ownerUserId), memberUserIDs);
|
||||
|
||||
// 审计日志
|
||||
auditService.log(groupId, null, operatorId, "CREATE_GROUP",
|
||||
String.format("{\"name\":\"%s\",\"ownerUserId\":%d,\"botUserId\":%d}",
|
||||
name.replace("\\", "\\\\").replace("\"", "\\\""), ownerUserId, botUserId));
|
||||
|
||||
log.info("创建群成功: groupId={}, name={}, ownerUserId={}, botUserId={}, operatorId={}",
|
||||
groupId, name, ownerUserId, botUserId, operatorId);
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取机器人用户ID(sys_config 中 cfg_key=robot_id)
|
||||
*/
|
||||
private Long getRobotUserId() {
|
||||
SysConfig config = sysConfigMapper.selectOne(new LambdaQueryWrapper<SysConfig>()
|
||||
.eq(SysConfig::getCfgKey, CFG_KEY_ROBOT_ID));
|
||||
|
||||
if (config == null || config.getCfgValue() == null || config.getCfgValue().isBlank()) {
|
||||
throw new RuntimeException("系统配置缺失: sys_config 中未配置 " + CFG_KEY_ROBOT_ID);
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(config.getCfgValue().trim());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RuntimeException("系统配置 " + CFG_KEY_ROBOT_ID + " 不是合法的用户ID: " + config.getCfgValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造群成员记录
|
||||
*/
|
||||
private GroupMember buildMember(Long groupId, Long userId, int role, LocalDateTime now) {
|
||||
GroupMember member = new GroupMember();
|
||||
member.setGroupId(groupId);
|
||||
member.setUserId(userId);
|
||||
member.setRole(role);
|
||||
member.setJoinTime(now);
|
||||
member.setCreateTime(now);
|
||||
member.setUpdateTime(now);
|
||||
return member;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉用户入群
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user