From 0b10f87268bd511c7d8dddad30060abefa49aab2 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Tue, 19 Aug 2025 20:25:16 +0800
Subject: [PATCH] 1

---
 ruoyi-admin/src/main/java/com/ruoyi/im/util/SymmetricCryptoUtil.java                 |  116 +++++++
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyMetaObjectHandler.java    |   37 ++
 ruoyi-admin/pom.xml                                                                  |   20 +
 ruoyi-system/pom.xml                                                                 |   25 +
 ruoyi-system/src/main/java/com/ruoyi/system/domain/UserAccount.java                  |   81 +++++
 ruoyi-admin/src/main/java/com/ruoyi/im/comm/Result.java                              |    2 
 pom.xml                                                                              |    1 
 ruoyi-admin/src/main/resources/application.yml                                       |   26 +
 ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java            |  180 ++++++++++++
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java      |   36 ++
 /dev/null                                                                            |   69 ----
 ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java                            |    1 
 ruoyi-admin/src/main/java/com/ruoyi/im/service/ImApiServcie.java                     |    8 
 ruoyi-admin/src/main/java/com/ruoyi/im/ImApiController.java                          |   35 ++
 ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImConstant.java                          |   15 +
 ruoyi-admin/src/main/java/com/ruoyi/im/config/AppAuthConfig.java                     |   39 ++
 ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImBusinessException.java                 |   11 
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java          |  106 ------
 ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java                          |    4 
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java         |    1 
 ruoyi-system/src/main/java/com/ruoyi/system/service/UserAccountService.java          |    7 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserAccountServiceImpl.java |   16 +
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/UserAccountMapper.java            |   14 
 23 files changed, 677 insertions(+), 173 deletions(-)

diff --git a/pom.xml b/pom.xml
index dab4a91..57062bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,7 +217,6 @@
                 <artifactId>ruoyi-common</artifactId>
                 <version>${ruoyi.version}</version>
             </dependency>
-
         </dependencies>
     </dependencyManagement>
 
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index c9e3ba3..bef9bb1 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -64,6 +64,26 @@
             <artifactId>ruoyi-generator</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.netease.nim</groupId>
