ruoyi-admin/src/main/java/com/ruoyi/im/config/AddTeamMembersRequest.java
New file @@ -0,0 +1,62 @@ package com.ruoyi.im.config; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.sun.istack.internal.NotNull; import lombok.Builder; import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.util.List; @Data @Builder public class AddTeamMembersRequest { private String groupId;//群id /** * 邀请入群的成员账号ID列表,必填 */ @Size(min = 1, max = 200, message = "邀请账号数量必须在1-200之间") @JsonProperty("invite_account_ids") private List<@NotBlank(message = "账号ID不能为空") String> inviteAccountIds; /** * 操作者(邀请人)账号 ID,必填 */ @Size(min = 1, max = 32, message = "操作者账号长度必须在1-32之间") @JsonProperty("operator_id") private String operatorId; /** * 邀请入群的附言,长度上限150位字符,选填 */ @Size(max = 150, message = "附言长度不能超过150字符") private String msg; /** * 群组ID,创建群组成功时自动生成,必填 */ @Min(value = 1, message = "群组ID必须大于0") @JsonProperty("team_id") private Long teamId; /** * 自定义扩展字段,JSON格式,长度上限512位字符,选填 */ @Size(max = 512, message = "扩展字段长度不能超过512字符") private String extension; /** * 群组类型,必填 * 1:高级群,2:超大群 */ @Min(value = 1, message = "群组类型必须为1或2") @Max(value = 2, message = "群组类型必须为1或2") @JsonProperty("team_type") private Integer teamType; } ruoyi-admin/src/main/java/com/ruoyi/im/config/AssignmentRequest.java
New file @@ -0,0 +1,21 @@ package com.ruoyi.im.config; import lombok.Builder; import lombok.Data; /** * @program: ruoyiim * @description: * @create: 2025-08-21 18:30 **/ @Data @Builder public class AssignmentRequest { private String groupId;//群id private Long teamId;//群组ID,创建群组成功时自动生成,必填 private Integer team_type;//需要转让的群组类型。1:高级群。2:超大群。 private String new_owner_account_id;//新群主的 IM 账号 ID。 private Integer leave = 2;//转让群主后,原群主是否退出该群组。1:退出。2:不退出,成为普通成员。 } ruoyi-admin/src/main/java/com/ruoyi/im/config/CreateTeamRequest.java
@@ -9,23 +9,25 @@ @Data public class CreateTeamRequest { @NotBlank(message = "群主id为空") private String groupId; /** * 群主(创建者)的 IM 账号 ID */ @NotBlank(message = "群主账号不能为空") @Size(max = 32, message = "群主账号长度不能超过32个字符") private String owner_account_id; /** * 群组类型:1:高级群。2:超大群。 */ @NotNull(message = "群组类型不能为空") private Integer team_type; /** * 群组名称,长度上限 64 位字符 */ @NotBlank(message = "群组名称不能为空") @Size(max = 64, message = "群组名称长度不能超过64个字符") private String name; @@ -67,13 +69,11 @@ /** * 创建群组时邀请入群的成员列表 */ @NotNull(message = "邀请成员列表不能为空") private List<String> invite_account_ids; /** * 邀请入群的附言,长度上限 150 位字符 */ @NotBlank(message = "邀请消息不能为空") @Size(max = 150, message = "邀请消息长度不能超过150个字符") private String invite_msg; @@ -91,7 +91,6 @@ * 1:需要群主或管理员验证通过才能入群 * 2:不允许任何人申请入群 */ @NotNull(message = "入群验证方式不能为空") private Integer join_mode = 0; /** ruoyi-admin/src/main/java/com/ruoyi/im/service/NeteaseTeamService.java
@@ -2,15 +2,21 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.im.comm.Result; import com.ruoyi.im.config.AddTeamMembersRequest; import com.ruoyi.im.config.CreateTeamRequest; import com.ruoyi.im.dto.CreateGroupDto; import com.ruoyi.im.config.AssignmentRequest; import com.ruoyi.system.domain.NeteaseTeam; import org.springframework.transaction.annotation.Transactional; public interface NeteaseTeamService extends IService<NeteaseTeam> { AjaxResult createGroup(CreateTeamRequest request); AjaxResult updateCreate(CreateTeamRequest request); AjaxResult inviteTeamMembers(AddTeamMembersRequest request); AjaxResult assignment(AssignmentRequest request); AjaxResult dismiss(Long id); } ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/NeteaseTeamServiceImpl.java
@@ -1,30 +1,29 @@ package com.ruoyi.im.service.impl; import cn.hutool.core.util.ObjectUtil; import com.alibaba.druid.support.json.JSONUtils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.netease.nim.server.sdk.core.BizName; import com.netease.nim.server.sdk.core.YunxinApiHttpClient; import com.netease.nim.server.sdk.core.YunxinApiResponse; import com.netease.nim.server.sdk.core.exception.YunxinSdkException; import com.netease.nim.server.sdk.core.http.HttpMethod; import com.netease.nim.server.sdk.im.v2.team.TeamV2UrlContext; import com.netease.nim.server.sdk.im.v2.team_member.TeamMemberV2UrlContext; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.im.comm.Result; import com.ruoyi.im.config.AppAuthConfig; import com.ruoyi.im.config.AddTeamMembersRequest; import com.ruoyi.im.config.CreateTeamRequest; import com.ruoyi.im.dto.CreateGroupDto; import com.ruoyi.im.config.AssignmentRequest; import com.ruoyi.im.service.NeteaseTeamService; import com.ruoyi.system.domain.NeteaseTeam; import com.ruoyi.system.mapper.NeteaseTeamMapper; import org.aspectj.weaver.loadtime.Aj; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.*; @@ -103,6 +102,80 @@ } } @Override @Transactional(rollbackFor = Exception.class) public AjaxResult updateCreate(CreateTeamRequest request) { try { // 1. 构建请求参数Map Map<String, Object> paramMap = buildCreateTeamParams(request); // 2. 将Map转换为JSON字符串 String jsonBody = JSON.toJSONString(paramMap); NeteaseTeam neteaseTeam = neteaseTeamMapper.selectById(request.getGroupId()); if(ObjectUtil.isNotEmpty(neteaseTeam)){ return AjaxResult.error("群不存在!"); } String path = TeamV2UrlContext.UPDATE_TEAM.replace("{team_id}", String.valueOf(neteaseTeam.getTid())); YunxinApiResponse response = yunxinClient.executeV2Api( HttpMethod.PATCH, path, path, null, jsonBody ); // 4. 处理响应 String data = response.getData(); JSONObject json = JSONObject.parseObject(data); int code = json.getIntValue("code"); if (code != 200) { log.error("网易云信修改群组失败,响应:"); return AjaxResult.error("修改群组失败"); } // 5. 解析响应获取群组ID JSONObject resultData = json.getJSONObject("data"); JSONObject teamInfo = resultData.getJSONObject("team_info"); String tid = teamInfo.getString("team_id"); String ownerAccountId = teamInfo.getString("owner_account_id"); // 6. 跟新群组信息到本地数据库 int count = updateNeteaseTeam(request,neteaseTeam); if(count > 0){ return AjaxResult.success("修改群组成功"); } return AjaxResult.error("修改群组失败"); } catch (YunxinSdkException e) { // 云信调用异常时回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); log.error("网易云信服务调用异常 traceId:"); return AjaxResult.error("修改群组失败,云信服务异常"); } catch (Exception e) { // 其他异常同样回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); log.error("修改群组过程发生未知异常", e); return AjaxResult.error("修改群组失败,系统异常"); } } private int updateNeteaseTeam(CreateTeamRequest request, NeteaseTeam neteaseTeam) { neteaseTeam.setName(request.getName()); neteaseTeam.setMemberCount(request.getMembers_limit()); neteaseTeam.setAnnouncement(request.getAnnouncement()); neteaseTeam.setIntro(request.getIntro()); neteaseTeam.setJoinMode(request.getJoin_mode()); neteaseTeam.setAgreeMode(request.getAgree_mode()); neteaseTeam.setInviteMode(request.getInvite_mode()); neteaseTeam.setUpdateTeamInfoMode(request.getUpdate_team_info_mode()); return neteaseTeamMapper.updateById(neteaseTeam); } /** * 构建创建群组的参数Map */ @@ -178,4 +251,145 @@ return insert; } @Override public AjaxResult inviteTeamMembers(AddTeamMembersRequest request) { try { NeteaseTeam neteaseTeam = neteaseTeamMapper.selectById(request.getGroupId()); if(ObjectUtil.isNotEmpty(neteaseTeam)){ return AjaxResult.error("群不存在!"); } AddTeamMembersRequest build = AddTeamMembersRequest.builder() .inviteAccountIds(request.getInviteAccountIds()) .operatorId(request.getOperatorId()) .msg(request.getMsg()) .teamId(Long.getLong(neteaseTeam.getTid())) .teamType(request.getTeamType()) .build(); String jsonBody = JSONUtils.toJSONString(build); YunxinApiResponse response = yunxinClient.executeV2Api( HttpMethod.POST, TeamMemberV2UrlContext.INVITE_TEAM_MEMBERS, TeamMemberV2UrlContext.INVITE_TEAM_MEMBERS, null, jsonBody ); // 4. 处理响应 String data = response.getData(); JSONObject json = JSONObject.parseObject(data); int code = json.getIntValue("code"); if (code != 200) { log.error("网易云信拉人入群失败,响应:"); return AjaxResult.error("拉人入群失败"); } return AjaxResult.success(); } catch (YunxinSdkException e) { // 云信调用异常时回滚事务 e.printStackTrace(); log.error("网易云信服务调用异常 traceId:"); return AjaxResult.error("拉人入群失败,云信服务异常"); } catch (Exception e) { // 其他异常同样回滚 e.printStackTrace(); log.error("拉人入群过程发生未知异常", e); return AjaxResult.error("拉人入群失败,系统异常"); } } @Override public AjaxResult assignment(AssignmentRequest request) { try { NeteaseTeam neteaseTeam = neteaseTeamMapper.selectById(request.getGroupId()); if(ObjectUtil.isNotEmpty(neteaseTeam)){ return AjaxResult.error("群不存在!"); } AssignmentRequest build = AssignmentRequest.builder() .teamId(Long.getLong(neteaseTeam.getTid())) .team_type(neteaseTeam.getTeamType()) .leave(2) .build(); String requestBody = JSONUtils.toJSONString(build); String path = TeamV2UrlContext.TRANSFER_OWNER.replace("{team_id}",neteaseTeam.getTid().toString()); YunxinApiResponse response = yunxinClient.executeV2Api( HttpMethod.PATCH, TeamV2UrlContext.TRANSFER_OWNER, path, null, requestBody ); // 4. 处理响应 String data = response.getData(); JSONObject json = JSONObject.parseObject(data); int code = json.getIntValue("code"); if (code != 200) { log.error("网易云信转让群主失败,响应:"); return AjaxResult.error("转让群主失败"); } return AjaxResult.success(); } catch (YunxinSdkException e) { // 云信调用异常时回滚事务 e.printStackTrace(); log.error("网易云信转让群主服务调用异常 traceId:"); return AjaxResult.error("转让群主失败,云信服务异常"); } catch (Exception e) { // 其他异常同样回滚 e.printStackTrace(); log.error("转让群主过程发生未知异常", e); return AjaxResult.error("转让群主失败,系统异常"); } } @Override public AjaxResult dismiss(Long id) { try { NeteaseTeam neteaseTeam = neteaseTeamMapper.selectById(id); if(ObjectUtil.isNotEmpty(neteaseTeam)){ return AjaxResult.error("群不存在!"); } Map<String, String> queryParams = new HashMap<>(); queryParams.put("team_type", String.valueOf(neteaseTeam.getTeamType())); String path = TeamV2UrlContext.DISBAND_TEAM.replace("{team_id}", neteaseTeam.getTid()); YunxinApiResponse response = yunxinClient.executeV2Api( HttpMethod.DELETE, path, path, queryParams, null ); // 4. 处理响应 String data = response.getData(); JSONObject json = JSONObject.parseObject(data); int code = json.getIntValue("code"); if (code != 200) { log.error("网易云信解散群失败,响应:"); return AjaxResult.error("解散群失败"); } return AjaxResult.success(); } catch (YunxinSdkException e) { // 云信调用异常时回滚事务 e.printStackTrace(); log.error("网易云信解散群服务调用异常 traceId:"); return AjaxResult.error("解散群失败,云信服务异常"); } catch (Exception e) { // 其他异常同样回滚 e.printStackTrace(); log.error("解散群过程发生未知异常", e); return AjaxResult.error("解散群失败,系统异常"); } } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/group/ImGroupController.java
@@ -2,16 +2,14 @@ import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.im.config.AddTeamMembersRequest; import com.ruoyi.im.config.CreateTeamRequest; import com.ruoyi.im.dto.CreateGroupDto; import com.ruoyi.im.config.AssignmentRequest; import com.ruoyi.im.service.NeteaseTeamService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/im/group") @@ -30,4 +28,36 @@ return neteaseGroupService.createGroup(request); } /** * 更新群组 */ @PostMapping("/updateCreate") public AjaxResult updateCreate(@Validated @RequestBody CreateTeamRequest request) { return neteaseGroupService.updateCreate(request); } /** * 拉人进去 */ @PostMapping("/inviteTeamMembers") public AjaxResult inviteTeamMembers(@Validated @RequestBody AddTeamMembersRequest request) { return neteaseGroupService.inviteTeamMembers(request); } /** * 转让群主 */ @PostMapping("/assignment") public AjaxResult assignment(@Validated @RequestBody AssignmentRequest request) { return neteaseGroupService.assignment(request); } /** * 解散 */ @PostMapping("/dismiss") public AjaxResult dismiss(@PathVariable("id") Long id) { return neteaseGroupService.dismiss(id); } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/BasicSetupController.java
New file @@ -0,0 +1,86 @@ package com.ruoyi.web.controller.system; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.im.config.CreateTeamRequest; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.IpBlacklist; import com.ruoyi.system.service.GroupWelcomeConfigService; import com.ruoyi.system.service.IpBlacklistService; import org.aspectj.weaver.loadtime.Aj; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.List; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:12 **/ @RestController @RequestMapping("/system/basic") public class BasicSetupController { @Autowired GroupWelcomeConfigService groupWelcomeConfigService; @Autowired IpBlacklistService ipBlacklistService; /** * 创建群组 */ @PostMapping("/update") public AjaxResult createGroup(@Validated @RequestBody GroupWelcomeConfig vo) { GroupWelcomeConfig configServiceById = groupWelcomeConfigService.getById(vo.getId()); if(ObjectUtil.isEmpty(configServiceById)){ return AjaxResult.error("请联系管理员初始化配置"); } configServiceById.setWebsiteName(vo.getWebsiteName()); configServiceById.setGroupId(vo.getGroupId()); configServiceById.setUserAccid(vo.getUserAccid()); configServiceById.setWelcomeMessage(vo.getWelcomeMessage()); configServiceById.setLegalInstitution(vo.getLegalInstitution()); configServiceById.setCopyrightInfo(vo.getCopyrightInfo()); groupWelcomeConfigService.updateById(configServiceById); return AjaxResult.success("保存成功!"); } /** * 新增ip黑名单 */ @PostMapping("/addIp") public AjaxResult addIp(@PathVariable("ip") String ip) { long count = ipBlacklistService.count(new LambdaQueryWrapper<>(IpBlacklist.class).eq(IpBlacklist::getIpAddress, ip)); if(count > 0){ return AjaxResult.error("ip已存在!"); } IpBlacklist ipBlacklist = new IpBlacklist(); ipBlacklist.setIpAddress(ip); ipBlacklist.setCreateTime(new Date()); ipBlacklistService.save(ipBlacklist); return AjaxResult.success("添加成功"); } /** * 新增ip黑名单 */ @PostMapping("/deleteIp") public AjaxResult addIp(@PathVariable("id") Integer id) { IpBlacklist ipBlacklist = ipBlacklistService.getById(id); if(ObjectUtil.isEmpty(ipBlacklist)){ return AjaxResult.error("ip地址不存在!"); } ipBlacklistService.removeById(id); return AjaxResult.success("添加成功"); } } ruoyi-system/src/main/java/com/ruoyi/system/domain/GroupWelcomeConfig.java
New file @@ -0,0 +1,66 @@ package com.ruoyi.system.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:06 **/ @Data public class GroupWelcomeConfig { /** * 主键ID */ @TableId(type = IdType.AUTO) private Long id; /** * 网站名称 */ private String websiteName; /** * 群组ID */ private String groupId; /** * 用户Accid */ private String userAccid; /** * 欢迎消息 */ private String welcomeMessage; /** * 法律机构 */ private String legalInstitution; /** * 版权信息 */ private String copyrightInfo; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; } ruoyi-system/src/main/java/com/ruoyi/system/domain/IpBlacklist.java
New file @@ -0,0 +1,34 @@ package com.ruoyi.system.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:08 **/ @Data public class IpBlacklist { /** * 主键ID */ @TableId(type = IdType.AUTO) private Long id; /** * IP地址 */ private String ipAddress; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; } ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/GroupWelcomeConfigVo.java
New file @@ -0,0 +1,15 @@ package com.ruoyi.system.domain.vo; import lombok.Data; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:17 **/ @Data public class GroupWelcomeConfigVo { } ruoyi-system/src/main/java/com/ruoyi/system/mapper/GroupWelcomeConfigMapper.java
New file @@ -0,0 +1,15 @@ package com.ruoyi.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.NeteaseTeam; import org.apache.ibatis.annotations.Mapper; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:07 **/ @Mapper public interface GroupWelcomeConfigMapper extends BaseMapper<GroupWelcomeConfig> { } ruoyi-system/src/main/java/com/ruoyi/system/mapper/IpBlacklistMapper.java
New file @@ -0,0 +1,15 @@ package com.ruoyi.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.IpBlacklist; import org.apache.ibatis.annotations.Mapper; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:09 **/ @Mapper public interface IpBlacklistMapper extends BaseMapper<IpBlacklist> { } ruoyi-system/src/main/java/com/ruoyi/system/service/GroupWelcomeConfigService.java
New file @@ -0,0 +1,8 @@ package com.ruoyi.system.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.UserAccount; public interface GroupWelcomeConfigService extends IService<GroupWelcomeConfig> { } ruoyi-system/src/main/java/com/ruoyi/system/service/IpBlacklistService.java
New file @@ -0,0 +1,8 @@ package com.ruoyi.system.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.IpBlacklist; public interface IpBlacklistService extends IService<IpBlacklist> { } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GroupWelcomeConfigServiceImpl.java
New file @@ -0,0 +1,19 @@ package com.ruoyi.system.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.UserAccount; import com.ruoyi.system.mapper.GroupWelcomeConfigMapper; import com.ruoyi.system.mapper.UserAccountMapper; import com.ruoyi.system.service.GroupWelcomeConfigService; import com.ruoyi.system.service.UserAccountService; import org.springframework.stereotype.Service; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:07 **/ @Service public class GroupWelcomeConfigServiceImpl extends ServiceImpl<GroupWelcomeConfigMapper, GroupWelcomeConfig> implements GroupWelcomeConfigService { } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/IpBlacklistServiceImpl.java
New file @@ -0,0 +1,19 @@ package com.ruoyi.system.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.system.domain.GroupWelcomeConfig; import com.ruoyi.system.domain.IpBlacklist; import com.ruoyi.system.mapper.GroupWelcomeConfigMapper; import com.ruoyi.system.mapper.IpBlacklistMapper; import com.ruoyi.system.service.GroupWelcomeConfigService; import com.ruoyi.system.service.IpBlacklistService; import org.springframework.stereotype.Service; /** * @program: ruoyiim * @description: * @create: 2025-08-21 19:10 **/ @Service public class IpBlacklistServiceImpl extends ServiceImpl<IpBlacklistMapper, IpBlacklist> implements IpBlacklistService { }