fix 创建群
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拉用户入群
|
* 拉用户入群
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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数组)';
|
||||||
|
|||||||
Reference in New Issue
Block a user