+            <artifactId>yunxin-server-sdk</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.3.1</version> <!-- 使用最新版本 -->
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus</artifactId>
+            <version>3.5.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.7.12</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
index 32eb6f1..d37e153 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
@@ -1,5 +1,6 @@
 package com.ruoyi;
 
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/ImApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/im/ImApiController.java
new file mode 100644
index 0000000..6b12c7b
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/ImApiController.java
@@ -0,0 +1,35 @@
+package com.ruoyi.im;
+
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.im.comm.Result;
+import com.ruoyi.im.service.ImApiServcie;
+import com.ruoyi.im.dto.RegisterDto;
+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;
+
+@RestController
+@RequestMapping("/im/api")
+@Slf4j
+public class ImApiController {
+
+    @Autowired
+    private ImApiServcie imApiServcie;
+
+    /**
+     * 获取参数配置列表
+     */
+    @PostMapping("/register")
+    public Result register(@Validated RegisterDto dto){
+        try {
+            return imApiServcie.register(dto);
+        }catch (Exception e){
+            log.error("注册报错:",e);
+            return Result.error("注册失败,请稍后再试!");
+        }
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImBusinessException.java b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImBusinessException.java
new file mode 100644
index 0000000..587bec3
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImBusinessException.java
@@ -0,0 +1,11 @@
+package com.ruoyi.im.comm;
+
+public class ImBusinessException extends RuntimeException {
+    public ImBusinessException(String message) {
+        super(message);
+    }
+
+    public ImBusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImConstant.java b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImConstant.java
new file mode 100644
index 0000000..eff9c7d
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImConstant.java
@@ -0,0 +1,15 @@
+package com.ruoyi.im.comm;
+
+public class ImConstant {
+    // 云信API路径
+    public static final String YUNXIN_CREATE_USER_PATH = "/user/create.action";
+
+    // 云信响应码
+    public static final int YUNXIN_SUCCESS_CODE = 200;
+    public static final int YUNXIN_ACCOUNT_EXISTS_CODE = 414;
+
+    // 业务常量
+    public static final int MAX_GENERATE_ACCOUNT_ATTEMPTS = 10;
+    public static final int MIN_GENERATE_DELAY_MS = 5;
+    public static final int MAX_GENERATE_DELAY_MS = 30;
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/comm/Result.java b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/Result.java
similarity index 98%
rename from ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/comm/Result.java
rename to ruoyi-admin/src/main/java/com/ruoyi/im/comm/Result.java
index e4f72bf..b23ad45 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/comm/Result.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/comm/Result.java
@@ -1,4 +1,4 @@
-package com.ruoyi.web.controller.api.comm;
+package com.ruoyi.im.comm;
 
 import java.io.Serializable;
 
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/config/AppAuthConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/im/config/AppAuthConfig.java
new file mode 100644
index 0000000..e9b5674
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/config/AppAuthConfig.java
@@ -0,0 +1,39 @@
+package com.ruoyi.im.config;
+
+public enum AppAuthConfig {
+
+    /**
+     * 应用认证配置
+     */
+    DEFAULT_CONFIG("cd693c66bb6992a7cc2b13379e981d48", "720c0b98f7a3");
+
+    private final String appKey;
+    private final String appSecret;
+
+    AppAuthConfig(String appKey, String appSecret) {
+        this.appKey = appKey;
+        this.appSecret = appSecret;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
+
+    /**
+     * 根据key查找配置
+     * @param key 应用key
+     * @return 对应的配置,找不到返回null
+     */
+    public static AppAuthConfig getByKey(String key) {
+        for (AppAuthConfig config : values()) {
+            if (config.getAppKey().equals(key)) {
+                return config;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/dto/RegisterDto.java b/ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java
similarity index 85%
rename from ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/dto/RegisterDto.java
rename to ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java
index c97b94c..bf0ce54 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/dto/RegisterDto.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java
@@ -1,4 +1,4 @@
-package com.ruoyi.web.controller.api.dto;
+package com.ruoyi.im.dto;
 
 import lombok.Data;
 
@@ -9,7 +9,7 @@
 public class RegisterDto {
 
     @NotNull(message = "账号不能为空")
-    private Integer account; // 账号
+    private String account; // 账号
 
     @NotEmpty(message = "密码不能为空")
     private String password; // 密码
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/service/ImApiServcie.java b/ruoyi-admin/src/main/java/com/ruoyi/im/service/ImApiServcie.java
new file mode 100644
index 0000000..c658f6e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/service/ImApiServcie.java
@@ -0,0 +1,8 @@
+package com.ruoyi.im.service;
+
+import com.ruoyi.im.comm.Result;
+import com.ruoyi.im.dto.RegisterDto;
+
+public interface ImApiServcie {
+    Result register(RegisterDto dto);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java
new file mode 100644
index 0000000..6a260c1
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java
@@ -0,0 +1,180 @@
+package com.ruoyi.im.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.ruoyi.im.comm.Result;
+import com.ruoyi.im.config.AppAuthConfig;
+import com.ruoyi.system.domain.UserAccount;
+import com.ruoyi.im.service.ImApiServcie;
+import com.ruoyi.im.dto.RegisterDto;
+import com.ruoyi.system.service.UserAccountService;
+import com.ruoyi.im.util.SymmetricCryptoUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.PostConstruct;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicLong;
+
+@Service
+public class ImApiServcieImpl implements ImApiServcie {
+    @Autowired
+    private UserAccountService userAccountService;
+
+    // 使用单例客户端,避免重复初始化
+    private YunxinApiHttpClient yunxinClient;
+
+    // 使用并发安全的集合存储正在生成的账号,防止重复
+    private final Set<Long> generatingAccounts = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+    // 使用原子计数器优化账号生成
+    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;
+
+        this.yunxinClient = new YunxinApiHttpClient.Builder(BizName.IM, appkey, appsecret)
+                .timeoutMillis(timeoutMillis)
+                .build();
+    }
+
+    @Override
+    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) {
+            return Result.error("账号已被注册!");
+        } else {
+            System.err.println("register fail, response = " + data + ", traceId=" + response.getTraceId());
+            return Result.error("注册失败,错误码: " + 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) {
+                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;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/im/util/SymmetricCryptoUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/im/util/SymmetricCryptoUtil.java
new file mode 100644
index 0000000..46fe42e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/im/util/SymmetricCryptoUtil.java
@@ -0,0 +1,116 @@
+package com.ruoyi.im.util;
+
+import cn.hutool.crypto.Mode;
+import cn.hutool.crypto.Padding;
+import cn.hutool.crypto.symmetric.AES;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @ClassName : SymmetricCryptoUtil  //类名
+ * @Description : 对称加密工具类  //描述
+ * @Author :  tf //作者
+ * @Date: 2022/10/23  23:31
+ */
+public class SymmetricCryptoUtil {
+
+    /**
+     * 16字节的密钥(128位)
+     */
+    private static final String ENCODE_KEY = "03SsJLwB03SsJLwB"; // 改为16字节
+
+    /**
+     * 16字节的初始化向量(128位)
+     */
+    private static final String IV_KEY = "0000000000000000"; // 改为16个0
+
+    public static void main(String[] args) {
+        String originalText = "zdm321123.";
+
+        String encryptData = encryptFromString(originalText, Mode.CBC, Padding.ZeroPadding);
+        System.out.println("加密:" + encryptData);
+
+        String decryptData = decryptFromString(encryptData, Mode.CBC, Padding.ZeroPadding);
+        System.out.println("解密:" + decryptData);
+
+        // 验证加解密是否一致
+        System.out.println("验证结果:" + originalText.equals(decryptData));
+    }
+
+    /**
+     * 加密字符串
+     */
+    public static String encryptFromString(String data, Mode mode, Padding padding) {
+        AES aes;
+        if (Mode.CBC == mode) {
+            aes = new AES(mode, padding,
+                    new SecretKeySpec(ENCODE_KEY.getBytes(StandardCharsets.UTF_8), "AES"),
+                    new IvParameterSpec(IV_KEY.getBytes(StandardCharsets.UTF_8)));
+        } else {
+            aes = new AES(mode, padding,
+                    new SecretKeySpec(ENCODE_KEY.getBytes(StandardCharsets.UTF_8), "AES"));
+        }
+        return aes.encryptBase64(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 解密字符串
+     */
+    public static String decryptFromString(String data, Mode mode, Padding padding) {
+        AES aes;
+        if (Mode.CBC == mode) {
+            aes = new AES(mode, padding,
+                    new SecretKeySpec(ENCODE_KEY.getBytes(StandardCharsets.UTF_8), "AES"),
+                    new IvParameterSpec(IV_KEY.getBytes(StandardCharsets.UTF_8)));
+        } else {
+            aes = new AES(mode, padding,
+                    new SecretKeySpec(ENCODE_KEY.getBytes(StandardCharsets.UTF_8), "AES"));
+        }
+        byte[] decryptDataBase64 = aes.decrypt(data);
+        return new String(decryptDataBase64, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @Description:  加密密码(使用CBC模式和ZeroPadding)
+     * @Param:  password 明文密码
+     * @return:  加密后的Base64字符串
+     * @Author: tf
+     * @Date: 2022/10/23
+     */
+    public static String encryptPassword(String password) {
+        return encryptFromString(password, Mode.CBC, Padding.ZeroPadding);
+    }
+
+    /**
+     * @Description:  解密密码
+     * @Param:  encryptedPassword 加密后的密码字符串
+     * @return:  解密后的明文密码
+     * @Author: tf
+     * @Date: 2022/10/23
+     */
+    public static String decryptPassword(String encryptedPassword) {
+        return decryptFromString(encryptedPassword, Mode.CBC, Padding.ZeroPadding);
+    }
+
+    /**
+     * 生成随机的16字节密钥(用于生产环境)
+     */
+    public static String generateRandomKey() {
+        java.security.SecureRandom random = new java.security.SecureRandom();
+        byte[] key = new byte[16];
+        random.nextBytes(key);
+        return new String(key, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 生成随机的16字节IV(用于生产环境)
+     */
+    public static String generateRandomIV() {
+        java.security.SecureRandom random = new java.security.SecureRandom();
+        byte[] iv = new byte[16];
+        random.nextBytes(iv);
+        return new String(iv, StandardCharsets.UTF_8);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/ImApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/ImApiController.java
deleted file mode 100644
index 7119fef..0000000
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/ImApiController.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.ruoyi.web.controller.api;
-
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.system.domain.SysConfig;
-import com.ruoyi.web.controller.api.comm.Result;
-import com.ruoyi.web.controller.api.dto.RegisterDto;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("api/")
-public class ImApiController {
-
-    /**
-     * 获取参数配置列表
-     */
-    @GetMapping("/register")
-    public Result register(RegisterDto dto){
-        if (!dto.getPassword().equals(dto.getConfirmPassword())) {
-            throw new IllegalArgumentException("两次输入的密码不一致");
-        }
-
-        return Result.success();
-    }
-
-
-}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/entity/User.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/entity/User.java
deleted file mode 100644
index 0fcaef0..0000000
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/entity/User.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.ruoyi.web.controller.api.entity;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.models.auth.In;
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.Date;
-
-@Data
-public class User {
-
-    // 自增ID主键
-    private Integer id;
-
-    // 账号(唯一)
-    private String account;
-
-    // 手机号(唯一)
-    private String phoneNumber;
-
-    // 密码
-    private String password;
-
-    // 账号类型: 0:真实 1:虚拟
-    private Integer accountType;
-
-    // 云信账号
-    private String cloudMessageAccount;
-
-    // 昵称
-    private String nickname;
-
-    // 账户余额
-    private BigDecimal balance;
-
-    // 账号状态: 0:正常 1:禁用
-    private String status;
-
-    // 最近登录时间
-    private LocalDateTime loginTime;
-
-    // 最近登录IP
-    private String loginIp;
-
-    // 注册时间
-    private LocalDateTime registerTime;
-
-    // 个性签名
-    private String signature;
-
-    // 是否支持昵称搜索
-    private Boolean supportNicknameSearch;
-
-    // 是否支持手机搜索
-    private Boolean supportPhoneSearch;
-
-    // 是否支持ID搜索
-    private Boolean supportIdSearch;
-
-    // 是否支持用户名搜索
-    private Boolean supportUsernameSearch;
-
-    // 是否申请删除账号
-    private Boolean requestDelete;
-
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date createTime;
-
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date updateTime;
-
-}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/util/MD5Util.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/util/MD5Util.java
deleted file mode 100644
index 4f43713..0000000
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/util/MD5Util.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.ruoyi.web.controller.api.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-
-public class MD5Util {
-
-    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
-
-    /**
-     * 计算字符串MD5
-     */
-    public static String md5(String input) {
-        if (input == null) return null;
-        try {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
-            return bytesToHex(digest);
-        } catch (Exception e) {
-            throw new RuntimeException("MD5计算失败", e);
-        }
-    }
-
-    /**
-     * 计算文件MD5
-     */
-    public static String md5(File file) {
-        if (file == null || !file.exists()) return null;
-
-        try (FileInputStream fis = new FileInputStream(file)) {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            byte[] buffer = new byte[8192];
-            int length;
-            while ((length = fis.read(buffer)) != -1) {
-                md.update(buffer, 0, length);
-            }
-            return bytesToHex(md.digest());
-        } catch (Exception e) {
-            throw new RuntimeException("文件MD5计算失败", e);
-        }
-    }
-
-    /**
-     * 字节数组转十六进制字符串
-     */
-    private static String bytesToHex(byte[] bytes) {
-        if (bytes == null) return null;
-        StringBuilder sb = new StringBuilder(bytes.length * 2);
-        for (byte b : bytes) {
-            sb.append(HEX_CHARS[(b >> 4) & 0x0F]);
-            sb.append(HEX_CHARS[b & 0x0F]);
-        }
-        return sb.toString();
-    }
-
-    /**
-     * 验证MD5
-     */
-    public static boolean verify(String input, String md5) {
-        return md5(input).equalsIgnoreCase(md5);
-    }
-
-    public static boolean verify(File file, String md5) {
-        return md5(file).equalsIgnoreCase(md5);
-    }
-}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 7089c0f..db8c314 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -96,16 +96,30 @@
   secret: abcdefghijklmnopqrstuvwxyz
   # 令牌有效期(默认30分钟)
   expireTime: 30
-
-# MyBatis配置
+# 原有 MyBatis 配置保持不变
 mybatis:
-  # 搜索指定包别名
   typeAliasesPackage: com.ruoyi.**.domain
-  # 配置mapper的扫描,找到所有的mapper.xml映射文件
   mapperLocations: classpath*:mapper/**/*Mapper.xml
-  # 加载全局的配置文件
   configLocation: classpath:mybatis/mybatis-config.xml
 
+# 新增 MyBatis-Plus 配置
+mybatis-plus:
+  # 可以指向新的目录,避免与原有XML冲突
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  type-aliases-package: com.ruoyi.**.domain
+  global-config:
+    db-config:
+      id-type: AUTO
+      logic-delete-value: 2  # 若依默认逻辑删除值是2
+      logic-not-delete-value: 0
+      # 字段策略:0:忽略判断,1:非NULL判断,2:非空判断
+      insert-strategy: not_null
+      update-strategy: not_null
+  configuration:
+    # 保持与原有配置一致
+    map-underscore-to-camel-case: true
+    # 开启SQL日志
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 # PageHelper分页插件
 pagehelper:
   helperDialect: mysql
@@ -126,4 +140,4 @@
   # 排除链接(多个用逗号分隔)
   excludes: /system/notice
   # 匹配链接
-  urlPatterns: /system/*,/monitor/*,/tool/*
+  urlPatterns: /system/*,/monitor/*,/tool/*
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
index 057c941..a85bd77 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
@@ -6,6 +6,8 @@
 import java.util.HashSet;
 import java.util.List;
 import javax.sql.DataSource;
+
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import org.apache.ibatis.io.VFS;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.SqlSessionFactoryBean;
@@ -32,101 +34,19 @@
 @Configuration
 public class MyBatisConfig
 {
-    @Autowired
-    private Environment env;
-
-    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
-
-    public static String setTypeAliasesPackage(String typeAliasesPackage)
-    {
-        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
-        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
-        List<String> allResult = new ArrayList<String>();
-        try
-        {
-            for (String aliasesPackage : typeAliasesPackage.split(","))
-            {
-                List<String> result = new ArrayList<String>();
-                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
-                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
-                Resource[] resources = resolver.getResources(aliasesPackage);
-                if (resources != null && resources.length > 0)
-                {
-                    MetadataReader metadataReader = null;
-                    for (Resource resource : resources)
-                    {
-                        if (resource.isReadable())
-                        {
-                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
-                            try
-                            {
-                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
-                            }
-                            catch (ClassNotFoundException e)
-                            {
-                                e.printStackTrace();
-                            }
-                        }
-                    }
-                }
-                if (result.size() > 0)
-                {
-                    HashSet<String> hashResult = new HashSet<String>(result);
-                    allResult.addAll(hashResult);
-                }
-            }
-            if (allResult.size() > 0)
-            {
-                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
-            }
-            else
-            {
-                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
-            }
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-        return typeAliasesPackage;
-    }
-
-    public Resource[] resolveMapperLocations(String[] mapperLocations)
-    {
-        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
-        List<Resource> resources = new ArrayList<Resource>();
-        if (mapperLocations != null)
-        {
-            for (String mapperLocation : mapperLocations)
-            {
-                try
-                {
-                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
-                    resources.addAll(Arrays.asList(mappers));
-                }
-                catch (IOException e)
-                {
-                    // ignore
-                }
-            }
-        }
-        return resources.toArray(new Resource[resources.size()]);
-    }
-
     @Bean
-    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
-    {
-        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
-        String mapperLocations = env.getProperty("mybatis.mapperLocations");
-        String configLocation = env.getProperty("mybatis.configLocation");
-        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
-        VFS.addImplClass(SpringBootVFS.class);
-
-        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
+        // 使用 MyBatis-Plus 的 SqlSessionFactory
+        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
         sessionFactory.setDataSource(dataSource);
-        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
-        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
-        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+
+        // 设置mapper.xml文件的位置
+        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
+                .getResources("classpath*:mapper/**/*Mapper.xml"));
+
+        // 设置类型别名包
+        sessionFactory.setTypeAliasesPackage("com.ruoyi.**.domain");
+
         return sessionFactory.getObject();
     }
 }
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyMetaObjectHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyMetaObjectHandler.java
new file mode 100644
index 0000000..f59008c
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyMetaObjectHandler.java
@@ -0,0 +1,37 @@
+package com.ruoyi.framework.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
+        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
+        // 若依特有的字段
+        this.strictInsertFill(metaObject, "createBy", String.class, getUsername());
+        this.strictInsertFill(metaObject, "updateBy", String.class, getUsername());
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
+        this.strictUpdateFill(metaObject, "updateBy", String.class, getUsername());
+    }
+
+    private String getUsername() {
+        // 从安全上下文中获取当前用户名
+        try {
+            // 若依原有的获取用户名方式
+            // return SecurityUtils.getUsername();
+            return "admin"; // 临时写死,实际中需要动态获取
+        } catch (Exception e) {
+            return "system";
+        }
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..575f9dc
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
@@ -0,0 +1,36 @@
+package com.ruoyi.framework.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisPlusConfig {
+
+    /**
+     * 添加插件
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+
+        // 分页插件
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+
+        // 乐观锁插件(如果需要)
+        // interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+
+        return interceptor;
+    }
+
+    /**
+     * 自动填充处理器(用于创建时间、更新时间等)
+     */
+    @Bean
+    public MyMetaObjectHandler metaObjectHandler() {
+        return new MyMetaObjectHandler();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 511842b..222ba3f 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -115,6 +115,7 @@
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                     .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+                    .antMatchers("/im/api/**").permitAll()
                     // 除上面外的所有请求全部需要鉴权认证
                     .anyRequest().authenticated();
             })
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index f514d3d..ec01f08 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -22,7 +22,30 @@
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>com.netease.nim</groupId>
+            <artifactId>yunxin-server-sdk</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.3.1</version> <!-- 使用最新版本 -->
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus</artifactId>
+            <version>3.5.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.7.12</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/UserAccount.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/UserAccount.java
new file mode 100644
index 0000000..73bb259
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/UserAccount.java
@@ -0,0 +1,81 @@
+package com.ruoyi.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("user_account") // 指定表名
+public class UserAccount {
+
+    // 自增ID主键
+    @TableId(type = IdType.AUTO) // 主键自增
+    private Integer id;
+
+    // 账号(唯一)
+    private String account;
+
+    // 手机号(唯一)
+    private String phoneNumber;
+
+    // 密码
+    private String password;
+
+    // 账号类型: 0:真实 1:虚拟
+    private Integer accountType = 0;
+
+    // 云信账号
+    private String cloudMessageAccount;
+
+    // 昵称
+    private String nickname;
+
+    // 账户余额
+    private BigDecimal balance = BigDecimal.ZERO;
+
+    // 账号状态: 0:正常 1:禁用
+    private Integer status = 0;
+
+    // 最近登录时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date loginTime;
+
+    // 最近登录IP
+    private String loginIp;
+
+    // 注册时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date registerTime;
+
+    // 个性签名
+    private String signature;
+
+    // 是否支持昵称搜索
+    private Boolean supportNicknameSearch = true;
+
+    // 是否支持手机搜索
+    private Boolean supportPhoneSearch = true;
+
+    // 是否支持ID搜索
+    private Boolean supportIdSearch = true;
+
+    // 是否支持用户名搜索
+    private Boolean supportUsernameSearch = true;
+
+    // 是否申请删除账号
+    private Boolean requestDelete = true;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT) // 自动填充
+    private Date createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    private Boolean deleted = true;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/UserAccountMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/UserAccountMapper.java
new file mode 100644
index 0000000..929e174
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/UserAccountMapper.java
@@ -0,0 +1,14 @@
+package com.ruoyi.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.system.domain.UserAccount;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @program: ruoyiim
+ * @description:
+ * @create: 2025-08-19 17:02
+ **/
+@Mapper
+public interface  UserAccountMapper extends BaseMapper<UserAccount> {
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/UserAccountService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/UserAccountService.java
new file mode 100644
index 0000000..d469d22
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/UserAccountService.java
@@ -0,0 +1,7 @@
+package com.ruoyi.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.system.domain.UserAccount;
+
+public interface UserAccountService extends IService<UserAccount> {
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserAccountServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserAccountServiceImpl.java
new file mode 100644
index 0000000..8ba90d0
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserAccountServiceImpl.java
@@ -0,0 +1,16 @@
+package com.ruoyi.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.system.domain.UserAccount;
+import com.ruoyi.system.mapper.UserAccountMapper;
+import com.ruoyi.system.service.UserAccountService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @program: ruoyiim
+ * @description:
+ * @create: 2025-08-19 16:41
+ **/
+@Service
+public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {
+}

--
Gitblit v1.9.3