1
zj
2025-08-22 5cb579d8b09978f6fefd829fb6220fcf4391cd2f
1
8 files modified
3 files added
763 ■■■■■ changed files
ruoyi-admin/src/main/java/com/ruoyi/im/config/AddTeamMembersRequest.java 1 ●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/config/UpdateUserInfoRequest.java 274 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java 4 ●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/out/UserAccountOut.java 44 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java 107 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/NeteaseTeamServiceImpl.java 1 ●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/util/ConverterUtil.java 45 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/util/PhoneNumberValidatorUtil.java 208 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/web/controller/group/ImGroupController.java 37 ●●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/web/controller/user/UserController.java 38 ●●●● patch | view | raw | blame | history
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/UserAccountVo.java 4 ●●●● patch | view | raw | blame | history
ruoyi-admin/src/main/java/com/ruoyi/im/config/AddTeamMembersRequest.java
@@ -2,7 +2,6 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.sun.istack.internal.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
ruoyi-admin/src/main/java/com/ruoyi/im/config/UpdateUserInfoRequest.java
@@ -1,207 +1,111 @@
package com.ruoyi.im.config;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
public class UpdateUserInfoRequest {
    private Map<String, Object> bodyData;
    @JsonProperty("avatar")
    private String avatar;
    public UpdateUserInfoRequest() {
        bodyData = new HashMap<>();
    @JsonProperty("name")
    private String name;
    @JsonProperty("sign")
    private String sign;
    @JsonProperty("email")
    private String email;
    @JsonProperty("mobile")
    private String mobile;
    // 无参构造函数
    public UpdateUserInfoRequest() {}
    // 全参构造函数(可选)
    public UpdateUserInfoRequest(String avatar, String name, String sign, String email, String mobile) {
        this.avatar = avatar;
        this.name = name;
        this.sign = sign;
        this.email = email;
        this.mobile = mobile;
    }
    /**
     * 设置name字段
     */
    public UpdateUserInfoRequest setName(String name) {
        if (name != null) {
            bodyData.put("name", name);
        }
    // Getter 和 Setter 方法
    public String getAvatar() {
        return avatar;
    }
    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSign() {
        return sign;
    }
    public void setSign(String sign) {
        this.sign = sign;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
    // toString 方法(可选,便于调试)
    @Override
    public String toString() {
        return "UpdateUserInfoRequest{" +
                "avatar='" + avatar + '\'' +
                ", name='" + name + '\'' +
                ", sign='" + sign + '\'' +
                ", email='" + email + '\'' +
                ", mobile=" + mobile +
                '}';
    }
    // Builder 模式(可选,便于链式调用)
    public UpdateUserInfoRequest avatar(String avatar) {
        this.avatar = avatar;
        return this;
    }
    /**
     * 设置avatar字段
     */
    public UpdateUserInfoRequest setAvatar(String avatar) {
        if (avatar != null) {
            bodyData.put("avatar", avatar);
        }
    public UpdateUserInfoRequest name(String name) {
        this.name = name;
        return this;
    }
    /**
     * 设置sign字段
     */
    public UpdateUserInfoRequest setSign(String sign) {
        if (sign != null) {
            bodyData.put("sign", sign);
        }
    public UpdateUserInfoRequest sign(String sign) {
        this.sign = sign;
        return this;
    }
    /**
     * 设置email字段
     */
    public UpdateUserInfoRequest setEmail(String email) {
        if (email != null) {
            bodyData.put("email", email);
        }
    public UpdateUserInfoRequest email(String email) {
        this.email = email;
        return this;
    }
    /**
     * 设置birthday字段
     */
    public UpdateUserInfoRequest setBirthday(String birthday) {
        if (birthday != null) {
            bodyData.put("birthday", birthday);
        }
    public UpdateUserInfoRequest mobile(String mobile) {
        this.mobile = mobile;
        return this;
    }
    /**
     * 设置mobile字段
     */
    public UpdateUserInfoRequest setMobile(String mobile) {
        if (mobile != null) {
            bodyData.put("mobile", mobile);
        }
        return this;
    }
    /**
     * 设置gender字段
     */
    public UpdateUserInfoRequest setGender(String gender) {
        if (gender != null) {
            bodyData.put("gender", gender);
        }
        return this;
    }
    /**
     * 设置extension字段
     */
    public UpdateUserInfoRequest setExtension(String extension) {
        if (extension != null) {
            bodyData.put("extension", extension);
        }
        return this;
    }
    /**
     * 批量设置所有字段
     */
    public UpdateUserInfoRequest setAllFields(Map<String, Object> fields) {
        if (fields != null) {
            for (Map.Entry<String, Object> entry : fields.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value != null) {
                    switch (key) {
                        case "name":
                            setName((String) value);
                            break;
                        case "avatar":
                            setAvatar((String) value);
                            break;
                        case "sign":
                            setSign((String) value);
                            break;
                        case "email":
                            setEmail((String) value);
                            break;
                        case "birthday":
                            setBirthday((String) value);
                            break;
                        case "mobile":
                            setMobile((String) value);
                            break;
                        case "gender":
                            setGender((String) value);
                            break;
                        case "extension":
                            setExtension((String) value);
                            break;
                        default:
                            // 忽略未知字段
                            break;
                    }
                }
            }
        }
        return this;
    }
    /**
     * 构建JSON字符串
     */
    public String build() {
        JSONObject jsonObject = new JSONObject(bodyData);
        return jsonObject.toString();
    }
    /**
     * 获取Map形式的数据
     */
    public Map<String, Object> getBodyData() {
        return new HashMap<>(bodyData);
    }
    /**
     * 清空所有已设置的数据
     */
    public void clear() {
        bodyData.clear();
    }
    /**
     * 使用示例
     */
    public static void main(String[] args) {
        // 示例1: 只设置部分字段
        UpdateUserInfoRequest builder = new UpdateUserInfoRequest();
        String partialBody = builder
                .setName("zhangsan")
                .setEmail("zhangsan@corp.xx.com")
                .setMobile("13312345678")
                .build();
        System.out.println("部分字段请求体: " + partialBody);
        // 示例2: 使用Map批量设置
        builder.clear();
        Map<String, Object> fields = new HashMap<>();
        fields.put("name", "lisi");
        fields.put("avatar", "http://xxxx.xx/lisi.png");
        fields.put("sign", "Hello World");
        fields.put("gender", "1");
        String batchBody = builder.setAllFields(fields).build();
        System.out.println("批量设置请求体: " + batchBody);
        // 示例3: 完全不设置任何字段
        builder.clear();
        String emptyBody = builder.build();
        System.out.println("空请求体: " + emptyBody);
        // 示例4: 设置所有字段
        builder.clear();
        String fullBody = builder
                .setName("zhangsan")
                .setAvatar("http://xxxx.xx/x.png")
                .setSign("Hello World")
                .setEmail("zhangsan@corp.xx.com")
                .setBirthday("1990-01-01")
                .setMobile("13312345678")
                .setGender("2")
                .setExtension("xxxx")
                .build();
        System.out.println("完整请求体: " + fullBody);
    }
}
}
ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java
@@ -9,15 +9,13 @@
@Data
public class RegisterDto {
    @NotNull(message = "账号不能为空")
    private String account; // 账号
    @NotEmpty(message = "密码不能为空")
    private String password; // 密码
    private String confirmPassword; // 再次确认密码
    private String name; // 昵称
    private String nickname; // 昵称
    private Integer type;//类型 1 批量  2 单一
ruoyi-admin/src/main/java/com/ruoyi/im/out/UserAccountOut.java
New file
@@ -0,0 +1,44 @@
package com.ruoyi.im.out;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class UserAccountOut {
    // 账号(唯一)
    private String account;
    // 手机号(唯一)
    private String phoneNumber;
    // 云信账号
    private String cloudMessageAccount;
    // 昵称
    private String nickname;
    // 账号状态: 0:正常 1:禁用
    private Integer status;
    // 最近登录时间
    private Date loginTime;
    // 最近登录IP
    private String loginIp;
    // 注册时间
    private Date registerTime;
    // 个性签名
    private String signature;
}
ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java
@@ -72,6 +72,7 @@
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Resource
    private final YunxinApiHttpClient yunxinClient;
    // 使用构造函数注入(推荐)
@@ -102,17 +103,17 @@
    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务注解确保操作原子性
    @Transactional(rollbackFor = Exception.class)
    public Result register(RegisterDto dto) {
        try {
            // 验证手机号是否已存在(数据库唯一索引提供最终保障)
            // 验证手机号是否已存在
            List<UserAccount> accounts = userAccountService.list(
                    new LambdaQueryWrapper<>(UserAccount.class)
                            .eq(UserAccount::getAccount, dto.getAccount())
            );
            if (!CollectionUtils.isEmpty(accounts)) {
                return Result.error("手机号已注册!");
                return Result.error("账号已被注册!");
            }
            // 创建本地用户账户记录
@@ -122,18 +123,17 @@
            userAccount.setCloudMessageAccount(dto.getAccount());
            userAccount.setPassword(SymmetricCryptoUtil.encryptPassword(dto.getPassword()));
            userAccount.setCreateTime(new Date());
            userAccount.setNickname(dto.getAccount());
            userAccount.setNickname(dto.getNickname());
            if (!userAccountService.save(userAccount)) {
                return Result.error("注册失败,请重试");
                throw new RuntimeException("保存用户账户失败"); // 改为抛出异常
            }
            // 注册云信账号(远程调用)
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("accid", dto.getAccount());
            if(StringUtils.isNotEmpty(dto.getName())){
                paramMap.put("name", dto.getName());
            if(StringUtils.isNotEmpty(dto.getNickname())){
                paramMap.put("name", dto.getNickname());
            }
            paramMap.put("token", dto.getPassword());
@@ -159,26 +159,15 @@
                    neteaseTeamService.inviteTeamMembers(request);
                }
                return Result.success("注册成功");
            } else if (code == 414) {
                // 手动触发回滚(事务注解会自动处理)
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return Result.error("账号已被注册!");
            } else {
                // 其他错误码同样触发回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                log.error("云信注册失败,响应: {}, traceId: {}", data, response.getTraceId());
                return Result.error("注册失败,错误码: " + code);
                // 所有非200状态码都抛出异常触发回滚
                throw new RuntimeException("云信注册失败,错误码: " + code + ", 响应: " + data);
            }
        } catch (YunxinSdkException e) {
            // 云信调用异常时回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("云信服务调用异常 traceId: {}", e.getTraceId(), e);
            return Result.error("注册失败,系统异常");
        } catch (Exception e) {
            // 其他异常同样回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("注册过程发生未知异常", e);
            return Result.error("注册失败,请重试");
            // 记录异常日志
            log.error("注册过程发生异常", e);
            // 重新抛出异常以确保事务回滚
            throw new RuntimeException("注册失败: " + e.getMessage(), e);
        }
    }
@@ -275,18 +264,24 @@
            httpPatch.setHeader("CheckSum", checkSum);
            // 构建请求体
            UpdateUserInfoRequest builder = new UpdateUserInfoRequest();
            if(StringUtils.isNotEmpty(dto.getMobile())){
                builder.setMobile(dto.getMobile());
            }else if(StringUtils.isNotEmpty(dto.getName())){
                builder.setName(dto.getName());
            }else if(StringUtils.isNotEmpty(dto.getSign())){
                builder.setSign(dto.getSign());
            }else if(StringUtils.isNotEmpty(dto.getAvatar())){
                builder.setAvatar(dto.getAvatar());
            }
            String body = builder.build();
            String jsonBody = objectMapper.writeValueAsString(body);
//            UpdateUserInfoRequest builder = new UpdateUserInfoRequest();
//            if(StringUtils.isNotEmpty(dto.getMobile())){
//                builder.setMobile(dto.getMobile());
//            }
//            if(StringUtils.isNotEmpty(dto.getName())){
//                builder.setName(dto.getName());
//            }
//            if(StringUtils.isNotEmpty(dto.getSign())){
//                builder.setSign(dto.getSign());
//            }
//            if(StringUtils.isNotEmpty(dto.getAvatar())){
//                builder.setAvatar(dto.getAvatar());
//            }
//            String body = builder.build();
            UpdateUserInfoRequest requestBody = new UpdateUserInfoRequest(dto.getAvatar(),
                    dto.getName(),dto.getSign(),dto.getEmail(),dto.getMobile());
            String jsonBody = objectMapper.writeValueAsString(requestBody);
//            String jsonBody = objectMapper.writeValueAsString(body);
            httpPatch.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
            // 执行请求
@@ -298,11 +293,11 @@
            if (neteaseResponse.isSuccess()) {
                result.put("success", true);
                result.put("message", "头像更新成功");
                result.put("message", "更新成功");
                result.put("data", neteaseResponse.getData());
            } else {
                result.put("success", false);
                result.put("message", "头像更新失败: " + neteaseResponse.getMsg());
                result.put("message", "更新失败: " + neteaseResponse.getMsg());
                result.put("errorCode", neteaseResponse.getCode());
            }
@@ -332,9 +327,11 @@
        UpdateUserBusinessDto dto = new UpdateUserBusinessDto();
        if(StringUtils.isNotEmpty(vo.getPhoneNumber())){
            dto.setMobile(vo.getPhoneNumber());
        }else if(StringUtils.isNotEmpty(vo.getNickname())){
        }
        if(StringUtils.isNotEmpty(vo.getNickname())){
            dto.setName(vo.getNickname());
        }else if(StringUtils.isNotEmpty(vo.getSignature())){
        }
        if(StringUtils.isNotEmpty(vo.getSignature())){
            dto.setSign(vo.getSignature());
        }
        Map<String, Object> map = updateUserAvatar(vo.getAccountId(), dto);
@@ -343,11 +340,25 @@
            AjaxResult ajaxResult = updateAccountProperties(vo.getAccountId(), vo);
            if(ajaxResult.isSuccess()){
                UserAccount userAccount = userAccountService.getById(vo.getId());
                userAccount.setPhoneNumber(vo.getPhoneNumber());
                userAccount.setAccount(vo.getAccountId());
                userAccount.setNickname(vo.getNickname());
                userAccount.setPassword(SymmetricCryptoUtil.encryptPassword(vo.getPassword()));
                userAccount.setSignature(vo.getSignature());
                if (StringUtils.isNotBlank(vo.getPhoneNumber())) {
                    userAccount.setPhoneNumber(vo.getPhoneNumber());
                }
                if (StringUtils.isNotBlank(vo.getAccountId())) {
                    userAccount.setAccount(vo.getAccountId());
                }
                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.setUpdateTime(new Date());
                userAccountService.updateById(userAccount);
            }else{
@@ -391,7 +402,8 @@
            if(null != vo.getStatus() && vo.getStatus() == 0){
                builder.setEnabled(false);
                builder.setNeedKick(true);
            }else if(StringUtils.isNotEmpty(vo.getPassword())){
            }
            if(StringUtils.isNotEmpty(vo.getPassword())){
                builder.setToken(vo.getPassword());
            }
            // 只设置需要的字段
@@ -427,6 +439,7 @@
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result batchRegister(RegisterDto dto) {
        if(dto.getType() == 2){
            return register(dto);
ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/NeteaseTeamServiceImpl.java
@@ -30,6 +30,7 @@
@Service
public class NeteaseTeamServiceImpl extends ServiceImpl<NeteaseTeamMapper, NeteaseTeam> implements NeteaseTeamService {
    @Resource
    private final YunxinApiHttpClient yunxinClient;
    // 使用构造函数注入(推荐)
ruoyi-admin/src/main/java/com/ruoyi/im/util/ConverterUtil.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.im.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
 * @program: dabaogp
 * @description:
 * @create: 2024-08-19 18:29
 **/
public class ConverterUtil {
    public static <T, V> V convert(T pojo, Class<V> voClass) {
        try {
            V vo = voClass.newInstance();
            Field[] pojoFields = pojo.getClass().getDeclaredFields();
            Field[] voFields = voClass.getDeclaredFields();
            for (Field pojoField : pojoFields) {
                pojoField.setAccessible(true);
                for (Field voField : voFields) {
                    voField.setAccessible(true);
                    if (pojoField.getName().equals(voField.getName()) && pojoField.getType().equals(voField.getType())) {
                        voField.set(vo, pojoField.get(pojo));
                        break;
                    }
                }
            }
            return vo;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
    public static <T, V> List<V> convertToList(List<T> pojoList, Class<V> voClass) {
        List<V> voList = new ArrayList<>();
        for (T pojo : pojoList) {
            V vo = convert(pojo, voClass);
            voList.add(vo);
        }
        return voList;
    }
}
ruoyi-admin/src/main/java/com/ruoyi/im/util/PhoneNumberValidatorUtil.java
New file
@@ -0,0 +1,208 @@
package com.ruoyi.im.util;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.HashMap;
import java.util.Map;
/**
 * 手机号格式校验工具类
 * 支持中国大陆手机号和国际手机号验证
 */
public class PhoneNumberValidatorUtil {
    // 中国大陆手机号正则表达式 (11位数字,1开头)
    private static final String CHINA_MOBILE_PATTERN = "^1[3-9]\\d{9}$";
    // 国际手机号正则表达式 (国家代码-号码,如+1-1234567890)
    private static final String INTERNATIONAL_PATTERN = "^\\+\\d{1,4}-\\d{4,15}$";
    // 常见国家/地区代码映射表
    private static final Map<String, String> COUNTRY_CODES = new HashMap<>();
    static {
        // 初始化常见国家/地区代码
        COUNTRY_CODES.put("1", "美国/加拿大");
        COUNTRY_CODES.put("86", "中国大陆");
        COUNTRY_CODES.put("852", "香港");
        COUNTRY_CODES.put("853", "澳门");
        COUNTRY_CODES.put("886", "台湾");
        COUNTRY_CODES.put("81", "日本");
        COUNTRY_CODES.put("82", "韩国");
        COUNTRY_CODES.put("65", "新加坡");
        COUNTRY_CODES.put("60", "马来西亚");
        COUNTRY_CODES.put("44", "英国");
        COUNTRY_CODES.put("33", "法国");
        COUNTRY_CODES.put("49", "德国");
        COUNTRY_CODES.put("61", "澳大利亚");
        // 可以继续添加更多国家/地区代码
    }
    /**
     * 验证手机号格式
     * @param phoneNumber 手机号码
     * @return 验证结果
     */
    public static ValidationResult validatePhoneNumber(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
            return new ValidationResult(false, "手机号不能为空");
        }
        // 去除可能存在的空格
        phoneNumber = phoneNumber.trim();
        // 检查是否为中国大陆手机号
        if (Pattern.matches(CHINA_MOBILE_PATTERN, phoneNumber)) {
            return new ValidationResult(true, "中国大陆手机号格式正确");
        }
        // 检查是否为国际手机号格式
        if (Pattern.matches(INTERNATIONAL_PATTERN, phoneNumber)) {
            // 提取国家代码部分
            String[] parts = phoneNumber.split("-", 2);
            String countryCode = parts[0].substring(1); // 去掉+号
            // 验证国家代码是否有效
            if (COUNTRY_CODES.containsKey(countryCode)) {
                return new ValidationResult(true, "国际手机号格式正确 (" + COUNTRY_CODES.get(countryCode) + ")");
            } else {
                return new ValidationResult(false, "不支持的国家/地区代码: +" + countryCode);
            }
        }
        // 如果都不匹配,返回错误信息
        if (phoneNumber.startsWith("+")) {
            return new ValidationResult(false, "国际手机号格式不正确,请使用'+国家代码-本地号码'格式");
        } else {
            return new ValidationResult(false, "中国大陆手机号应为11位数字且以1开头");
        }
    }
    /**
     * 验证结果类
     */
    public static class ValidationResult {
        private final boolean valid;
        private final String message;
        public ValidationResult(boolean valid, String message) {
            this.valid = valid;
            this.message = message;
        }
        public boolean isValid() {
            return valid;
        }
        public String getMessage() {
            return message;
        }
        @Override
        public String toString() {
            return message;
        }
    }
    /**
     * 提取国家代码(如果是国际手机号)
     * @param phoneNumber 手机号码
     * @return 国家代码(不带+号),如果是中国大陆手机号返回"86"
     */
    public static String extractCountryCode(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
            return null;
        }
        phoneNumber = phoneNumber.trim();
        // 如果是国际手机号格式
        if (Pattern.matches(INTERNATIONAL_PATTERN, phoneNumber)) {
            String[] parts = phoneNumber.split("-", 2);
            return parts[0].substring(1); // 去掉+号
        }
        // 如果是中国大陆手机号
        if (Pattern.matches(CHINA_MOBILE_PATTERN, phoneNumber)) {
            return "86";
        }
        return null;
    }
    /**
     * 提取本地号码部分
     * @param phoneNumber 手机号码
     * @return 本地号码部分
     */
    public static String extractLocalNumber(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
            return null;
        }
        phoneNumber = phoneNumber.trim();
        // 如果是国际手机号格式
        if (Pattern.matches(INTERNATIONAL_PATTERN, phoneNumber)) {
            String[] parts = phoneNumber.split("-", 2);
            return parts[1];
        }
        // 如果是中国大陆手机号
        if (Pattern.matches(CHINA_MOBILE_PATTERN, phoneNumber)) {
            return phoneNumber;
        }
        return null;
    }
    /**
     * 格式化手机号显示
     * @param phoneNumber 手机号码
     * @return 格式化后的手机号
     */
    public static String formatPhoneNumber(String phoneNumber) {
        ValidationResult result = validatePhoneNumber(phoneNumber);
        if (!result.isValid()) {
            return phoneNumber; // 如果格式不正确,返回原值
        }
        if (Pattern.matches(CHINA_MOBILE_PATTERN, phoneNumber)) {
            // 中国大陆手机号格式化为 3-4-4 格式
            return phoneNumber.replaceFirst("(\\d{3})(\\d{4})(\\d{4})", "$1-$2-$3");
        } else if (Pattern.matches(INTERNATIONAL_PATTERN, phoneNumber)) {
            // 国际手机号保持原格式
            return phoneNumber;
        }
        return phoneNumber;
    }
    // 测试方法
    public static void main(String[] args) {
        // 测试用例
        String[] testNumbers = {
                "13800138000",         // 中国大陆手机号
                "+1-1234567890",       // 美国手机号
                "+852-12345678",       // 香港手机号
                "12345678901",         // 无效的中国大陆手机号(不以1开头)
                "+86-13800138000",     // 带国家代码的中国大陆手机号
                "+999-1234567890",     // 不支持的国家代码
                "+1-123",              // 号码部分太短
                "abc",                 // 完全无效
                "+1-1234567890123456"  // 号码部分太长
        };
        for (String number : testNumbers) {
            ValidationResult result = validatePhoneNumber(number);
            System.out.println(number + " : " + result.getMessage());
            if (result.isValid()) {
                System.out.println("  国家代码: " + extractCountryCode(number));
                System.out.println("  本地号码: " + extractLocalNumber(number));
                System.out.println("  格式化: " + formatPhoneNumber(number));
            }
            System.out.println();
        }
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/group/ImGroupController.java
@@ -1,15 +1,24 @@
package com.ruoyi.web.controller.group;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.im.config.AddTeamMembersRequest;
import com.ruoyi.im.config.CreateTeamRequest;
import com.ruoyi.im.config.AssignmentRequest;
import com.ruoyi.im.service.NeteaseTeamService;
import com.ruoyi.system.domain.NeteaseTeam;
import com.ruoyi.system.domain.UserAccount;
import com.ruoyi.system.domain.vo.UserAccountVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/im/group")
@@ -21,6 +30,34 @@
    NeteaseTeamService neteaseGroupService;
    /**
     * 获取会员列表
     */
    @PreAuthorize("@ss.hasPermi('im:group:list')")
    @GetMapping("/list")
    public TableDataInfo list(@PathVariable("keyword") String keyword)
    {
        // 创建查询条件包装器
        LambdaQueryWrapper<NeteaseTeam> queryWrapper = new LambdaQueryWrapper<>();
        // 只有当 keyword 不为空时才添加 OR 条件
        if (ObjectUtil.isNotEmpty(keyword)) {
            queryWrapper.and(wrapper -> wrapper
                    .eq(NeteaseTeam::getTid, keyword)
                    .or()
                    .eq(NeteaseTeam::getOwnerAccountId, keyword)
                    .or()
                    .eq(NeteaseTeam::getName,keyword)
            );
        }
        // 默认按创建时间倒序
        queryWrapper.orderByDesc(NeteaseTeam::getCreateTime);
        List<NeteaseTeam> list = neteaseGroupService.list(queryWrapper);
        return getDataTable(list);
    }
    /**
     * 创建群组
     */
    @PostMapping("/create")
ruoyi-admin/src/main/java/com/ruoyi/web/controller/user/UserController.java
@@ -9,7 +9,10 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.im.comm.Result;
import com.ruoyi.im.dto.RegisterDto;
import com.ruoyi.im.out.UserAccountOut;
import com.ruoyi.im.service.ImApiServcie;
import com.ruoyi.im.util.ConverterUtil;
import com.ruoyi.im.util.PhoneNumberValidatorUtil;
import com.ruoyi.system.domain.UserAccount;
import com.ruoyi.system.domain.vo.UserAccountUpdateVo;
import com.ruoyi.system.domain.vo.UserAccountVo;
@@ -45,6 +48,7 @@
    public TableDataInfo list(UserAccountVo vo)
    {
        // 创建查询条件包装器
        LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<>();
        // 只有当 keyword 不为空时才添加 OR 条件
@@ -70,13 +74,14 @@
        // 默认按创建时间倒序
        queryWrapper.orderByDesc(UserAccount::getCreateTime);
        List<UserAccount> list = userAccountService.list(queryWrapper);
        return getDataTable(list);
        List<UserAccountOut> toList = ConverterUtil.convertToList(list, UserAccountOut.class);
        return getDataTable(toList);
    }
    /**
     * 修改会员
     */
    @PreAuthorize("@ss.hasPermi('im:user:updateUserAccount')")
//    @PreAuthorize("@ss.hasPermi('im:user:updateUserAccount')")
    @PostMapping("/updateUserAccount")
    public AjaxResult updateUserAccount(UserAccountUpdateVo vo) {
@@ -85,6 +90,11 @@
            if(ObjectUtil.isEmpty(userAccount)){
                return AjaxResult.error("会员不存在!");
            }
            PhoneNumberValidatorUtil.ValidationResult result = PhoneNumberValidatorUtil.validatePhoneNumber(vo.getPhoneNumber());
            if(!result.isValid()){
                return AjaxResult.error("手机号格式不正确!");
            }
            vo.setAccountId(userAccount.getCloudMessageAccount());
            return imApiServcie.updateUserAccount(vo);
        }catch (Exception e){
            e.printStackTrace();
@@ -94,14 +104,34 @@
    }
    /**
     * 批量注册
     * 创建会员
     */
    @PostMapping("/batchRegister")
    public Result batchRegister(@Validated RegisterDto dto){
        try {
            if(null == dto.getType()){
                return Result.error("类型不能为空");
            }else if(dto.getType() == 2){
                if(StringUtils.isEmpty(dto.getAccount())){
                    return Result.error("账号不能为空");
                }
                if(StringUtils.isEmpty(dto.getPassword())){
                    return Result.error("密码不能为空");
                }
                if(StringUtils.isEmpty(dto.getNickname())){
                    return Result.error("昵称不能为空");
                }
            }else if (dto.getType() == 1){
                if(dto.getNumber() <= 0){
                    return Result.error("数量不能为空");
                }else if(dto.getNumber() > 100){
                    return Result.error("批量注册最多一次100");
                }
            }
            return imApiServcie.batchRegister(dto);
        }catch (Exception e){
            log.error("批量注册报错:",e);
            e.printStackTrace();
            log.error("-----------批量注册报错------------");
            return Result.error("注册失败,请稍后再试!");
        }
    }
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/UserAccountVo.java
@@ -28,7 +28,7 @@
    private String password;
    // 账号类型: 0:真实 1:虚拟
    private Integer accountType = 0;
    private Integer accountType;
    // 云信账号
    private String cloudMessageAccount;
@@ -37,7 +37,7 @@
    private String nickname;
    // 账号状态: 0:正常 1:禁用
    private Integer status = 0;
    private Integer status;
    // 最近登录时间
    private Date loginTime;