1
zj
2025-11-21 2ec5c14a87bce4cb6ccba7a1bd87708bcce055a9
ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java
@@ -1,36 +1,97 @@
package com.ruoyi.im.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.friend.FriendV2UrlContext;
import com.netease.nim.server.sdk.im.v2.team.TeamV2UrlContext;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.im.comm.Result;
import com.ruoyi.im.config.AppAuthConfig;
import com.ruoyi.im.config.*;
import com.ruoyi.im.dto.UpdateUserBusinessDto;
import com.ruoyi.im.service.NeteaseTeamService;
import com.ruoyi.imenum.ErrorCodeEnum;
import com.ruoyi.system.domain.GroupWelcomeConfig;
import com.ruoyi.system.domain.InvitationBlacklist;
import com.ruoyi.system.domain.NeteaseTeam;
import com.ruoyi.system.domain.UserAccount;
import com.ruoyi.im.service.ImApiServcie;
import com.ruoyi.im.dto.RegisterDto;
import com.ruoyi.system.domain.vo.UserAccountUpdateVo;
import com.ruoyi.system.mapper.NeteaseTeamMapper;
import com.ruoyi.system.service.GroupWelcomeConfigService;
import com.ruoyi.system.service.UserAccountService;
import com.ruoyi.im.util.SymmetricCryptoUtil;
import com.ruoyi.system.service.impl.InvitationBlacklistServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.poi.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ImApiServcieImpl implements ImApiServcie {
    @Autowired
    private UserAccountService userAccountService;
    @Autowired
    GroupWelcomeConfigService groupWelcomeConfigService;
    @Autowired
    NeteaseTeamService neteaseTeamService;
    // 使用单例客户端,避免重复初始化
    private YunxinApiHttpClient yunxinClient;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private NeteaseTeamMapper neteaseTeamMapper;
    @Autowired
    InvitationBlacklistServiceImpl invitationBlacklistService;
    @Resource
    private final YunxinApiHttpClient yunxinClient;
    // 使用构造函数注入(推荐)
    @Autowired
    public ImApiServcieImpl(YunxinApiHttpClient yunxinClient) {
        this.yunxinClient = yunxinClient;
    }
    // 使用并发安全的集合存储正在生成的账号,防止重复
    private final Set<Long> generatingAccounts = Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -39,80 +100,165 @@
    private final AtomicLong lastTimestamp = new AtomicLong(0);
    private final AtomicLong sequence = new AtomicLong(0);
    @PostConstruct
    public void init() {
        // 初始化云信客户端,只执行一次
        String appkey = AppAuthConfig.DEFAULT_CONFIG.getAppKey();
        String appsecret = AppAuthConfig.DEFAULT_CONFIG.getAppSecret();
        int timeoutMillis = 5000;
    private static final String DEFAULT_PASSWORD = "123456";//批量注册密码
    private static final String ENCRYPTED_PASSWORD = SymmetricCryptoUtil.encryptPassword(DEFAULT_PASSWORD); // 密码加密一次,多次使用
        this.yunxinClient = new YunxinApiHttpClient.Builder(BizName.IM, appkey, appsecret)
                .timeoutMillis(timeoutMillis)
                .build();
    }
    private static final String YUNXIN_CREATE_PATH = "/user/create.action";
    private static final String FRIENDS_PATH = "/im/v2.1/friends";
    @Value("${netease.im.api-head-portrait-url}")
    private String headPortraitUrl;
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result register(RegisterDto dto) {
        // 短信验证 TODO
        // 手机号是否存在 - 添加分布式锁或数据库唯一索引确保并发安全
        // 验证手机号是否已存在
        List<UserAccount> accounts = userAccountService.list(
                new LambdaQueryWrapper<>(UserAccount.class)
                        .eq(UserAccount::getAccount, dto.getAccount())
        );
        if(!CollectionUtils.isEmpty(accounts)){
            return Result.error("手机号已注册!");
        }
//        // 生成云信账号
//        long cloudMessageAccount;
//        try {
//            cloudMessageAccount = generateUniqueCloudMessageAccount();
//        } catch (RuntimeException e) {
//            return Result.error("系统繁忙,请稍后重试");
//        }
        // 注册云信
        String path = "/user/create.action";
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("accid", String.valueOf(dto.getAccount()));
        paramMap.put("token", dto.getPassword());
        YunxinApiResponse response;
        try {
            response = yunxinClient.executeV1Api(path, paramMap);
        } catch (YunxinSdkException e) {
            System.err.println("register error, traceId = " + e.getTraceId());
            return Result.error("注册失败,系统异常");
        }
        // 获取结果
        String data = response.getData();
        JSONObject json = JSONObject.parseObject(data);
        int code = json.getIntValue("code");
        if (code == 200) {
            // 注册成功
            UserAccount userAccount = new UserAccount();
            userAccount.setAccount(dto.getAccount());
            userAccount.setPhoneNumber(dto.getAccount());
            userAccount.setCloudMessageAccount(dto.getAccount());
            userAccount.setPassword(SymmetricCryptoUtil.encryptPassword(dto.getPassword()));
            try {
                userAccountService.save(userAccount);
                return Result.success("注册成功");
            } catch (Exception e) {
                // 处理数据库插入异常,可能需要回滚云信账号
                return Result.error("注册失败,请重试");
            }
        } else if (code == 414) {
        if (!CollectionUtils.isEmpty(accounts)) {
            return Result.error("账号已被注册!");
        } else {
            System.err.println("register fail, response = " + data + ", traceId=" + response.getTraceId());
            return Result.error("注册失败,错误码: " + code);
        }
        if(dto.getAccountType() == 0 && StringUtils.isEmpty(dto.getInvitationCode())){
            return Result.error("邀请码不能为空!");
        }
        long count = invitationBlacklistService.count(new LambdaQueryWrapper<InvitationBlacklist>()
                .eq(InvitationBlacklist::getInvitationCode, dto.getInvitationCode())
        );
        if(count > 0){
            return Result.error("邀请码已被限制邀请!");
        }
        String invitationCode = getInvitationCode();
        UserAccount user = new UserAccount();
        if(dto.getAccountType() == 0 && StringUtils.isNotEmpty(dto.getInvitationCode()) &&  !dto.getInvitationCode().equals("00000000")){
            user = userAccountService.getOne(new LambdaQueryWrapper<UserAccount>()
                    .eq(UserAccount::getInvitationCode, dto.getInvitationCode()).last(" limit 1"));
            if(ObjectUtil.isEmpty(user)){
                return Result.error("邀请码错误");
            }
        }
        // 创建本地用户账户记录
        UserAccount userAccount = new UserAccount();
        userAccount.setAccount(dto.getAccount());
        userAccount.setPhoneNumber(dto.getAccount());
        userAccount.setCloudMessageAccount(dto.getAccount());
        userAccount.setPassword(SymmetricCryptoUtil.encryptPassword(dto.getPassword()));
        userAccount.setCreateTime(new Date());
        userAccount.setNickname(dto.getNikeName());
        userAccount.setCreateTime(new Date());
        userAccount.setUpdateTime(new Date());
        userAccount.setInvitationCode(invitationCode);
        userAccount.setInvitationAccount(ObjectUtil.isNotEmpty(user.getAccount()) ? user.getAccount() : "");
        if (!userAccountService.save(userAccount)) {
            throw new RuntimeException("保存用户账户失败");
        }
        try {
            // 注册云信账号(远程调用)
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("accid", dto.getAccount());
            paramMap.put("token", dto.getPassword());
            if (StringUtils.isNotEmpty(dto.getNikeName())) {
                Map<String, String> userInfoMap = new HashMap<>();
                userInfoMap.put("name", dto.getNikeName());
                // 使用 JSON 工具将 Map 转为 JSON 字符串
                ObjectMapper objectMapper = new ObjectMapper();
                String userInfoJson = objectMapper.writeValueAsString(userInfoMap);
                paramMap.put("user_information", userInfoJson);
            }
            YunxinApiResponse response = yunxinClient.executeV1Api(YUNXIN_CREATE_PATH, paramMap);
            // 处理云信响应
            String data = response.getData();
            JSONObject json = JSONObject.parseObject(data);
            int code = json.getIntValue("code");
            if (code != 200) {
                String errorMsg = "";
                if(code == 102405){
                    errorMsg = "用户已存在";
                }
                log.error("-----------注册账号异常:"+ErrorCodeEnum.getByCode(code).getComment()+"----im信息:"+ErrorCodeEnum.getByCode(code).getDesc());
                throw new RuntimeException(errorMsg);
            }
            //修改昵称
            UpdateUserBusinessDto userBusinessDto = new UpdateUserBusinessDto();
            userBusinessDto.setName(dto.getNikeName());
            updateUserAvatar(dto.getAccount(),userBusinessDto);
            //默认添加邀请人为好友
            if(ObjectUtil.isNotEmpty(user)){
                addFriends(userAccount.getAccount(),user.getAccount());
            }
            // 注册成功后的其他操作
            GroupWelcomeConfig groupWelcomeConfig = groupWelcomeConfigService.getOne(new LambdaQueryWrapper<>(GroupWelcomeConfig.class)
                    .eq(GroupWelcomeConfig::getConfigurationName, "IM-BASICS").last(" limit 1"));
            NeteaseTeam neteaseTeam = neteaseTeamMapper.selectOne(new LambdaQueryWrapper<NeteaseTeam>().eq(NeteaseTeam::getTid,groupWelcomeConfig.getGroupId()));
            if(ObjectUtil.isNotEmpty(groupWelcomeConfig) &&  ObjectUtil.isNotEmpty(groupWelcomeConfig.getUserAccid())){
                addFriends(userAccount.getAccount(),groupWelcomeConfig.getUserAccid());
            }
            if(ObjectUtil.isNotEmpty(groupWelcomeConfig) &&  ObjectUtil.isNotEmpty(groupWelcomeConfig.getGroupId()) && ObjectUtil.isNotEmpty(neteaseTeam)){
                List<String> accountList = new ArrayList<>();
                accountList.add(userAccount.getAccount());
                AddTeamMembersRequest request = new AddTeamMembersRequest();
                request.setInviteAccountIds(accountList);
                request.setGroupId(ObjectUtil.isNotEmpty(neteaseTeam.getId().toString()) ? neteaseTeam.getId().toString() : null);
                neteaseTeamService.inviteTeamMembers(request);
            }
            return Result.success("注册成功");
        } catch (Exception e) {
            log.error("注册过程发生异常", e);
            // 将异常包装为Result并抛出RuntimeException触发回滚
            throw new RuntimeException(Result.error("注册失败: " + e.getMessage()).toString(), e);
        }
    }
    private String getInvitationCode() {
        String invitationCode = null;
        int maxAttempts = 100; // 最大尝试次数
        int attempts = 0;
        while (attempts < maxAttempts) {
            invitationCode = generateInvitationCode();
            long count = userAccountService.count(new LambdaQueryWrapper<UserAccount>()
                    .eq(UserAccount::getInvitationCode, invitationCode));
            if(count <= 0){
                break;
            }
            attempts++;
        }
        if (attempts >= maxAttempts) {
            log.error("生成邀请码已超最大尝试次数!");
            throw new RuntimeException("无法生成唯一的邀请码,请稍后重试");
        }
        return invitationCode;
    }
    /**
     * 生成邀请码
     * @return
     */
    public static String generateInvitationCode() {
        Random random = new Random();
        int code = 10000000 + random.nextInt(90000000);
        return String.valueOf(code);
    }
    /**
@@ -145,6 +291,7 @@
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(10, 50));
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
                throw new RuntimeException("生成账号被中断", e);
            }
@@ -177,4 +324,379 @@
        // 组合时间戳和序列号,生成9位ID
        return timestamp * 100 + seq;
    }
    /**
     * 更新用户名片
     * @param accountId 用户账号
     * @return 操作结果
     */
    public Map<String, Object> updateUserAvatar(String accountId, UpdateUserBusinessDto dto) {
        Map<String, Object> result = new HashMap<>();
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 生成请求参数
            String nonce = UUID.randomUUID().toString().replace("-", "");
            String curTime = String.valueOf(System.currentTimeMillis() / 1000);
            String checkSum = generateCheckSum(nonce, curTime);
            // 构建请求URL
            String url = headPortraitUrl + "/im/v2/users/" + accountId;
            // 构建请求头
            HttpPatch httpPatch = new HttpPatch(url);
            httpPatch.setHeader("Content-Type", "application/json;charset=utf-8");
            httpPatch.setHeader("AppKey", AppAuthConfig.DEFAULT_CONFIG.getAppKey());
            httpPatch.setHeader("Nonce", nonce);
            httpPatch.setHeader("CurTime", curTime);
            httpPatch.setHeader("CheckSum", checkSum);
            UpdateUserInfoRequest requestBody = new UpdateUserInfoRequest(dto.getAvatar(),
                    dto.getName(),dto.getSign(),dto.getEmail(),dto.getMobile(),dto.getGender());
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            httpPatch.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
            // 执行请求
            HttpResponse response = httpClient.execute(httpPatch);
            String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            // 解析响应
            NeteaseResponse neteaseResponse = objectMapper.readValue(responseString, NeteaseResponse.class);
            if (neteaseResponse.isSuccess()) {
                result.put("success", true);
                result.put("message", "更新成功");
                result.put("data", neteaseResponse.getData());
            } else {
                result.put("success", false);
                result.put("message", "更新失败: " + neteaseResponse.getMsg());
                result.put("errorCode", neteaseResponse.getCode());
            }
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "请求网易云信API失败: " + e.getMessage());
        }
        return result;
    }
    /**
     * 生成校验和
     */
    private String generateCheckSum(String nonce, String curTime) {
        String content = AppAuthConfig.DEFAULT_CONFIG.getAppSecret() + nonce + curTime;
        return DigestUtils.sha1Hex(content);
    }
    @Override
    public AjaxResult updateUserAccount(UserAccountUpdateVo vo) {
        //更新用户名片
//        UpdateUserBusinessDto dto = new UpdateUserBusinessDto();
//        if(StringUtils.isNotEmpty(vo.getPhoneNumber())){
//            dto.setMobile(vo.getPhoneNumber());
//        }
//        if(StringUtils.isNotEmpty(vo.getNickname())){
//            dto.setName(vo.getNickname());
//        }
//        if(StringUtils.isNotEmpty(vo.getSignature())){
//            dto.setSign(vo.getSignature());
//        }
//        if(ObjectUtil.isNotEmpty(vo.getGender())){
//            dto.setGender(vo.getGender());
//        }
//        Map<String, Object> map = updateUserAvatar(vo.getAccountId(), dto);
        //更新用户属性 状态 密码
//        if ((Boolean) map.get("success")) {
            AjaxResult ajaxResult = updateAccountProperties(vo.getAccount(), vo);
            if(ajaxResult.isSuccess()){
                UserAccount userAccount = userAccountService.getOne(new LambdaQueryWrapper<UserAccount>()
                        .eq(UserAccount::getAccount,vo.getAccount())
                );
                if (StringUtils.isNotBlank(vo.getPhoneNumber())) {
                    userAccount.setPhoneNumber(vo.getPhoneNumber());
                }
                if (StringUtils.isNotBlank(vo.getAccount())) {
                    userAccount.setAccount(vo.getAccount());
                }
                if (StringUtils.isNotBlank(vo.getNickname())) {
                    userAccount.setNickname(vo.getNickname());
                }
                if (StringUtils.isNotBlank(vo.getPassword())) {
                    userAccount.setPassword(SymmetricCryptoUtil.encryptPassword(vo.getPassword()));
                }
                if (StringUtils.isNotBlank(vo.getSignature())) {
                    userAccount.setSignature(vo.getSignature());
                }
                userAccount.setGroupPermissions(vo.getGroupPermissions());
                userAccount.setAddFriend(vo.getAddFriend());
                userAccount.setStatus(vo.getStatus());
                userAccount.setUpdateTime(new Date());
                userAccountService.updateById(userAccount);
            }else{
                return AjaxResult.error("更新用户属性失败!");
            }
//        } else {
//            return AjaxResult.error("更新用户名片失败!");
//        }
        return AjaxResult.success("更新成功!");
    }
    /**
     * 更新账号属性
     * @param accountId 用户账号ID
     * @return 操作结果
     */
    public AjaxResult updateAccountProperties(String accountId, UserAccountUpdateVo vo) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 生成请求参数
            String nonce = UUID.randomUUID().toString().replace("-", "");
            String curTime = String.valueOf(System.currentTimeMillis() / 1000);
            String checkSum = generateCheckSum(nonce, curTime);
            // 构建请求URL
            String url = headPortraitUrl + "/im/v2/accounts/" + accountId;
            // 构建请求头
            HttpPatch httpPatch = new HttpPatch(url);
            httpPatch.setHeader("Content-Type", "application/json;charset=utf-8");
            httpPatch.setHeader("AppKey", AppAuthConfig.DEFAULT_CONFIG.getAppKey());
            httpPatch.setHeader("Nonce", nonce);
            httpPatch.setHeader("CurTime", curTime);
            httpPatch.setHeader("CheckSum", checkSum);
            // 创建构建器实例
            DynamicRequestBodyBuilder builder = new DynamicRequestBodyBuilder();
            if(null != vo.getStatus() && vo.getStatus() == 1){
                builder.setEnabled(false);
                builder.setNeedKick(true);
            }else if(null != vo.getStatus() && vo.getStatus() == 0){
                builder.setEnabled(true);
            }else if(StringUtils.isNotEmpty(vo.getPassword())){
                builder.setToken(vo.getPassword());
            }
            // 只设置需要的字段
            String jsonBody = builder.build();
            httpPatch.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
            // 执行请求
            HttpResponse response = httpClient.execute(httpPatch);
            String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            // 解析响应
            NeteaseResponse neteaseResponse = objectMapper.readValue(responseString, NeteaseResponse.class);
            if (neteaseResponse.isSuccess()) {
                return AjaxResult.success("账号属性更新成功");
            } else {
                return AjaxResult.error("账号属性更新失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("请求网易云信API失败");
        }
    }
    /**
     * 批量注册
     * @param dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result batchRegister(RegisterDto dto) {
        dto.setAccountType(1);
        if(dto.getType() == 2){
            return register(dto);
        }else{
            return batchRegister(dto.getNumber());
        }
    }
    /**
     * 同步批量注册
     * 注意:大批量(如超过1000)可能会造成事务过长、数据库连接占用较久,请根据实际情况调整批次大小或考虑异步方式
     * @param count 要注册的账号数量
     * @return 批量注册结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result batchRegister(int count) {
        if (count <= 0) {
            return Result.error("注册数量必须大于0");
        }
        try {
            // 1. 生成批量账号数据
            List<UserAccount> accountsToSave = generateBatchAccounts(count);
            // 2. 批量插入数据库 (使用JDBC Batch,性能远高于MyBatis-Plus的saveBatch)
            batchInsertAccounts(accountsToSave);
            // 3. 批量注册云信账号 (并行处理)
            Result yunxinResult = batchRegisterYunxinAccounts(accountsToSave);
            if (yunxinResult.getCode() != 200) {
                // 云信注册失败,手动触发回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return yunxinResult;
            }
            return Result.success("成功批量注册 " + count + " 个账号",yunxinResult.getData());
        } catch (Exception e) {
            // 其他异常,触发回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("批量注册过程发生异常", e);
            return Result.error("批量注册失败: " + e.getMessage());
        }
    }
    /**
     * 生成批量的账号信息
     * @param count 需要生成的数量
     * @return 用户账号列表
     */
    private List<UserAccount> generateBatchAccounts(int count) {
        List<UserAccount> accounts = new ArrayList<>(count);
        Set<String> generatedAccounts = new HashSet<>(count); // 用于内存中去重
        Random random = new Random();
        String invitationCode = getInvitationCode();
        for (int i = 0; i < count; i++) {
            String account;
            do {
                // 生成13开头的11位随机手机号作为账号
                account = "13" + String.format("%09d", random.nextInt(1000000000));
            } while (generatedAccounts.contains(account)); // 确保本次批量中唯一
            generatedAccounts.add(account);
            UserAccount userAccount = new UserAccount();
            userAccount.setAccount(account);
            userAccount.setPhoneNumber(account);
            userAccount.setCloudMessageAccount(account);
            userAccount.setPassword(ENCRYPTED_PASSWORD); // 使用预加密的密码
            userAccount.setCreateTime(new Date());
            userAccount.setNickname("用户_" + account.substring(7)); // 简单生成昵称
            userAccount.setAccountType(1); // 设置账号类型为1
            userAccount.setInvitationCode(invitationCode);
            accounts.add(userAccount);
        }
        return accounts;
    }
    /**
     * 使用JDBC批量插入数据库,最高效的方式
     * @param accounts 待插入的用户账号列表
     */
    private void batchInsertAccounts(List<UserAccount> accounts) {
        String sql = "INSERT INTO user_account (account, phone_number, cloud_message_account, password, create_time, nickname, account_type) VALUES (?, ?, ?, ?, ?, ?, ?)";
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                UserAccount account = accounts.get(i);
                ps.setString(1, account.getAccount());
                ps.setString(2, account.getPhoneNumber());
                ps.setString(3, account.getCloudMessageAccount());
                ps.setString(4, account.getPassword());
                ps.setTimestamp(5, new java.sql.Timestamp(account.getCreateTime().getTime()));
                ps.setString(6, account.getNickname());
                ps.setInt(7, account.getAccountType());
            }
            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        });
    }
    /**
     * 批量注册云信账号(并行调用单个接口)
     * @param accounts 已存入本地数据库的账号列表
     * @return 注册结果
     */
    private Result batchRegisterYunxinAccounts(List<UserAccount> accounts) {
        // 使用并行流并行调用云信接口
        List<CompletableFuture<YunxinApiResponse>> futures = accounts.parallelStream()
                .map(account -> CompletableFuture.supplyAsync(() -> {
                    Map<String, String> paramMap = new HashMap<>();
                    paramMap.put("accid", account.getAccount());
                    paramMap.put("token", DEFAULT_PASSWORD); // 使用明文密码
                    // 调用云信接口
                    return yunxinClient.executeV1Api(YUNXIN_CREATE_PATH, paramMap);
                }))
                .collect(Collectors.toList());
        // 等待所有异步操作完成,并获取结果
        List<YunxinApiResponse> responses = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
        List<String> acccountList = new ArrayList<>();
        // 检查所有响应结果
        for (YunxinApiResponse response : responses) {
            String data = response.getData();
            JSONObject json = JSONObject.parseObject(data);
            int code = json.getIntValue("code");
            JSONObject info = json.getJSONObject("info");
            String accid = info.getString("accid");
            if (code != 200) {
                log.error("-----------云信账号注册失败:"+ ErrorCodeEnum.getByCode(code).getComment()+"----im信息:"+ErrorCodeEnum.getByCode(code).getDesc());
                return Result.error("云信注册失败:"+ErrorCodeEnum.getByCode(code).getComment());
            }
            acccountList.add(accid);
        }
        return Result.success(acccountList);
    }
    public void addFriends(String accountId,String userAccid){
        try {
            Map<String, String> queryParams = null;
            Map<String, Object> map = new HashMap<>();
            map.put("account_id",accountId);
            map.put("friend_account_id",userAccid);
            JSONObject jsonBody = JSONObject.parseObject(JSONObject.toJSONString(map));
            YunxinApiResponse apiResponse = yunxinClient.executeV2Api(
                    HttpMethod.POST,
                    FRIENDS_PATH,
                    FRIENDS_PATH,
                    queryParams,
                    jsonBody.toJSONString()
            );
            // 检查所有响应结果
            String data = apiResponse.getData();
            JSONObject json = JSONObject.parseObject(data);
            int code = json.getIntValue("code");
            if (code != 200) {
                log.error("云信账号注册添加默认好友失败");
            }
        } catch (YunxinSdkException e) {
            // 云信调用异常时回滚事务
            e.printStackTrace();
            log.error("网易云信创建用户加群服务调用异常 traceId:");
        } catch (Exception e) {
            // 其他异常同样回滚
            e.printStackTrace();
            log.error("创建用户加群过程发生未知异常", e);
        }
    }
}