pom.xml
@@ -217,7 +217,6 @@ <artifactId>ruoyi-common</artifactId> <version>${ruoyi.version}</version> </dependency> </dependencies> </dependencyManagement> 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> 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; ruoyi-admin/src/main/java/com/ruoyi/im/ImApiController.java
New file @@ -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("注册失败,请稍后再试!"); } } } ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImBusinessException.java
New file @@ -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); } } ruoyi-admin/src/main/java/com/ruoyi/im/comm/ImConstant.java
New file @@ -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; } ruoyi-admin/src/main/java/com/ruoyi/im/comm/Result.java
File was renamed from ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/comm/Result.java @@ -1,4 +1,4 @@ package com.ruoyi.web.controller.api.comm; package com.ruoyi.im.comm; import java.io.Serializable; ruoyi-admin/src/main/java/com/ruoyi/im/config/AppAuthConfig.java
New file @@ -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; } } ruoyi-admin/src/main/java/com/ruoyi/im/dto/RegisterDto.java
File was renamed from ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/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; // 密码 ruoyi-admin/src/main/java/com/ruoyi/im/service/ImApiServcie.java
New file @@ -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); } ruoyi-admin/src/main/java/com/ruoyi/im/service/impl/ImApiServcieImpl.java
New file @@ -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; } } ruoyi-admin/src/main/java/com/ruoyi/im/util/SymmetricCryptoUtil.java
New file @@ -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); } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/ImApiController.java
File was deleted ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/entity/User.java
File was deleted ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/util/MD5Util.java
File was deleted 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/* 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(); } } ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyMetaObjectHandler.java
New file @@ -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"; } } } ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
New file @@ -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(); } } 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(); }) 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> ruoyi-system/src/main/java/com/ruoyi/system/domain/UserAccount.java
New file @@ -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; } ruoyi-system/src/main/java/com/ruoyi/system/mapper/UserAccountMapper.java
New file @@ -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> { } ruoyi-system/src/main/java/com/ruoyi/system/service/UserAccountService.java
New file @@ -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> { } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserAccountServiceImpl.java
New file @@ -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 { }