fix 创建群

This commit is contained in:
wells
2026-08-08 17:07:24 +08:00
parent 1f93719ee5
commit aa1a9c9f23
6 changed files with 187 additions and 1 deletions
@@ -1,12 +1,15 @@
package com.tailbet.controller; package com.tailbet.controller;
import com.tailbet.model.dto.CreateGroupDTO;
import com.tailbet.model.dto.PointsDTO; import com.tailbet.model.dto.PointsDTO;
import com.tailbet.model.dto.PullGroupDTO; import com.tailbet.model.dto.PullGroupDTO;
import com.tailbet.model.entity.Group;
import com.tailbet.model.vo.R; import com.tailbet.model.vo.R;
import com.tailbet.model.vo.UserContext; import com.tailbet.model.vo.UserContext;
import com.tailbet.service.IPointsService; import com.tailbet.service.IPointsService;
import com.tailbet.service.IGroupService; import com.tailbet.service.IGroupService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*; 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 @Data
@TableName("group") @TableName("g_group")
public class Group { public class Group {
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
@@ -25,6 +25,18 @@ public interface IGroupService {
*/ */
List<Group> getUserGroups(Long userId); 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.Group;
import com.tailbet.model.entity.GroupMember; 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.GroupMapper;
import com.tailbet.mapper.GroupMemberMapper; import com.tailbet.mapper.GroupMemberMapper;
import com.tailbet.mapper.SysConfigMapper;
import com.tailbet.mapper.UserMapper; import com.tailbet.mapper.UserMapper;
import com.tailbet.openim.OpenIMApiClient;
import com.tailbet.service.IGroupService; import com.tailbet.service.IGroupService;
import com.tailbet.service.IAuditService; import com.tailbet.service.IAuditService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -24,10 +28,17 @@ import java.util.List;
@Service @Service
public class GroupServiceImpl implements IGroupService { public class GroupServiceImpl implements IGroupService {
/**
* 系统配置键:机器人用户ID
*/
private static final String CFG_KEY_ROBOT_ID = "robot_id";
private final GroupMapper groupMapper; private final GroupMapper groupMapper;
private final GroupMemberMapper groupMemberMapper; private final GroupMemberMapper groupMemberMapper;
private final UserMapper userMapper; private final UserMapper userMapper;
private final SysConfigMapper sysConfigMapper;
private final IAuditService auditService; private final IAuditService auditService;
private final OpenIMApiClient openIMApiClient;
/** /**
* 获取群信息 * 获取群信息
@@ -58,6 +69,117 @@ public class GroupServiceImpl implements IGroupService {
.eq(Group::getStatus, 1)); .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;
}
/** /**
* 拉用户入群 * 拉用户入群
*/ */
+1
View File
@@ -221,6 +221,7 @@ INSERT INTO sys_config (cfg_key, cfg_value, remark) VALUES
('fake_daily_active_max', '20', '每日活跃假人最大数量'), ('fake_daily_active_max', '20', '每日活跃假人最大数量'),
('win_profit_threshold', '500', '小赢利润阈值'), ('win_profit_threshold', '500', '小赢利润阈值'),
('lose_profit_threshold', '-500', '小输利润阈值'); ('lose_profit_threshold', '-500', '小输利润阈值');
INSERT INTO `guess_game`.`sys_config`(`id`, `cfg_key`, `cfg_value`, `remark`, `update_time`) VALUES (27, 'robot_id', '8', '机器人id', '2026-08-08 16:33:16');
-- red_packet 表增加预分配金额字段 -- red_packet 表增加预分配金额字段
ALTER TABLE red_packet ADD COLUMN share_amounts JSON COMMENT '预分配金额列表(JSON数组)'; ALTER TABLE red_packet ADD COLUMN share_amounts JSON COMMENT '预分配金额列表(JSON数组)';