1
zj
2025-10-10 080dd1f1de81758b946c01e6db71938c008e9427
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
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.*;
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;
 
    @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<>());
 
    // 使用原子计数器优化账号生成
    private final AtomicLong lastTimestamp = new AtomicLong(0);
    private final AtomicLong sequence = new AtomicLong(0);
 
    private static final String DEFAULT_PASSWORD = "123456";//批量注册密码
    private static final String ENCRYPTED_PASSWORD = SymmetricCryptoUtil.encryptPassword(DEFAULT_PASSWORD); // 密码加密一次,多次使用
 
    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) {
        // 验证手机号是否已存在
        List<UserAccount> accounts = userAccountService.list(
                new LambdaQueryWrapper<>(UserAccount.class)
                        .eq(UserAccount::getAccount, dto.getAccount())
        );
 
        if (!CollectionUtils.isEmpty(accounts)) {
            return Result.error("账号已被注册!");
        }
 
        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);
            }
            //默认添加邀请人为好友
            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);
    }
 
    /**
     * 优化的账号生成方法,使用雪花算法变体提高并发性能
     */
    public long generateUniqueCloudMessageAccount() {
        final int maxAttempts = 10; // 减少尝试次数,提高效率
 
        for (int attempt = 0; attempt < maxAttempts; attempt++) {
            long account = generateSnowflakeLikeId();
 
            // 使用并发集合检查是否正在生成相同账号
            if (generatingAccounts.add(account)) {
                try {
                    // 检查数据库是否存在
                    boolean exists = userAccountService.lambdaQuery()
                            .eq(UserAccount::getCloudMessageAccount, account)
                            .exists();
 
                    if (!exists) {
                        return account;
                    }
                } finally {
                    // 无论成功与否,都从集合中移除
                    generatingAccounts.remove(account);
                }
            }
 
            // 短暂等待后重试
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(10, 50));
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
                throw new RuntimeException("生成账号被中断", e);
            }
        }
 
        throw new RuntimeException("无法在" + maxAttempts + "次尝试内生成唯一账号");
    }
 
    /**
     * 基于雪花算法的ID生成器,提高并发性能
     */
    private long generateSnowflakeLikeId() {
        long currentTime = System.currentTimeMillis();
        long timestamp = currentTime % 100_000_000L; // 取时间戳的后8位
 
        // 使用原子操作确保序列号递增
        long seq;
        long lastTime;
        do {
            lastTime = lastTimestamp.get();
            if (currentTime == lastTime) {
                seq = sequence.incrementAndGet() % 100;
            } else {
                sequence.set(0);
                lastTimestamp.set(currentTime);
                seq = 0;
            }
        } while (!lastTimestamp.compareAndSet(lastTime, currentTime));
 
        // 组合时间戳和序列号,生成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(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);
        }
    }
 
 
 
}